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.
@@ -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": "10008755549",
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
- data = {
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
- r = s.post(API_URL, json=data, headers=headers, params=params)
66
- r.raise_for_status()
78
+ for request_id, date_key, bin_types in lookups:
79
+ params = {"id": request_id, **base_params}
67
80
 
68
- data = r.json()
69
- rows_data = data["integration"]["transformed"]["rows_data"]
70
- if not isinstance(rows_data, dict):
71
- raise ValueError("Invalid data returned from API")
72
- bin_types = {
73
- "Recycling (Blue)",
74
- "Household Waste (Grey)",
75
- "Food Waste (Brown)",
76
- "Garden Waste (Green)",
77
- }
78
- for row in rows_data.items():
79
- date = row[1]["DWDate"]
80
- for bin_type in bin_types:
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uk_bin_collection
3
- Version: 0.143.6
3
+ Version: 0.144.1
4
4
  Summary: Python Lib to collect UK Bin Data
5
5
  Author: Robert Bradley
6
6
  Author-email: robbrad182@gmail.com
@@ -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=Jsgpmyg3oz-2v87n_SbJGHcPM4-yIkQFFAsLvJDDI2g,121493
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=shJhvqDcha2ypDCSfhss59G95jNaWBuMnVIxJiZXcY8,3110
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.143.6.dist-info/LICENSE,sha256=vABBUOzcrgfaTKpzeo-si9YVEun6juDkndqA8RKdKGs,1071
332
- uk_bin_collection-0.143.6.dist-info/METADATA,sha256=_nNZJggZZm7s_yy51Yse2Dn-n4DyRJfrSHzfy_nhxK0,19858
333
- uk_bin_collection-0.143.6.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
334
- uk_bin_collection-0.143.6.dist-info/entry_points.txt,sha256=36WCSGMWSc916S3Hi1ZkazzDKHaJ6CD-4fCEFm5MIao,90
335
- uk_bin_collection-0.143.6.dist-info/RECORD,,
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,,