uk_bin_collection 0.153.0__py3-none-any.whl → 0.157.0__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.
Files changed (35) hide show
  1. uk_bin_collection/tests/input.json +34 -25
  2. uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py +0 -1
  3. uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py +45 -120
  4. uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py +4 -1
  5. uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py +15 -36
  6. uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py +55 -24
  7. uk_bin_collection/uk_bin_collection/councils/DacorumBoroughCouncil.py +22 -13
  8. uk_bin_collection/uk_bin_collection/councils/EastDunbartonshireCouncil.py +52 -0
  9. uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py +32 -34
  10. uk_bin_collection/uk_bin_collection/councils/FarehamBoroughCouncil.py +5 -2
  11. uk_bin_collection/uk_bin_collection/councils/FolkstoneandHytheDistrictCouncil.py +22 -0
  12. uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py +1 -1
  13. uk_bin_collection/uk_bin_collection/councils/HartlepoolBoroughCouncil.py +3 -1
  14. uk_bin_collection/uk_bin_collection/councils/IslingtonCouncil.py +8 -5
  15. uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py +23 -10
  16. uk_bin_collection/uk_bin_collection/councils/MidSuffolkDistrictCouncil.py +70 -92
  17. uk_bin_collection/uk_bin_collection/councils/NewForestCouncil.py +104 -47
  18. uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py +138 -21
  19. uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py +26 -128
  20. uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +245 -82
  21. uk_bin_collection/uk_bin_collection/councils/OxfordCityCouncil.py +1 -0
  22. uk_bin_collection/uk_bin_collection/councils/RenfrewshireCouncil.py +170 -13
  23. uk_bin_collection/uk_bin_collection/councils/RotherhamCouncil.py +70 -38
  24. uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py +4 -2
  25. uk_bin_collection/uk_bin_collection/councils/SandwellBoroughCouncil.py +4 -11
  26. uk_bin_collection/uk_bin_collection/councils/SloughBoroughCouncil.py +39 -21
  27. uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py +136 -21
  28. uk_bin_collection/uk_bin_collection/councils/SouthGloucestershireCouncil.py +18 -22
  29. uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py +138 -21
  30. uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py +16 -13
  31. {uk_bin_collection-0.153.0.dist-info → uk_bin_collection-0.157.0.dist-info}/METADATA +1 -1
  32. {uk_bin_collection-0.153.0.dist-info → uk_bin_collection-0.157.0.dist-info}/RECORD +35 -34
  33. {uk_bin_collection-0.153.0.dist-info → uk_bin_collection-0.157.0.dist-info}/LICENSE +0 -0
  34. {uk_bin_collection-0.153.0.dist-info → uk_bin_collection-0.157.0.dist-info}/WHEEL +0 -0
  35. {uk_bin_collection-0.153.0.dist-info → uk_bin_collection-0.157.0.dist-info}/entry_points.txt +0 -0
@@ -1,57 +1,89 @@
1
- from bs4 import BeautifulSoup
2
1
  from uk_bin_collection.uk_bin_collection.common import *
3
2
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
3
+ import requests
4
+ from datetime import datetime
4
5
 
5
6
 
6
- # import the wonderful Beautiful Soup and the URL grabber
7
7
  class CouncilClass(AbstractGetBinDataClass):
8
8
  """
9
- Concrete classes have to implement all abstract operations of the
10
- base class. They can also override some operations with a default
11
- implementation.
9
+ Rotherham collections via the public JSON API.
10
+ Returns the same shape as before:
11
+ {"bins": [{"type": "Black Bin", "collectionDate": "Tuesday, 29 September 2025"}, ...]}
12
+ Accepts kwargs['premisesid'] (recommended) or a numeric kwargs['uprn'].
12
13
  """
13
14
 
14
15
  def parse_data(self, page: str, **kwargs) -> dict:
15
- user_uprn = kwargs.get("uprn")
16
+ # prefer explicit premisesid, fallback to uprn (if numeric)
17
+ premises = kwargs.get("premisesid")
18
+ uprn = kwargs.get("uprn")
16
19
 
17
- check_uprn(user_uprn)
20
+ if uprn:
21
+ # preserve original behaviour where check_uprn exists for validation,
22
+ # but don't fail if uprn is intended as a simple premises id number.
23
+ try:
24
+ check_uprn(uprn)
25
+ except Exception:
26
+ # silently continue — user may have passed a numeric premises id as uprn
27
+ pass
28
+
29
+ if not premises and str(uprn).strip().isdigit():
30
+ premises = str(uprn).strip()
31
+
32
+ if not premises:
33
+ raise ValueError("No premises ID supplied. Pass 'premisesid' in kwargs or a numeric 'uprn'.")
34
+
35
+ api_url = "https://bins.azurewebsites.net/api/getcollections"
36
+ params = {
37
+ "premisesid": str(premises),
38
+ "localauthority": kwargs.get("localauthority", "Rotherham"),
39
+ }
18
40
  headers = {
19
- "Content-Type": "application/x-www-form-urlencoded",
20
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
41
+ "User-Agent": "UKBinCollectionData/1.0 (+https://github.com/robbrad/UKBinCollectionData)"
21
42
  }
22
- response = requests.post(
23
- "https://www.rotherham.gov.uk/bin-collections?address={}&submit=Submit".format(
24
- user_uprn
25
- ),
26
- headers=headers
27
- )
28
- # Make a BS4 object
29
- soup = BeautifulSoup(response.text, features="html.parser")
30
- soup.prettify()
31
43
 
32
- data = {"bins": []}
44
+ try:
45
+ resp = requests.get(api_url, params=params, headers=headers, timeout=10)
46
+ except Exception as exc:
47
+ print(f"Error contacting Rotherham API: {exc}")
48
+ return {"bins": []}
49
+
50
+ if resp.status_code != 200:
51
+ print(f"Rotherham API request failed ({resp.status_code}). URL: {resp.url}")
52
+ return {"bins": []}
33
53
 
34
- table = soup.select("table")[0]
54
+ try:
55
+ collections = resp.json()
56
+ except ValueError:
57
+ print("Rotherham API returned non-JSON response.")
58
+ return {"bins": []}
59
+
60
+ data = {"bins": []}
61
+ seen = set() # dedupe identical (type, date) pairs
62
+ for item in collections:
63
+ bin_type = item.get("BinType") or item.get("bintype") or "Unknown"
64
+ date_str = item.get("CollectionDate") or item.get("collectionDate")
65
+ if not date_str:
66
+ continue
35
67
 
36
- if table:
37
- rows = table.select("tr")
68
+ # API gives ISO date like '2025-09-29' (or possibly '2025-09-29T00:00:00').
69
+ try:
70
+ iso_date = date_str.split("T")[0]
71
+ parsed = datetime.strptime(iso_date, "%Y-%m-%d")
72
+ formatted = parsed.strftime(date_format)
73
+ except Exception:
74
+ # skip malformed dates
75
+ continue
38
76
 
39
- for index, row in enumerate(rows):
40
- bin_info_cell = row.select("td")
41
- if bin_info_cell:
42
- bin_type = bin_info_cell[0].get_text(separator=" ", strip=True)
43
- bin_collection = bin_info_cell[1]
77
+ key = (bin_type.strip().lower(), formatted)
78
+ if key in seen:
79
+ continue
80
+ seen.add(key)
44
81
 
45
- if bin_collection:
46
- dict_data = {
47
- "type": bin_type.title(),
48
- "collectionDate": datetime.strptime(
49
- bin_collection.get_text(strip=True), "%A, %d %B %Y"
50
- ).strftime(date_format),
51
- }
82
+ dict_data = {"type": bin_type.title(), "collectionDate": formatted}
83
+ data["bins"].append(dict_data)
52
84
 
53
- data["bins"].append(dict_data)
54
- else:
55
- print("Something went wrong. Please open a GitHub issue.")
85
+ if not data["bins"]:
86
+ # helpful debugging note
87
+ print(f"Rotherham API returned no collection entries for premisesid={premises}")
56
88
 
57
- return data
89
+ return data
@@ -1,7 +1,8 @@
1
1
  from bs4 import BeautifulSoup
2
+ from lxml import etree
3
+
2
4
  from uk_bin_collection.uk_bin_collection.common import *
3
5
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
4
- from lxml import etree
5
6
 
6
7
 
7
8
  # import the wonderful Beautiful Soup and the URL grabber
@@ -20,7 +21,8 @@ class CouncilClass(AbstractGetBinDataClass):
20
21
  collections = []
21
22
 
22
23
  # Convert the XML to JSON and load the next collection data
23
- result = soup.find("p").contents[0].text.replace("\\", "")[1:-1]
24
+ result = soup.find("p").contents[0]
25
+
24
26
  json_data = json.loads(result)["NextCollection"]
25
27
 
26
28
  # Get general waste data
@@ -28,17 +28,10 @@ class CouncilClass(AbstractGetBinDataClass):
28
28
  "Referer": "https://my.sandwell.gov.uk/fillform/?iframe_id=fillform-frame-1&db_id=",
29
29
  }
30
30
  LOOKUPS = [
31
- (
32
- "58a1a71694992",
33
- "DWDate",
34
- [
35
- "Recycling (Blue)",
36
- "Household Waste (Grey)",
37
- "Food Waste (Brown)",
38
- "Batteries",
39
- ],
40
- ),
41
- ("56b1cdaf6bb43", "GWDate", ["Garden Waste (Green)"]),
31
+ ("686295a88a750", "GWDate", ["Garden Waste (Green)"]),
32
+ ("686294de50729", "DWDate", ["Household Waste (Grey)"]),
33
+ ("6863a78a1dd8e", "FWDate", ["Food Waste (Brown)"]),
34
+ ("68629dd642423", "MDRDate", ["Recycling (Blue)"]),
42
35
  ]
43
36
 
44
37
  def parse_data(self, page: str, **kwargs) -> dict:
@@ -1,15 +1,18 @@
1
- import time
2
1
  import re
3
- import requests
2
+ import time
4
3
  from datetime import datetime
4
+
5
+ import requests
5
6
  from bs4 import BeautifulSoup
6
7
  from selenium.webdriver.common.by import By
7
8
  from selenium.webdriver.common.keys import Keys
8
9
  from selenium.webdriver.support import expected_conditions as EC
9
10
  from selenium.webdriver.support.ui import WebDriverWait
11
+
10
12
  from uk_bin_collection.uk_bin_collection.common import *
11
13
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
12
14
 
15
+
13
16
  def get_street_from_postcode(postcode: str, api_key: str) -> str:
14
17
  url = "https://maps.googleapis.com/maps/api/geocode/json"
15
18
  params = {"address": postcode, "key": api_key}
@@ -25,6 +28,7 @@ def get_street_from_postcode(postcode: str, api_key: str) -> str:
25
28
 
26
29
  raise ValueError("No street (route) found in the response.")
27
30
 
31
+
28
32
  class CouncilClass(AbstractGetBinDataClass):
29
33
  def parse_data(self, page: str, **kwargs) -> dict:
30
34
  driver = None
@@ -37,10 +41,10 @@ class CouncilClass(AbstractGetBinDataClass):
37
41
 
38
42
  headless = kwargs.get("headless")
39
43
  web_driver = kwargs.get("web_driver")
40
- driver = create_webdriver(web_driver, headless, None, __name__)
44
+ UserAgent = "Mozilla/5.0"
45
+ driver = create_webdriver(web_driver, headless, UserAgent, __name__)
41
46
  page = "https://www.slough.gov.uk/bin-collections"
42
47
  driver.get(page)
43
-
44
48
  # Accept cookies
45
49
  WebDriverWait(driver, 10).until(
46
50
  EC.element_to_be_clickable((By.ID, "ccc-recommended-settings"))
@@ -50,14 +54,20 @@ class CouncilClass(AbstractGetBinDataClass):
50
54
  address_input = WebDriverWait(driver, 10).until(
51
55
  EC.presence_of_element_located((By.ID, "keyword_directory25"))
52
56
  )
53
- user_address = get_street_from_postcode(user_postcode, "AIzaSyBDLULT7EIlNtHerswPtfmL15Tt3Oc0bV8")
57
+ user_address = get_street_from_postcode(
58
+ user_postcode, "AIzaSyBDLULT7EIlNtHerswPtfmL15Tt3Oc0bV8"
59
+ )
54
60
  address_input.send_keys(user_address + Keys.ENTER)
55
61
 
56
62
  # Wait for address results to load
57
63
  WebDriverWait(driver, 10).until(
58
- EC.presence_of_all_elements_located((By.CSS_SELECTOR, "span.list__link-text"))
64
+ EC.presence_of_all_elements_located(
65
+ (By.CSS_SELECTOR, "span.list__link-text")
66
+ )
67
+ )
68
+ span_elements = driver.find_elements(
69
+ By.CSS_SELECTOR, "span.list__link-text"
59
70
  )
60
- span_elements = driver.find_elements(By.CSS_SELECTOR, "span.list__link-text")
61
71
 
62
72
  for span in span_elements:
63
73
  if user_address.lower() in span.text.lower():
@@ -68,7 +78,9 @@ class CouncilClass(AbstractGetBinDataClass):
68
78
 
69
79
  # Wait for address detail page
70
80
  WebDriverWait(driver, 10).until(
71
- EC.presence_of_element_located((By.CSS_SELECTOR, "section.site-content"))
81
+ EC.presence_of_element_located(
82
+ (By.CSS_SELECTOR, "section.site-content")
83
+ )
72
84
  )
73
85
  soup = BeautifulSoup(driver.page_source, "html.parser")
74
86
 
@@ -86,28 +98,33 @@ class CouncilClass(AbstractGetBinDataClass):
86
98
  bin_url = "https://www.slough.gov.uk" + bin_url
87
99
 
88
100
  # Visit the child page
89
- print(f"Navigating to {bin_url}")
101
+ # print(f"Navigating to {bin_url}")
90
102
  driver.get(bin_url)
91
103
  WebDriverWait(driver, 10).until(
92
- EC.presence_of_element_located((By.CSS_SELECTOR, "div.page-content"))
104
+ EC.presence_of_element_located(
105
+ (By.CSS_SELECTOR, "div.page-content")
106
+ )
93
107
  )
94
108
  child_soup = BeautifulSoup(driver.page_source, "html.parser")
95
109
 
96
110
  editor_div = child_soup.find("div", class_="editor")
97
111
  if not editor_div:
98
- print("No editor div found on bin detail page.")
112
+ # print("No editor div found on bin detail page.")
99
113
  continue
100
114
 
101
115
  ul = editor_div.find("ul")
102
116
  if not ul:
103
- print("No <ul> with dates found in editor div.")
117
+ # print("No <ul> with dates found in editor div.")
104
118
  continue
105
119
 
106
120
  for li in ul.find_all("li"):
107
121
  raw_text = li.get_text(strip=True).replace(".", "")
108
122
 
109
- if "no collection" in raw_text.lower() or "no collections" in raw_text.lower():
110
- print(f"Ignoring non-collection note: {raw_text}")
123
+ if (
124
+ "no collection" in raw_text.lower()
125
+ or "no collections" in raw_text.lower()
126
+ ):
127
+ # print(f"Ignoring non-collection note: {raw_text}")
111
128
  continue
112
129
 
113
130
  raw_date = raw_text
@@ -117,19 +134,20 @@ class CouncilClass(AbstractGetBinDataClass):
117
134
  except ValueError:
118
135
  raw_date_cleaned = raw_date.split("(")[0].strip()
119
136
  try:
120
- parsed_date = datetime.strptime(raw_date_cleaned, "%d %B %Y")
137
+ parsed_date = datetime.strptime(
138
+ raw_date_cleaned, "%d %B %Y"
139
+ )
121
140
  except Exception:
122
141
  print(f"Could not parse date: {raw_text}")
123
142
  continue
124
143
 
125
144
  formatted_date = parsed_date.strftime("%d/%m/%Y")
126
145
  contains_date(formatted_date)
127
- bin_data["bins"].append({
128
- "type": bin_type,
129
- "collectionDate": formatted_date
130
- })
146
+ bin_data["bins"].append(
147
+ {"type": bin_type, "collectionDate": formatted_date}
148
+ )
131
149
 
132
- print(f"Type: {bin_type}, Date: {formatted_date}")
150
+ # print(f"Type: {bin_type}, Date: {formatted_date}")
133
151
 
134
152
  except Exception as e:
135
153
  print(f"An error occurred: {e}")
@@ -137,4 +155,4 @@ class CouncilClass(AbstractGetBinDataClass):
137
155
  finally:
138
156
  if driver:
139
157
  driver.quit()
140
- return bin_data
158
+ return bin_data
@@ -1,4 +1,9 @@
1
+ import datetime
2
+
1
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.wait import WebDriverWait
2
7
 
3
8
  from uk_bin_collection.uk_bin_collection.common import *
4
9
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
@@ -13,6 +18,7 @@ class CouncilClass(AbstractGetBinDataClass):
13
18
  """
14
19
 
15
20
  def parse_data(self, page: str, **kwargs) -> dict:
21
+ <<<<<<< HEAD
16
22
  user_postcode = kwargs.get("postcode")
17
23
  check_postcode(user_postcode)
18
24
  user_uprn = kwargs.get("uprn")
@@ -43,10 +49,16 @@ class CouncilClass(AbstractGetBinDataClass):
43
49
  i["data-for"]: i.get("value", "")
44
50
  for i in soup.select("input[data-for]")
45
51
  }
46
- payload_salt = soup.select_one('input[id="pSalt"]').get("value")
47
- payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get(
48
- "value"
49
- )
52
+
53
+ # Check if required form elements exist
54
+ salt_element = soup.select_one('input[id="pSalt"]')
55
+ protected_element = soup.select_one('input[id="pPageItemsProtected"]')
56
+
57
+ if not salt_element or not protected_element:
58
+ raise Exception("Required form elements not found. The council website may have changed or be unavailable.")
59
+
60
+ payload_salt = salt_element.get("value")
61
+ payload_protected = protected_element.get("value")
50
62
 
51
63
  # Add the PostCode and 'SEARCH' to the payload
52
64
  payload["p_request"] = "SEARCH"
@@ -123,10 +135,16 @@ class CouncilClass(AbstractGetBinDataClass):
123
135
  i["data-for"]: i.get("value", "")
124
136
  for i in soup.select("input[data-for]")
125
137
  }
126
- payload_salt = soup.select_one('input[id="pSalt"]').get("value")
127
- payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get(
128
- "value"
129
- )
138
+
139
+ # Check if required form elements exist
140
+ salt_element = soup.select_one('input[id="pSalt"]')
141
+ protected_element = soup.select_one('input[id="pPageItemsProtected"]')
142
+
143
+ if not salt_element or not protected_element:
144
+ raise Exception("Required form elements not found. The council website may have changed or be unavailable.")
145
+
146
+ payload_salt = salt_element.get("value")
147
+ payload_protected = protected_element.get("value")
130
148
 
131
149
  # Add the UPRN and 'SUBMIT' to the payload
132
150
  payload["p_request"] = "SUBMIT"
@@ -187,18 +205,115 @@ class CouncilClass(AbstractGetBinDataClass):
187
205
 
188
206
  # Create a BeautifulSoup object from the page's HTML
189
207
  soup = BeautifulSoup(resource.text, "html.parser")
208
+ =======
209
+ driver = None
210
+ try:
211
+ >>>>>>> master
190
212
  data = {"bins": []}
213
+ url = kwargs.get("url")
214
+ user_paon = kwargs.get("paon")
215
+ user_postcode = kwargs.get("postcode")
216
+ web_driver = kwargs.get("web_driver")
217
+ headless = kwargs.get("headless")
218
+ check_paon(user_paon)
219
+ check_postcode(user_postcode)
220
+
221
+ # Use a realistic user agent to help bypass Cloudflare
222
+ user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
223
+ driver = create_webdriver(web_driver, headless, user_agent, __name__)
224
+ driver.get("https://www.somerset.gov.uk/collection-days")
225
+
226
+ # Wait for the postcode field to appear then populate it
227
+ inputElement_postcode = WebDriverWait(driver, 30).until(
228
+ EC.presence_of_element_located((By.ID, "postcodeSearch"))
229
+ )
230
+ inputElement_postcode.send_keys(user_postcode)
231
+
232
+ # Click search button
233
+ findAddress = WebDriverWait(driver, 10).until(
234
+ EC.presence_of_element_located((By.CLASS_NAME, "govuk-button"))
235
+ )
236
+ findAddress.click()
237
+
238
+ # Wait for the 'Select address' dropdown to appear and select option matching the house name/number
239
+ WebDriverWait(driver, 10).until(
240
+ EC.element_to_be_clickable(
241
+ (
242
+ By.XPATH,
243
+ "//select[@id='addressSelect']//option[contains(., '"
244
+ + user_paon
245
+ + "')]",
246
+ )
247
+ )
248
+ ).click()
249
+
250
+ # Wait for the collections table to appear
251
+ WebDriverWait(driver, 20).until(
252
+ EC.presence_of_element_located(
253
+ (
254
+ By.XPATH,
255
+ "//h2[contains(@class,'mt-4') and contains(@class,'govuk-heading-s') and normalize-space(.)='Your next collections']",
256
+ )
257
+ )
258
+ )
259
+
260
+ soup = BeautifulSoup(driver.page_source, features="html.parser")
261
+
262
+ collections = soup.find_all("div", {"class": "p-2"})
263
+
264
+ for collection in collections:
265
+ bin_type = collection.find("h3").get_text()
266
+
267
+ next_collection = soup.find("div", {"class": "fw-bold"}).get_text()
268
+
269
+ following_collection = soup.find(
270
+ lambda t: (
271
+ t.name == "div"
272
+ and t.get_text(strip=True).lower().startswith("followed by")
273
+ )
274
+ ).get_text()
275
+
276
+ next_collection_date = datetime.strptime(next_collection, "%A %d %B")
277
+
278
+ following_collection_date = datetime.strptime(
279
+ following_collection, "followed by %A %d %B"
280
+ )
281
+
282
+ current_date = datetime.now()
283
+ next_collection_date = next_collection_date.replace(
284
+ year=current_date.year
285
+ )
286
+ following_collection_date = following_collection_date.replace(
287
+ year=current_date.year
288
+ )
289
+
290
+ next_collection_date = get_next_occurrence_from_day_month(
291
+ next_collection_date
292
+ )
293
+
294
+ following_collection_date = get_next_occurrence_from_day_month(
295
+ following_collection_date
296
+ )
297
+
298
+ dict_data = {
299
+ "type": bin_type,
300
+ "collectionDate": next_collection_date.strftime(date_format),
301
+ }
302
+ data["bins"].append(dict_data)
303
+
304
+ dict_data = {
305
+ "type": bin_type,
306
+ "collectionDate": following_collection_date.strftime(date_format),
307
+ }
308
+ data["bins"].append(dict_data)
191
309
 
192
- # Loop through the items on the page and build a JSON object for ingestion
193
- for item in soup.select(".t-MediaList-item"):
194
- for value in item.select(".t-MediaList-body"):
195
- dict_data = {
196
- "type": value.select("span")[1].get_text(strip=True).title(),
197
- "collectionDate": datetime.strptime(
198
- value.select(".t-MediaList-desc")[0].get_text(strip=True),
199
- "%A, %d %B, %Y",
200
- ).strftime(date_format),
201
- }
202
- data["bins"].append(dict_data)
203
-
204
- return data
310
+ except Exception as e:
311
+ # Here you can log the exception if needed
312
+ print(f"An error occurred: {e}")
313
+ # Optionally, re-raise the exception if you want it to propagate
314
+ raise
315
+ finally:
316
+ # This block ensures that the driver is closed regardless of an exception
317
+ if driver:
318
+ driver.quit()
319
+ return data
@@ -6,17 +6,16 @@ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataC
6
6
 
7
7
  def format_bin_data(key: str, date: datetime):
8
8
  formatted_date = date.strftime(date_format)
9
-
10
- if re.match(r"^R\d+$", key) is not None:
11
- # RX matches both general waste and recycling
12
- return [
13
- ("General Waste (Black Bin)", formatted_date),
14
- ("Recycling & Food Waste", formatted_date),
15
- ]
16
- elif re.match(r"^G\d+$", key) is not None:
9
+ servicename = key.get("hso_servicename")
10
+ print(servicename)
11
+ if re.match(r"^Recycl", servicename) is not None:
12
+ return [ ("Recycling", formatted_date) ]
13
+ elif re.match(r"^Refuse", servicename) is not None:
14
+ return [("General Waste (Black Bin)", formatted_date)]
15
+ elif re.match(r"^Garden", servicename) is not None:
17
16
  return [("Garden Waste (Green Bin)", formatted_date)]
18
- elif re.match(r"^C\d+$", key) is not None:
19
- return [("Recycling & Food Waste", formatted_date)]
17
+ elif re.match(r"^Food", servicename) is not None:
18
+ return [("Food Waste", formatted_date)]
20
19
  else:
21
20
  return None
22
21
 
@@ -27,37 +26,34 @@ class CouncilClass(AbstractGetBinDataClass):
27
26
  check_uprn(uprn)
28
27
 
29
28
  api_url = (
30
- f"https://webapps.southglos.gov.uk/Webservices/SGC.RefuseCollectionService/RefuseCollectionService"
31
- f".svc/getCollections/{uprn}"
29
+ f"https://api.southglos.gov.uk/wastecomp/GetCollectionDetails"
30
+ f"?uprn={uprn}"
32
31
  )
33
32
 
34
33
  headers = {"content-type": "application/json"}
35
34
 
36
35
  response = requests.get(api_url, headers=headers)
37
36
 
38
- json_response = json.loads(response.content)
37
+ json_response = response.json()
39
38
  if not json_response:
40
39
  raise ValueError("No collection data found for provided UPRN.")
41
40
 
42
- collection_data = json_response[0]
41
+ collection_data = json_response.get('value')
43
42
 
44
43
  today = datetime.today()
45
44
  eight_weeks = datetime.today() + timedelta(days=8 * 7)
46
45
  data = {"bins": []}
47
46
  collection_tuple = []
48
-
49
- for key in collection_data:
50
- if key == "CalendarName":
51
- continue
52
-
53
- item = collection_data[key]
47
+ for collection in collection_data:
48
+ print(collection)
49
+ item = collection.get('hso_nextcollection')
54
50
 
55
51
  if item == "":
56
52
  continue
57
53
 
58
- collection_date = datetime.strptime(item, date_format)
54
+ collection_date = datetime.fromisoformat(item)
59
55
  if today.date() <= collection_date.date() <= eight_weeks.date():
60
- bin_data = format_bin_data(key, collection_date)
56
+ bin_data = format_bin_data(collection, collection_date)
61
57
  if bin_data is not None:
62
58
  for bin_date in bin_data:
63
59
  collection_tuple.append(bin_date)