uk_bin_collection 0.143.6__py3-none-any.whl → 0.144.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uk_bin_collection/tests/input.json +11 -2
- uk_bin_collection/uk_bin_collection/councils/GreatYarmouthBoroughCouncil.py +140 -0
- uk_bin_collection/uk_bin_collection/councils/SandwellBoroughCouncil.py +32 -24
- {uk_bin_collection-0.143.6.dist-info → uk_bin_collection-0.144.1.dist-info}/METADATA +1 -1
- {uk_bin_collection-0.143.6.dist-info → uk_bin_collection-0.144.1.dist-info}/RECORD +8 -7
- {uk_bin_collection-0.143.6.dist-info → uk_bin_collection-0.144.1.dist-info}/LICENSE +0 -0
- {uk_bin_collection-0.143.6.dist-info → uk_bin_collection-0.144.1.dist-info}/WHEEL +0 -0
- {uk_bin_collection-0.143.6.dist-info → uk_bin_collection-0.144.1.dist-info}/entry_points.txt +0 -0
@@ -877,6 +877,15 @@
|
|
877
877
|
"wiki_name": "Gravesham Borough Council",
|
878
878
|
"wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search)."
|
879
879
|
},
|
880
|
+
"GreatYarmouthBoroughCouncil": {
|
881
|
+
"postcode": "NR31 7EB",
|
882
|
+
"skip_get_url": true,
|
883
|
+
"uprn": "100090834792",
|
884
|
+
"url": "https://myaccount.great-yarmouth.gov.uk/article/6456/Find-my-waste-collection-days",
|
885
|
+
"web_driver": "http://selenium:4444",
|
886
|
+
"wiki_name": "Great Yarmouth Borough Council",
|
887
|
+
"wiki_note": "Pass the postcode, and UPRN in their respective parameters. This parser requires a Selenium webdriver."
|
888
|
+
},
|
880
889
|
"GuildfordCouncil": {
|
881
890
|
"house_number": "THE LODGE, PUTTENHAM HILL HOUSE, PUTTENHAM HILL, PUTTENHAM, GUILDFORD, GU3 1AH",
|
882
891
|
"postcode": "GU3 1AH",
|
@@ -1668,7 +1677,7 @@
|
|
1668
1677
|
},
|
1669
1678
|
"SandwellBoroughCouncil": {
|
1670
1679
|
"skip_get_url": true,
|
1671
|
-
"uprn": "
|
1680
|
+
"uprn": "32101971",
|
1672
1681
|
"url": "https://www.sandwell.gov.uk",
|
1673
1682
|
"wiki_name": "Sandwell Borough Council",
|
1674
1683
|
"wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search)."
|
@@ -2323,4 +2332,4 @@
|
|
2323
2332
|
"wiki_name": "York Council",
|
2324
2333
|
"wiki_note": "Provide your UPRN."
|
2325
2334
|
}
|
2326
|
-
}
|
2335
|
+
}
|
@@ -0,0 +1,140 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
from bs4 import BeautifulSoup
|
4
|
+
from selenium.webdriver.common.by import By
|
5
|
+
from selenium.webdriver.support import expected_conditions as EC
|
6
|
+
from selenium.webdriver.support.ui import Select, WebDriverWait
|
7
|
+
from selenium.webdriver.common.keys import Keys
|
8
|
+
from datetime import datetime
|
9
|
+
|
10
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
11
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
12
|
+
|
13
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
14
|
+
class CouncilClass(AbstractGetBinDataClass):
|
15
|
+
"""
|
16
|
+
Concrete classes have to implement all abstract operations of the
|
17
|
+
base class. They can also override some operations with a default
|
18
|
+
implementation.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
22
|
+
driver = None
|
23
|
+
try:
|
24
|
+
user_uprn = kwargs.get("uprn")
|
25
|
+
user_postcode = kwargs.get("postcode")
|
26
|
+
headless = kwargs.get("headless")
|
27
|
+
web_driver = kwargs.get("web_driver")
|
28
|
+
url = kwargs.get("url")
|
29
|
+
|
30
|
+
check_uprn(user_uprn)
|
31
|
+
check_postcode(user_postcode)
|
32
|
+
|
33
|
+
driver = create_webdriver(web_driver, headless, None, __name__)
|
34
|
+
|
35
|
+
driver.get(url)
|
36
|
+
|
37
|
+
wait = WebDriverWait(driver, 10)
|
38
|
+
accept_cookies_button = wait.until(
|
39
|
+
EC.element_to_be_clickable(
|
40
|
+
(
|
41
|
+
By.NAME,
|
42
|
+
"acceptall",
|
43
|
+
)
|
44
|
+
)
|
45
|
+
)
|
46
|
+
accept_cookies_button.click()
|
47
|
+
|
48
|
+
postcode_input = WebDriverWait(driver, 10).until(
|
49
|
+
EC.element_to_be_clickable(
|
50
|
+
(By.ID, "WASTECOLLECTIONCALENDARV2_ADDRESS_ALSF")
|
51
|
+
)
|
52
|
+
)
|
53
|
+
|
54
|
+
postcode_input.send_keys(user_postcode)
|
55
|
+
postcode_input.send_keys(Keys.TAB + Keys.ENTER)
|
56
|
+
|
57
|
+
time.sleep(2)
|
58
|
+
# Wait for address box to be visible
|
59
|
+
select_address_input = WebDriverWait(driver, 10).until(
|
60
|
+
EC.presence_of_element_located(
|
61
|
+
(
|
62
|
+
By.ID,
|
63
|
+
"WASTECOLLECTIONCALENDARV2_ADDRESS_ALML",
|
64
|
+
)
|
65
|
+
)
|
66
|
+
)
|
67
|
+
select_address_input.click()
|
68
|
+
|
69
|
+
# Assume select_address_input is already the dropdown <select> element
|
70
|
+
select = Select(select_address_input)
|
71
|
+
|
72
|
+
# Select the option with the matching UPRN
|
73
|
+
select.select_by_value(user_uprn)
|
74
|
+
select_address_input.click()
|
75
|
+
|
76
|
+
select_address_input.send_keys(Keys.TAB * 2 + Keys.ENTER)
|
77
|
+
|
78
|
+
time.sleep(5)
|
79
|
+
# Wait for the specified div to be present
|
80
|
+
target_div = WebDriverWait(driver, 10).until(
|
81
|
+
EC.presence_of_element_located((By.ID, "WASTECOLLECTIONCALENDARV2_LOOKUP_SHOWSCHEDULE"))
|
82
|
+
)
|
83
|
+
|
84
|
+
soup = BeautifulSoup(driver.page_source, "html.parser")
|
85
|
+
|
86
|
+
bin_data = {"bins": []}
|
87
|
+
next_collections = {} # Dictionary to store the next collection for each bin type
|
88
|
+
|
89
|
+
bin_types = {
|
90
|
+
"bulky": "Bulky Collection",
|
91
|
+
"green": "Recycling",
|
92
|
+
"black": "General Waste",
|
93
|
+
"brown": "Garden Waste"
|
94
|
+
}
|
95
|
+
|
96
|
+
for div in soup.select(".collection-area"):
|
97
|
+
img = div.select_one("img")
|
98
|
+
detail = div.select_one(".collection-detail")
|
99
|
+
date_text = detail.select_one("b").get_text(strip=True)
|
100
|
+
|
101
|
+
try:
|
102
|
+
# Parse the date text
|
103
|
+
date_obj = datetime.strptime(date_text + " 2025", "%A %d %B %Y")
|
104
|
+
if date_obj.date() < datetime.today().date():
|
105
|
+
continue # Skip past dates
|
106
|
+
except ValueError:
|
107
|
+
continue
|
108
|
+
|
109
|
+
# Determine bin type from alt or description
|
110
|
+
description = detail.get_text(separator=" ", strip=True).lower()
|
111
|
+
alt_text = img['alt'].lower()
|
112
|
+
|
113
|
+
for key, name in bin_types.items():
|
114
|
+
if key in alt_text or key in description:
|
115
|
+
# Format date as dd/mm/yyyy
|
116
|
+
formatted_date = date_obj.strftime("%d/%m/%Y")
|
117
|
+
bin_entry = {
|
118
|
+
"type": name,
|
119
|
+
"collectionDate": formatted_date
|
120
|
+
}
|
121
|
+
|
122
|
+
# Only keep the earliest date for each bin type
|
123
|
+
if name not in next_collections or date_obj < datetime.strptime(next_collections[name]["collectionDate"], "%d/%m/%Y"):
|
124
|
+
next_collections[name] = bin_entry
|
125
|
+
print(f"Found next collection for {name}: {formatted_date}") # Debug output
|
126
|
+
break
|
127
|
+
|
128
|
+
# Add the next collections to the bin_data
|
129
|
+
bin_data["bins"] = list(next_collections.values())
|
130
|
+
|
131
|
+
except Exception as e:
|
132
|
+
print(f"An error occurred: {e}")
|
133
|
+
raise
|
134
|
+
finally:
|
135
|
+
if driver:
|
136
|
+
driver.quit()
|
137
|
+
|
138
|
+
print("\nFinal bin data:")
|
139
|
+
print(bin_data) # Debug output
|
140
|
+
return bin_data
|
@@ -32,12 +32,13 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
32
32
|
"Referer": "https://my.sandwell.gov.uk/fillform/?iframe_id=fillform-frame-1&db_id=",
|
33
33
|
}
|
34
34
|
s = requests.session()
|
35
|
+
# Establish a session and grab the session ID
|
35
36
|
r = s.get(SESSION_URL)
|
36
37
|
r.raise_for_status()
|
37
38
|
session_data = r.json()
|
38
39
|
sid = session_data["auth-session"]
|
39
40
|
|
40
|
-
|
41
|
+
payload = {
|
41
42
|
"formValues": {
|
42
43
|
"Property details": {
|
43
44
|
"Uprn": {
|
@@ -49,9 +50,7 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
49
50
|
},
|
50
51
|
},
|
51
52
|
}
|
52
|
-
|
53
|
-
params = {
|
54
|
-
"id": "58a1a71694992",
|
53
|
+
base_params = {
|
55
54
|
"repeat_against": "",
|
56
55
|
"noRetry": "false",
|
57
56
|
"getOnlyTokens": "undefined",
|
@@ -61,27 +60,36 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
61
60
|
"_": str(int(time.time() * 1000)),
|
62
61
|
"sid": sid,
|
63
62
|
}
|
63
|
+
# (request_id, date field to use from response, bin type labels)
|
64
|
+
lookups = [
|
65
|
+
(
|
66
|
+
"58a1a71694992",
|
67
|
+
"DWDate",
|
68
|
+
[
|
69
|
+
"Recycling (Blue)",
|
70
|
+
"Household Waste (Grey)",
|
71
|
+
"Food Waste (Brown)",
|
72
|
+
"Batteries",
|
73
|
+
],
|
74
|
+
),
|
75
|
+
("56b1cdaf6bb43", "GWDate", ["Garden Waste (Green)"]),
|
76
|
+
]
|
64
77
|
|
65
|
-
|
66
|
-
|
78
|
+
for request_id, date_key, bin_types in lookups:
|
79
|
+
params = {"id": request_id, **base_params}
|
67
80
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
dict_data = {
|
82
|
-
"type": bin_type,
|
83
|
-
"collectionDate": date,
|
84
|
-
}
|
85
|
-
bindata["bins"].append(dict_data)
|
81
|
+
resp = s.post(API_URL, json=payload, headers=headers, params=params)
|
82
|
+
resp.raise_for_status()
|
83
|
+
result = resp.json()
|
84
|
+
|
85
|
+
rows_data = result["integration"]["transformed"]["rows_data"]
|
86
|
+
if not isinstance(rows_data, dict):
|
87
|
+
# Garden waste for some Uprns returns an empty list
|
88
|
+
continue
|
89
|
+
|
90
|
+
for row in rows_data.values():
|
91
|
+
date = row[date_key]
|
92
|
+
for bin_type in bin_types:
|
93
|
+
bindata["bins"].append({"type": bin_type, "collectionDate": date})
|
86
94
|
|
87
95
|
return bindata
|
@@ -3,7 +3,7 @@ uk_bin_collection/tests/check_selenium_url_in_input.json.py,sha256=Iecdja0I3XIiY
|
|
3
3
|
uk_bin_collection/tests/council_feature_input_parity.py,sha256=DO6Mk4ImYgM5ZCZ-cutwz5RoYYWZRLYx2tr6zIs_9Rc,3843
|
4
4
|
uk_bin_collection/tests/features/environment.py,sha256=VQZjJdJI_kZn08M0j5cUgvKT4k3iTw8icJge1DGOkoA,127
|
5
5
|
uk_bin_collection/tests/features/validate_council_outputs.feature,sha256=SJK-Vc737hrf03tssxxbeg_JIvAH-ddB8f6gU1LTbuQ,251
|
6
|
-
uk_bin_collection/tests/input.json,sha256=
|
6
|
+
uk_bin_collection/tests/input.json,sha256=6GN41kIFiUAvCo4mzwTWpxumcY9MSYTqCIqsvY5wg_4,121957
|
7
7
|
uk_bin_collection/tests/output.schema,sha256=ZwKQBwYyTDEM4G2hJwfLUVM-5v1vKRvRK9W9SS1sd18,1086
|
8
8
|
uk_bin_collection/tests/step_defs/step_helpers/file_handler.py,sha256=Ygzi4V0S1MIHqbdstUlIqtRIwnynvhu4UtpweJ6-5N8,1474
|
9
9
|
uk_bin_collection/tests/step_defs/test_validate_council.py,sha256=VZ0a81sioJULD7syAYHjvK_-nT_Rd36tUyzPetSA0gk,3475
|
@@ -131,6 +131,7 @@ uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py,sha256=9Rk9Kf
|
|
131
131
|
uk_bin_collection/uk_bin_collection/councils/GloucesterCityCouncil.py,sha256=67D8rbhn0t4rsCSJRTXZVtHmph2wT6rJiexNWKOnMok,4625
|
132
132
|
uk_bin_collection/uk_bin_collection/councils/GooglePublicCalendarCouncil.py,sha256=KiDfL9AVhUlRuY98sbNs_Fb-vUVeo5n1bBShTJHcO8w,1173
|
133
133
|
uk_bin_collection/uk_bin_collection/councils/GraveshamBoroughCouncil.py,sha256=ueQ9xFiTxMUBTGV9VjtySHA1EFWliTM0AeNePBIG9ho,4568
|
134
|
+
uk_bin_collection/uk_bin_collection/councils/GreatYarmouthBoroughCouncil.py,sha256=6NIzcn6iiXHEeBer1TlUAAXRWSJOFiYO1IUDmLkF7R0,5295
|
134
135
|
uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py,sha256=9pVrmQhZcK2AD8gX8mNvP--L4L9KaY6L3B822VX6fec,5695
|
135
136
|
uk_bin_collection/uk_bin_collection/councils/GwyneddCouncil.py,sha256=eK2KkY1NbIxVtBruQYSNPA0J7fuzMik5it02dFbKYV0,1855
|
136
137
|
uk_bin_collection/uk_bin_collection/councils/HackneyCouncil.py,sha256=vO3ugk5fcdkYTslXNKmpJC84-ZHqrdFqW8zfX7TSiTQ,3104
|
@@ -238,7 +239,7 @@ uk_bin_collection/uk_bin_collection/councils/RunnymedeBoroughCouncil.py,sha256=v
|
|
238
239
|
uk_bin_collection/uk_bin_collection/councils/RushcliffeBoroughCouncil.py,sha256=nWo8xeER71FEbnMTX8W9bcwZNpLEExWzPvgRT7DmcMc,4221
|
239
240
|
uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py,sha256=ZsGnXjoEaOS6U7fI0w7-uqxayAHdNVKsJi2fqIWEls8,3375
|
240
241
|
uk_bin_collection/uk_bin_collection/councils/SalfordCityCouncil.py,sha256=XUGemp2cdzsvkWjnv2m4YKTMcoKDUfIlVy3YucX-_o4,2601
|
241
|
-
uk_bin_collection/uk_bin_collection/councils/SandwellBoroughCouncil.py,sha256=
|
242
|
+
uk_bin_collection/uk_bin_collection/councils/SandwellBoroughCouncil.py,sha256=Y9aPYNu1ZahJJ0BuDIWoQqynSz2AQGUcwrJR_cvPsvc,3516
|
242
243
|
uk_bin_collection/uk_bin_collection/councils/SeftonCouncil.py,sha256=XUEz2li0oHrRhdkls5qzlZNZ0GuwSG7r0dwsL-qdoFA,2480
|
243
244
|
uk_bin_collection/uk_bin_collection/councils/SevenoaksDistrictCouncil.py,sha256=qqrrRaSVm9CYAtm0rB2ZnyH_nLwaReuacoUxZpo597k,4260
|
244
245
|
uk_bin_collection/uk_bin_collection/councils/SheffieldCityCouncil.py,sha256=9g9AeiackoWyej9EVlKUzywzAtMuBVD0f93ZryAUha8,2016
|
@@ -328,8 +329,8 @@ uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py,sha256=I2kBYMlsD4bId
|
|
328
329
|
uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py,sha256=EQWRhZ2pEejlvm0fPyOTsOHKvUZmPnxEYO_OWRGKTjs,1158
|
329
330
|
uk_bin_collection/uk_bin_collection/create_new_council.py,sha256=m-IhmWmeWQlFsTZC4OxuFvtw5ZtB8EAJHxJTH4O59lQ,1536
|
330
331
|
uk_bin_collection/uk_bin_collection/get_bin_data.py,sha256=YvmHfZqanwrJ8ToGch34x-L-7yPe31nB_x77_Mgl_vo,4545
|
331
|
-
uk_bin_collection-0.
|
332
|
-
uk_bin_collection-0.
|
333
|
-
uk_bin_collection-0.
|
334
|
-
uk_bin_collection-0.
|
335
|
-
uk_bin_collection-0.
|
332
|
+
uk_bin_collection-0.144.1.dist-info/LICENSE,sha256=vABBUOzcrgfaTKpzeo-si9YVEun6juDkndqA8RKdKGs,1071
|
333
|
+
uk_bin_collection-0.144.1.dist-info/METADATA,sha256=Pk5QeIPJ_FQYDxoe4UgUdqG8-vDxsX4CbzkE-EGVmzs,19858
|
334
|
+
uk_bin_collection-0.144.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
335
|
+
uk_bin_collection-0.144.1.dist-info/entry_points.txt,sha256=36WCSGMWSc916S3Hi1ZkazzDKHaJ6CD-4fCEFm5MIao,90
|
336
|
+
uk_bin_collection-0.144.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
{uk_bin_collection-0.143.6.dist-info → uk_bin_collection-0.144.1.dist-info}/entry_points.txt
RENAMED
File without changes
|