uk_bin_collection 0.101.0__py3-none-any.whl → 0.103.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 (21) hide show
  1. uk_bin_collection/tests/input.json +1 -1
  2. uk_bin_collection/uk_bin_collection/councils/BasildonCouncil.py +42 -39
  3. uk_bin_collection/uk_bin_collection/councils/BelfastCityCouncil.py +13 -8
  4. uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py +24 -21
  5. uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py +1 -1
  6. uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py +25 -10
  7. uk_bin_collection/uk_bin_collection/councils/CornwallCouncil.py +21 -20
  8. uk_bin_collection/uk_bin_collection/councils/EnfieldCouncil.py +16 -18
  9. uk_bin_collection/uk_bin_collection/councils/GedlingBoroughCouncil.py +10 -4
  10. uk_bin_collection/uk_bin_collection/councils/IslingtonCouncil.py +6 -4
  11. uk_bin_collection/uk_bin_collection/councils/MoleValleyDistrictCouncil.py +37 -20
  12. uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py +11 -9
  13. uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py +1 -2
  14. uk_bin_collection/uk_bin_collection/councils/RotherhamCouncil.py +8 -6
  15. uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py +21 -11
  16. uk_bin_collection/uk_bin_collection/councils/WokinghamBoroughCouncil.py +1 -1
  17. {uk_bin_collection-0.101.0.dist-info → uk_bin_collection-0.103.0.dist-info}/METADATA +19 -1
  18. {uk_bin_collection-0.101.0.dist-info → uk_bin_collection-0.103.0.dist-info}/RECORD +21 -21
  19. {uk_bin_collection-0.101.0.dist-info → uk_bin_collection-0.103.0.dist-info}/LICENSE +0 -0
  20. {uk_bin_collection-0.101.0.dist-info → uk_bin_collection-0.103.0.dist-info}/WHEEL +0 -0
  21. {uk_bin_collection-0.101.0.dist-info → uk_bin_collection-0.103.0.dist-info}/entry_points.txt +0 -0
@@ -367,7 +367,7 @@
367
367
  },
368
368
  "EastDevonDC": {
369
369
  "url": "https://eastdevon.gov.uk/recycling-and-waste/recycling-waste-information/when-is-my-bin-collected/future-collections-calendar/?UPRN=010090909915",
370
- "wiki_command_url_override": "https://eastdevon.gov.uk/recycling-waste/recycling-and-waste-information/when-is-my-bin-collected/future-collections-calendar/?UPRN=XXXXXXXX",
370
+ "wiki_command_url_override": "https://eastdevon.gov.uk/recycling-and-waste/recycling-waste-information/when-is-my-bin-collected/future-collections-calendar/?UPRN=XXXXXXXX",
371
371
  "wiki_name": "East Devon District Council",
372
372
  "wiki_note": "Replace XXXXXXXX with UPRN."
373
373
  },
@@ -1,13 +1,13 @@
1
- from uk_bin_collection.uk_bin_collection.common import *
1
+ import requests
2
+ import json
3
+ from datetime import datetime
4
+ from uk_bin_collection.uk_bin_collection.common import check_uprn, date_format as DATE_FORMAT
2
5
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
3
6
 
4
7
 
5
- # import the wonderful Beautiful Soup and the URL grabber
6
8
  class CouncilClass(AbstractGetBinDataClass):
7
9
  """
8
- Concrete classes have to implement all abstract operations of the
9
- base class. They can also override some operations with a default
10
- implementation.
10
+ Concrete class that implements the abstract bin data fetching and parsing logic.
11
11
  """
12
12
 
13
13
  def parse_data(self, page: str, **kwargs) -> dict:
@@ -18,64 +18,67 @@ class CouncilClass(AbstractGetBinDataClass):
18
18
  check_uprn(uprn)
19
19
 
20
20
  payload = {
21
- # Add your payload details here (replace this with the actual payload structure if required)
22
21
  "uprn": uprn
23
22
  }
24
23
 
25
- # Headers for the request
26
- headers = {
27
- "Content-Type": "application/json"
28
- }
24
+ headers = {"Content-Type": "application/json"}
29
25
 
30
26
  response = requests.post(url_base, data=json.dumps(payload), headers=headers)
31
27
 
32
- # Ensure the request was successful
33
28
  if response.status_code == 200:
34
29
  data = response.json()
35
30
 
36
31
  # Initialize an empty list to store the bin collection details
37
-
38
32
  bins = []
39
33
 
40
34
  # Function to add collection details to bins list
41
35
  def add_collection(service_name, collection_data):
42
- bins.append({
43
- "type": service_name,
44
- "collectionDate": collection_data.get("current_collection_date")
45
- })
36
+ bins.append(
37
+ {
38
+ "type": service_name,
39
+ "collectionDate": collection_data.get("current_collection_date"),
40
+ }
41
+ )
46
42
 
47
- # Extract refuse information
48
- available_services = data["refuse"]["available_services"]
43
+ available_services = data.get("refuse", {}).get("available_services", {})
44
+
45
+ date_format = "%d-%m-%Y" # Define the desired date format
49
46
 
50
47
  for service_name, service_data in available_services.items():
51
- # Append the service name and current collection date to the "bins" list
48
+ # Handle the different cases of service data
52
49
  match service_data["container"]:
53
50
  case "Green Wheelie Bin":
54
- subscription_status = service_data["subscription"]["active"] if service_data["subscription"] else False
55
- type_descr = f"Green Wheelie Bin ({"Active" if subscription_status else "Expired"})"
51
+ subscription_status = (
52
+ service_data["subscription"]["active"]
53
+ if service_data.get("subscription")
54
+ else False
55
+ )
56
+ type_descr = f"Green Wheelie Bin ({'Active' if subscription_status else 'Expired'})"
56
57
  case "N/A":
57
- type_descr = service_data["name"]
58
+ type_descr = service_data.get("name", "Unknown Service")
58
59
  case _:
59
- type_descr = service_data["container"]
60
-
60
+ type_descr = service_data.get("container", "Unknown Container")
61
61
 
62
62
  date_str = service_data.get("current_collection_date")
63
- # Parse the date string into a datetime object
64
- date_obj = datetime.strptime(date_str, "%Y-%m-%d")
65
-
66
- # Convert the datetime object to the desired format
67
- formatted_date = date_obj.strftime(date_format)
68
-
69
- bins.append({
70
- "type": type_descr, # Use service name from the data
71
- "collectionDate": formatted_date
72
- })
63
+ if date_str: # Ensure the date string exists
64
+ try:
65
+ # Parse and format the date string
66
+ date_obj = datetime.strptime(date_str, "%Y-%m-%d")
67
+ formatted_date = date_obj.strftime(DATE_FORMAT)
68
+ except ValueError:
69
+ formatted_date = "Invalid Date"
70
+ else:
71
+ formatted_date = "No Collection Date"
72
+
73
+ bins.append(
74
+ {
75
+ "type": type_descr, # Use service name from the data
76
+ "collectionDate": formatted_date,
77
+ }
78
+ )
73
79
 
74
80
  else:
75
81
  print(f"Failed to fetch data. Status code: {response.status_code}")
82
+ return {}
76
83
 
77
- data = {
78
- "bins": bins
79
- }
80
-
81
- return data
84
+ return {"bins": bins}
@@ -9,7 +9,6 @@ from uk_bin_collection.uk_bin_collection.common import *
9
9
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
10
10
 
11
11
 
12
-
13
12
  # import the wonderful Beautiful Soup and the URL grabber
14
13
  class CouncilClass(AbstractGetBinDataClass):
15
14
  """
@@ -34,7 +33,7 @@ class CouncilClass(AbstractGetBinDataClass):
34
33
 
35
34
  session = requests.Session()
36
35
  session.headers.update(headers)
37
-
36
+
38
37
  user_uprn = kwargs.get("uprn")
39
38
  user_postcode = kwargs.get("postcode")
40
39
  URL = "https://online.belfastcity.gov.uk/find-bin-collection-day/Default.aspx"
@@ -47,14 +46,16 @@ class CouncilClass(AbstractGetBinDataClass):
47
46
  "__EVENTTARGET": "",
48
47
  "__EVENTARGUMENT": "",
49
48
  "__VIEWSTATE": self.get_session_variable(soup, "__VIEWSTATE"),
50
- "__VIEWSTATEGENERATOR": self.get_session_variable(soup, "__VIEWSTATEGENERATOR"),
49
+ "__VIEWSTATEGENERATOR": self.get_session_variable(
50
+ soup, "__VIEWSTATEGENERATOR"
51
+ ),
51
52
  "__SCROLLPOSITIONX": "0",
52
53
  "__SCROLLPOSITIONY": "0",
53
54
  "__EVENTVALIDATION": self.get_session_variable(soup, "__EVENTVALIDATION"),
54
55
  "ctl00$MainContent$searchBy_radio": "P",
55
56
  "ctl00$MainContent$Street_textbox": "",
56
57
  "ctl00$MainContent$Postcode_textbox": user_postcode,
57
- "ctl00$MainContent$AddressLookup_button": "Find address"
58
+ "ctl00$MainContent$AddressLookup_button": "Find address",
58
59
  }
59
60
 
60
61
  # Build intermediate ASP.NET variables for uprn Select address
@@ -65,7 +66,9 @@ class CouncilClass(AbstractGetBinDataClass):
65
66
  "__EVENTTARGET": "",
66
67
  "__EVENTARGUMENT": "",
67
68
  "__VIEWSTATE": self.get_session_variable(soup, "__VIEWSTATE"),
68
- "__VIEWSTATEGENERATOR": self.get_session_variable(soup, "__VIEWSTATEGENERATOR"),
69
+ "__VIEWSTATEGENERATOR": self.get_session_variable(
70
+ soup, "__VIEWSTATEGENERATOR"
71
+ ),
69
72
  "__SCROLLPOSITIONX": "0",
70
73
  "__SCROLLPOSITIONY": "0",
71
74
  "__EVENTVALIDATION": self.get_session_variable(soup, "__EVENTVALIDATION"),
@@ -73,14 +76,14 @@ class CouncilClass(AbstractGetBinDataClass):
73
76
  "ctl00$MainContent$Street_textbox": "",
74
77
  "ctl00$MainContent$Postcode_textbox": user_postcode,
75
78
  "ctl00$MainContent$lstAddresses": user_uprn,
76
- "ctl00$MainContent$SelectAddress_button": "Select address"
79
+ "ctl00$MainContent$SelectAddress_button": "Select address",
77
80
  }
78
81
 
79
82
  # Actual http call to get Bins Data
80
83
  response = session.post(URL, data=form_data)
81
84
  response.raise_for_status()
82
85
  soup = BeautifulSoup(response.text, "html.parser")
83
-
86
+
84
87
  # Find Bins table and data
85
88
  table = soup.find("div", {"id": "binsGrid"})
86
89
  if table:
@@ -91,7 +94,9 @@ class CouncilClass(AbstractGetBinDataClass):
91
94
  collection_type = columns[0].get_text(strip=True)
92
95
  collection_date_raw = columns[3].get_text(strip=True)
93
96
  # if the month number is a single digit there are 2 spaces, stripping all spaces to make it consistent
94
- collection_date = datetime.strptime(collection_date_raw.replace(" ", ""),'%a%b%d%Y')
97
+ collection_date = datetime.strptime(
98
+ collection_date_raw.replace(" ", ""), "%a%b%d%Y"
99
+ )
95
100
  bin_entry = {
96
101
  "type": collection_type,
97
102
  "collectionDate": collection_date.strftime(date_format),
@@ -45,17 +45,13 @@ class CouncilClass(AbstractGetBinDataClass):
45
45
  )
46
46
  inputElement_postcodesearch.send_keys(user_postcode)
47
47
 
48
-
49
-
50
48
  find_address_btn = wait.until(
51
49
  EC.element_to_be_clickable((By.XPATH, '//*[@id="sub"]'))
52
50
  )
53
51
  find_address_btn.click()
54
52
 
55
53
  dropdown_options = wait.until(
56
- EC.presence_of_element_located(
57
- (By.XPATH, '//*[@id="address"]')
58
- )
54
+ EC.presence_of_element_located((By.XPATH, '//*[@id="address"]'))
59
55
  )
60
56
  time.sleep(2)
61
57
  dropdown_options.click()
@@ -71,11 +67,8 @@ class CouncilClass(AbstractGetBinDataClass):
71
67
  # Click the element
72
68
  address.click()
73
69
 
74
-
75
70
  submit_address = wait.until(
76
- EC.presence_of_element_located(
77
- (By.XPATH, '//*[@id="go"]')
78
- )
71
+ EC.presence_of_element_located((By.XPATH, '//*[@id="go"]'))
79
72
  )
80
73
  time.sleep(2)
81
74
  submit_address.click()
@@ -83,13 +76,11 @@ class CouncilClass(AbstractGetBinDataClass):
83
76
  results_found = wait.until(
84
77
  EC.element_to_be_clickable(
85
78
  (By.XPATH, '//h1[contains(text(), "Your bin days")]')
86
- )
87
79
  )
80
+ )
88
81
 
89
82
  final_page = wait.until(
90
- EC.presence_of_element_located(
91
- (By.CLASS_NAME, "waste__collections")
92
- )
83
+ EC.presence_of_element_located((By.CLASS_NAME, "waste__collections"))
93
84
  )
94
85
 
95
86
  soup = BeautifulSoup(driver.page_source, features="html.parser")
@@ -103,29 +94,41 @@ class CouncilClass(AbstractGetBinDataClass):
103
94
  # Loop through each bin field
104
95
  for bin_section in bin_sections:
105
96
  # Extract the bin type (e.g., "Brown Caddy", "Green Wheelie Bin", etc.)
106
- bin_type = bin_section.get_text(strip=True).split("\n")[0] # The first part is the bin type
97
+ bin_type = bin_section.get_text(strip=True).split("\n")[
98
+ 0
99
+ ] # The first part is the bin type
107
100
 
108
101
  # Find the next sibling <dl> tag that contains the next collection information
109
102
  summary_list = bin_section.find_next("dl", class_="govuk-summary-list")
110
103
 
111
104
  if summary_list:
112
105
  # Now, instead of finding by class, we'll search by text within the dt element
113
- next_collection_dt = summary_list.find("dt", string=lambda text: "Next collection" in text)
106
+ next_collection_dt = summary_list.find(
107
+ "dt", string=lambda text: "Next collection" in text
108
+ )
114
109
 
115
110
  if next_collection_dt:
116
111
  # Find the sibling <dd> tag for the collection date
117
- next_collection = next_collection_dt.find_next_sibling("dd").get_text(strip=True)
112
+ next_collection = next_collection_dt.find_next_sibling(
113
+ "dd"
114
+ ).get_text(strip=True)
118
115
 
119
116
  if next_collection:
120
117
  try:
121
118
  # Parse the next collection date (assuming the format is like "Tuesday 15 October 2024")
122
- parsed_date = datetime.strptime(next_collection, "%A %d %B %Y")
119
+ parsed_date = datetime.strptime(
120
+ next_collection, "%A %d %B %Y"
121
+ )
123
122
 
124
123
  # Add the bin information to the data dictionary
125
- data["bins"].append({
126
- "type": bin_type,
127
- "collectionDate": parsed_date.strftime(date_format),
128
- })
124
+ data["bins"].append(
125
+ {
126
+ "type": bin_type,
127
+ "collectionDate": parsed_date.strftime(
128
+ date_format
129
+ ),
130
+ }
131
+ )
129
132
  except ValueError as e:
130
133
  print(f"Error parsing date for {bin_type}: {e}")
131
134
  else:
@@ -82,7 +82,7 @@ class CouncilClass(AbstractGetBinDataClass):
82
82
  bin_type = " ".join(words).capitalize()
83
83
  date_list = item.find_all("p")
84
84
  for d in date_list:
85
- clean_date_str = re.sub(r'[^A-Za-z0-9 ]+', '', d.text.strip())
85
+ clean_date_str = re.sub(r"[^A-Za-z0-9 ]+", "", d.text.strip())
86
86
  next_collection = datetime.strptime(clean_date_str, "%A %d %B %Y")
87
87
  collections.append((bin_type, next_collection))
88
88
 
@@ -1,26 +1,41 @@
1
- from bs4 import BeautifulSoup
1
+ from typing import Dict, Any, Optional
2
+ from bs4 import BeautifulSoup, Tag, NavigableString
2
3
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
3
4
 
4
- #Cheshire East
5
+ """
6
+ This module provides bin collection data for Cheshire East Council.
7
+ """
8
+
9
+
5
10
  class CouncilClass(AbstractGetBinDataClass):
6
- def parse_data(self, page: str, **kwargs) -> dict:
11
+ """
12
+ A class to fetch and parse bin collection data for Cheshire East Council.
13
+ """
14
+
15
+ def parse_data(self, page: Any, **kwargs: Any) -> Dict[str, Any]:
7
16
  soup = BeautifulSoup(page.text, features="html.parser")
8
17
 
9
- bin_data_dict = {"bins": []}
18
+ bin_data_dict: Dict[str, Any] = {"bins": []}
10
19
 
11
- table = soup.find("table", {"class": "job-details"})
12
- if table:
20
+ table: Optional[Tag | NavigableString] = soup.find(
21
+ "table", {"class": "job-details"}
22
+ )
23
+ if isinstance(table, Tag): # Ensure we only proceed if 'table' is a Tag
13
24
  rows = table.find_all("tr", {"class": "data-row"})
14
25
 
15
26
  for row in rows:
16
27
  cells = row.find_all(
17
- "td", {"class": lambda L: L and L.startswith("visible-cell")}
28
+ "td",
29
+ {
30
+ "class": lambda L: isinstance(L, str)
31
+ and L.startswith("visible-cell")
32
+ }, # Explicitly check if L is a string
18
33
  )
19
- labels = cells[0].find_all("label") if cells else []
34
+ labels: list[Tag] = cells[0].find_all("label") if cells else []
20
35
 
21
36
  if len(labels) >= 3:
22
- bin_type = labels[2].get_text(strip=True)
23
- collection_date = labels[1].get_text(strip=True)
37
+ bin_type: str = labels[2].get_text(strip=True)
38
+ collection_date: str = labels[1].get_text(strip=True)
24
39
 
25
40
  bin_data_dict["bins"].append(
26
41
  {
@@ -4,7 +4,6 @@ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataC
4
4
  from dateutil.relativedelta import relativedelta
5
5
 
6
6
 
7
-
8
7
  # import the wonderful Beautiful Soup and the URL grabber
9
8
  class CouncilClass(AbstractGetBinDataClass):
10
9
  """
@@ -23,37 +22,39 @@ class CouncilClass(AbstractGetBinDataClass):
23
22
  check_uprn(user_uprn)
24
23
 
25
24
  headers = {
26
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
27
- 'Accept-Language': 'en-GB,en;q=0.9',
28
- 'Cache-Control': 'no-cache',
29
- 'Connection': 'keep-alive',
30
- 'Pragma': 'no-cache',
31
- 'Sec-Fetch-Dest': 'document',
32
- 'Sec-Fetch-Mode': 'navigate',
33
- 'Sec-Fetch-Site': 'none',
34
- 'Sec-Fetch-User': '?1',
35
- 'Upgrade-Insecure-Requests': '1',
36
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.143 Safari/537.36',
37
- 'sec-ch-ua': '"Opera GX";v="111", "Chromium";v="125", "Not.A/Brand";v="24"',
38
- 'sec-ch-ua-mobile': '?0',
39
- 'sec-ch-ua-platform': '"Windows"',
25
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
26
+ "Accept-Language": "en-GB,en;q=0.9",
27
+ "Cache-Control": "no-cache",
28
+ "Connection": "keep-alive",
29
+ "Pragma": "no-cache",
30
+ "Sec-Fetch-Dest": "document",
31
+ "Sec-Fetch-Mode": "navigate",
32
+ "Sec-Fetch-Site": "none",
33
+ "Sec-Fetch-User": "?1",
34
+ "Upgrade-Insecure-Requests": "1",
35
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.143 Safari/537.36",
36
+ "sec-ch-ua": '"Opera GX";v="111", "Chromium";v="125", "Not.A/Brand";v="24"',
37
+ "sec-ch-ua-mobile": "?0",
38
+ "sec-ch-ua-platform": '"Windows"',
40
39
  }
41
40
  params = {
42
- 'uprn': f'{user_uprn}',
41
+ "uprn": f"{user_uprn}",
43
42
  # 'uprn': f'100040128734',
44
43
  }
45
44
  response = requests.get(
46
- 'https://www.cornwall.gov.uk/umbraco/surface/waste/MyCollectionDays',
45
+ "https://www.cornwall.gov.uk/umbraco/surface/waste/MyCollectionDays",
47
46
  params=params,
48
- headers=headers
47
+ headers=headers,
49
48
  )
50
49
 
51
50
  soup = BeautifulSoup(response.text, features="html.parser")
52
51
  soup.prettify()
53
52
 
54
- for item in soup.find_all('div', class_='collection text-center service'):
53
+ for item in soup.find_all("div", class_="collection text-center service"):
55
54
  bin_type = item.contents[1].text + " bin"
56
- collection_date = datetime.strptime(item.contents[5].text, "%d %b").replace(year=curr_date.year)
55
+ collection_date = datetime.strptime(item.contents[5].text, "%d %b").replace(
56
+ year=curr_date.year
57
+ )
57
58
  if curr_date.month == 12 and collection_date.month == 1:
58
59
  collection_date = collection_date + relativedelta(years=1)
59
60
  collections.append((bin_type, collection_date))
@@ -56,9 +56,7 @@ class CouncilClass(AbstractGetBinDataClass):
56
56
  postcode_input.send_keys(user_postcode)
57
57
 
58
58
  find_address_button = WebDriverWait(driver, 10).until(
59
- EC.presence_of_element_located(
60
- (By.ID, 'submitButton0')
61
- )
59
+ EC.presence_of_element_located((By.ID, "submitButton0"))
62
60
  )
63
61
  find_address_button.click()
64
62
 
@@ -80,7 +78,7 @@ class CouncilClass(AbstractGetBinDataClass):
80
78
  template_parts = first_option.split(", ")
81
79
  template_parts[0] = user_paon # Replace the first part with user_paon
82
80
 
83
- addr_label = ", ".join(template_parts)
81
+ addr_label = ", ".join(template_parts)
84
82
  for addr_option in select.options:
85
83
  option_name = addr_option.accessible_name[0 : len(addr_label)]
86
84
  if option_name == addr_label:
@@ -100,32 +98,27 @@ class CouncilClass(AbstractGetBinDataClass):
100
98
  # Find the div with the specified id
101
99
  target_div = soup.find("div", {"id": target_div_id})
102
100
 
103
-
104
101
  # Check if the div is found
105
102
  if target_div:
106
103
  bin_data = {"bins": []}
107
104
 
108
- for bin_div in target_div.find_all(
109
- "div"
110
- ):
105
+ for bin_div in target_div.find_all("div"):
111
106
  # Extract the collection date from the message
112
107
  try:
113
108
  bin_collection_message = bin_div.find("p").text.strip()
114
109
  date_pattern = r"\b\d{2}/\d{2}/\d{4}\b"
115
110
 
116
111
  collection_date_string = (
117
- re.search(date_pattern, bin_div.text)
118
- .group(0)
119
- .strip()
120
- .replace(",", "")
121
- )
112
+ re.search(date_pattern, bin_div.text)
113
+ .group(0)
114
+ .strip()
115
+ .replace(",", "")
116
+ )
122
117
  except AttributeError:
123
118
  continue
124
119
 
125
120
  current_date = datetime.now()
126
- parsed_date = datetime.strptime(
127
- collection_date_string, "%d/%m/%Y"
128
- )
121
+ parsed_date = datetime.strptime(collection_date_string, "%d/%m/%Y")
129
122
  # Check if the parsed date is in the past and not today
130
123
  if parsed_date.date() < current_date.date():
131
124
  # If so, set the year to the next year
@@ -137,9 +130,14 @@ class CouncilClass(AbstractGetBinDataClass):
137
130
  contains_date(formatted_date)
138
131
 
139
132
  # Extract the bin type from the message
140
- bin_type_match = re.search(r"Your next (.*?) collection", bin_collection_message)
133
+ bin_type_match = re.search(
134
+ r"Your next (.*?) collection", bin_collection_message
135
+ )
141
136
  if bin_type_match:
142
- bin_info = {"type": bin_type_match.group(1), "collectionDate": formatted_date}
137
+ bin_info = {
138
+ "type": bin_type_match.group(1),
139
+ "collectionDate": formatted_date,
140
+ }
143
141
  bin_data["bins"].append(bin_info)
144
142
  else:
145
143
  raise ValueError("Collection data not found.")
@@ -16,7 +16,7 @@ class CouncilClass(AbstractGetBinDataClass):
16
16
  def parse_data(self, page: str, **kwargs) -> dict:
17
17
  data = {"bins": []}
18
18
  collections = []
19
- selected_collections = kwargs.get("paon").split(',')
19
+ selected_collections = kwargs.get("paon").split(",")
20
20
  calendar_urls = []
21
21
  run_date = datetime.now().date()
22
22
 
@@ -25,9 +25,13 @@ class CouncilClass(AbstractGetBinDataClass):
25
25
  for item in selected_collections:
26
26
  item = item.strip().lower().replace(" ", "_")
27
27
  if has_numbers(item):
28
- calendar_urls.append(f"https://www.gbcbincalendars.co.uk/json/gedling_borough_council_{item}_bin_schedule.json")
28
+ calendar_urls.append(
29
+ f"https://www.gbcbincalendars.co.uk/json/gedling_borough_council_{item}_bin_schedule.json"
30
+ )
29
31
  else:
30
- calendar_urls.append(f"https://www.gbcbincalendars.co.uk/json/gedling_borough_council_{item}_garden_bin_schedule.json")
32
+ calendar_urls.append(
33
+ f"https://www.gbcbincalendars.co.uk/json/gedling_borough_council_{item}_garden_bin_schedule.json"
34
+ )
31
35
 
32
36
  # Parse each URL and load future data
33
37
  for url in calendar_urls:
@@ -36,7 +40,9 @@ class CouncilClass(AbstractGetBinDataClass):
36
40
  raise ConnectionError(f"Could not get response from: {url}")
37
41
  json_data = response.json()["collectionDates"]
38
42
  for col in json_data:
39
- bin_date = datetime.strptime(col.get("collectionDate"), "%Y-%m-%d").date()
43
+ bin_date = datetime.strptime(
44
+ col.get("collectionDate"), "%Y-%m-%d"
45
+ ).date()
40
46
  if bin_date >= run_date:
41
47
  collections.append((col.get("alternativeName"), bin_date))
42
48
 
@@ -29,9 +29,11 @@ class CouncilClass(AbstractGetBinDataClass):
29
29
  waste_type = row.find("th").text.strip()
30
30
  next_collection = parse(row.find("td").text.strip()).date()
31
31
 
32
- data['bins'].append({
33
- "type": waste_type,
34
- "collectionDate": next_collection.strftime(date_format),
35
- })
32
+ data["bins"].append(
33
+ {
34
+ "type": waste_type,
35
+ "collectionDate": next_collection.strftime(date_format),
36
+ }
37
+ )
36
38
 
37
39
  return data
@@ -7,6 +7,7 @@ from bs4 import BeautifulSoup
7
7
  from uk_bin_collection.uk_bin_collection.common import *
8
8
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
9
9
 
10
+
10
11
  class CouncilClass(AbstractGetBinDataClass):
11
12
  def parse_data(self, page: str, **kwargs) -> dict:
12
13
 
@@ -14,7 +15,9 @@ class CouncilClass(AbstractGetBinDataClass):
14
15
  check_postcode(user_postcode)
15
16
 
16
17
  # Fetch the page content
17
- root_url = "https://myproperty.molevalley.gov.uk/molevalley/api/live_addresses/{}?format=json".format(user_postcode)
18
+ root_url = "https://myproperty.molevalley.gov.uk/molevalley/api/live_addresses/{}?format=json".format(
19
+ user_postcode
20
+ )
18
21
  response = requests.get(root_url, verify=False)
19
22
 
20
23
  if not response.ok:
@@ -63,23 +66,27 @@ class CouncilClass(AbstractGetBinDataClass):
63
66
  if bins_panel:
64
67
  panel = bins_panel.find_parent("div", class_="panel")
65
68
  print("Found 'Bins and Recycling' panel.")
66
-
69
+
67
70
  # Extract bin collection info from the un-commented HTML
68
71
  for strong_tag in panel.find_all("strong"):
69
72
  bin_type = strong_tag.text.strip()
70
73
  collection_string = strong_tag.find_next("p").text.strip()
71
-
74
+
72
75
  # Debugging output
73
76
  print(f"Processing bin type: {bin_type}")
74
77
  print(f"Collection string: {collection_string}")
75
-
78
+
76
79
  match = regex_date.search(collection_string)
77
80
  if match:
78
- collection_date = datetime.strptime(match.group(1), "%d/%m/%Y").date()
79
- data["bins"].append({
80
- "type": bin_type,
81
- "collectionDate": collection_date.strftime("%d/%m/%Y"),
82
- })
81
+ collection_date = datetime.strptime(
82
+ match.group(1), "%d/%m/%Y"
83
+ ).date()
84
+ data["bins"].append(
85
+ {
86
+ "type": bin_type,
87
+ "collectionDate": collection_date.strftime("%d/%m/%Y"),
88
+ }
89
+ )
83
90
  all_collection_dates.append(collection_date)
84
91
  else:
85
92
  # Add a debug line to show which collections are missing dates
@@ -88,7 +95,7 @@ class CouncilClass(AbstractGetBinDataClass):
88
95
  # Search for additional collections like electrical and textiles
89
96
  for p in panel.find_all("p"):
90
97
  additional_match = regex_additional_collection.match(p.text.strip())
91
-
98
+
92
99
  # Debugging output for additional collections
93
100
  if additional_match:
94
101
  bin_type = additional_match.group(1)
@@ -96,23 +103,33 @@ class CouncilClass(AbstractGetBinDataClass):
96
103
  if "each collection day" in additional_match.group(2):
97
104
  if all_collection_dates:
98
105
  collection_date = min(all_collection_dates)
99
- data["bins"].append({
100
- "type": bin_type,
101
- "collectionDate": collection_date.strftime("%d/%m/%Y"),
102
- })
106
+ data["bins"].append(
107
+ {
108
+ "type": bin_type,
109
+ "collectionDate": collection_date.strftime(
110
+ "%d/%m/%Y"
111
+ ),
112
+ }
113
+ )
103
114
  else:
104
- print("No collection dates available for additional collection.")
115
+ print(
116
+ "No collection dates available for additional collection."
117
+ )
105
118
  raise ValueError("No valid bin collection dates found.")
106
119
  else:
107
- print(f"No additional collection found in paragraph: {p.text.strip()}")
120
+ print(
121
+ f"No additional collection found in paragraph: {p.text.strip()}"
122
+ )
108
123
  else:
109
- raise ValueError("Unable to find 'Bins and Recycling' panel in the HTML data.")
124
+ raise ValueError(
125
+ "Unable to find 'Bins and Recycling' panel in the HTML data."
126
+ )
110
127
 
111
128
  # Debugging to check collected data
112
129
  print(f"Collected bin data: {data}")
113
-
130
+
114
131
  # Handle the case where no collection dates were found
115
132
  if not all_collection_dates:
116
133
  raise ValueError("No valid collection dates were found in the data.")
117
-
118
- return data
134
+
135
+ return data
@@ -23,7 +23,7 @@ class CouncilClass(AbstractGetBinDataClass):
23
23
  # Get the first form
24
24
  response = s.get(
25
25
  "https://my.northtyneside.gov.uk/category/81/bin-collection-dates",
26
- verify = False,
26
+ verify=False,
27
27
  )
28
28
 
29
29
  # Find the form ID and submit with a postcode
@@ -31,13 +31,13 @@ class CouncilClass(AbstractGetBinDataClass):
31
31
  form_build_id = soup.find("input", {"name": "form_build_id"})["value"]
32
32
  response = s.post(
33
33
  "https://my.northtyneside.gov.uk/category/81/bin-collection-dates",
34
- data = {
34
+ data={
35
35
  "postcode": user_postcode,
36
36
  "op": "Find",
37
37
  "form_build_id": form_build_id,
38
38
  "form_id": "ntc_address_wizard",
39
39
  },
40
- verify = False,
40
+ verify=False,
41
41
  )
42
42
 
43
43
  # Find the form ID and submit with the UPRN
@@ -45,18 +45,18 @@ class CouncilClass(AbstractGetBinDataClass):
45
45
  form_build_id = soup.find("input", {"name": "form_build_id"})["value"]
46
46
  response = s.post(
47
47
  "https://my.northtyneside.gov.uk/category/81/bin-collection-dates",
48
- data = {
48
+ data={
49
49
  "house_number": f"0000{user_uprn}",
50
50
  "op": "Use",
51
51
  "form_build_id": form_build_id,
52
52
  "form_id": "ntc_address_wizard",
53
53
  },
54
- verify = False,
54
+ verify=False,
55
55
  )
56
56
 
57
57
  # Parse form page and get the day of week and week offsets
58
58
  soup = BeautifulSoup(response.text, features="html.parser")
59
- info_section = soup.find("section", {"class": "block block-ntc-bins clearfix"})
59
+ info_section = soup.find("section", {"class": "block block-ntc-bins clearfix"})
60
60
 
61
61
  regular_day, garden_day, special_day = None, None, None
62
62
  # Get day of week and week label for refuse, garden and special collections.
@@ -82,7 +82,9 @@ class CouncilClass(AbstractGetBinDataClass):
82
82
  weeks_total = math.floor((datetime(2026, 4, 1) - datetime.now()).days / 7)
83
83
 
84
84
  # The garden calendar only shows until end of November 2024, work out how many weeks that is
85
- garden_weeks_total = math.floor((datetime(2024, 12, 1) - datetime.now()).days / 7)
85
+ garden_weeks_total = math.floor(
86
+ (datetime(2024, 12, 1) - datetime.now()).days / 7
87
+ )
86
88
 
87
89
  regular_collections, garden_collections, special_collections = [], [], []
88
90
  # Convert day text to series of dates using previous calculation
@@ -134,10 +136,10 @@ class CouncilClass(AbstractGetBinDataClass):
134
136
 
135
137
  return {
136
138
  "bins": [
137
- {
139
+ {
138
140
  "type": item[0],
139
141
  "collectionDate": item[1].strftime(date_format),
140
142
  }
141
143
  for item in sorted(collections, key=lambda x: x[1])
142
144
  ]
143
- }
145
+ }
@@ -36,8 +36,7 @@ class CouncilClass(AbstractGetBinDataClass):
36
36
  collection_date = datetime.strptime(
37
37
  remove_ordinal_indicator_from_date_string(
38
38
  week_text[0].split(" - ")[0]
39
- )
40
- .strip(),
39
+ ).strip(),
41
40
  "%A %d %B",
42
41
  )
43
42
  next_collection = collection_date.replace(year=datetime.now().year)
@@ -17,7 +17,9 @@ class CouncilClass(AbstractGetBinDataClass):
17
17
  check_uprn(user_uprn)
18
18
 
19
19
  response = requests.post(
20
- "https://www.rotherham.gov.uk/bin-collections?address={}&submit=Submit".format(user_uprn)
20
+ "https://www.rotherham.gov.uk/bin-collections?address={}&submit=Submit".format(
21
+ user_uprn
22
+ )
21
23
  )
22
24
  # Make a BS4 object
23
25
  soup = BeautifulSoup(response.text, features="html.parser")
@@ -25,15 +27,15 @@ class CouncilClass(AbstractGetBinDataClass):
25
27
 
26
28
  data = {"bins": []}
27
29
 
28
- table = soup.select('table')[0]
30
+ table = soup.select("table")[0]
29
31
 
30
32
  if table:
31
- rows = table.select('tr')
33
+ rows = table.select("tr")
32
34
 
33
35
  for index, row in enumerate(rows):
34
- bin_info_cell = row.select('td')
35
- if bin_info_cell:
36
- bin_type = bin_info_cell[0].get_text(separator=' ', strip=True)
36
+ bin_info_cell = row.select("td")
37
+ if bin_info_cell:
38
+ bin_type = bin_info_cell[0].get_text(separator=" ", strip=True)
37
39
  bin_collection = bin_info_cell[1]
38
40
 
39
41
  if bin_collection:
@@ -3,6 +3,7 @@ from uk_bin_collection.uk_bin_collection.common import *
3
3
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
4
4
  from datetime import datetime
5
5
 
6
+
6
7
  class CouncilClass(AbstractGetBinDataClass):
7
8
  """
8
9
  Concrete class to scrape bin collection data.
@@ -25,23 +26,23 @@ class CouncilClass(AbstractGetBinDataClass):
25
26
  data = {"bins": []}
26
27
  # Locate the section with bin collection data
27
28
  sections = soup.find_all("div", {"class": "wil_c-content-section_heading"})
28
-
29
+
29
30
  for s in sections:
30
31
  if s.get_text(strip=True).lower() == "bin collections":
31
32
  rows = s.find_next_sibling(
32
33
  "div", {"class": "c-content-section_body"}
33
34
  ).find_all("div", class_="tablet:l-col-fb-4 u-mt-10")
34
-
35
+
35
36
  for row in rows:
36
37
  title_elem = row.find("div", class_="u-mb-4")
37
38
  if title_elem:
38
39
  title = title_elem.get_text(strip=True).capitalize()
39
-
40
+
40
41
  # Find all collection info in the same section
41
42
  collections = row.find_all("div", class_="u-mb-2")
42
43
  for c in collections:
43
44
  text = c.get_text(strip=True).lower()
44
-
45
+
45
46
  if "next collection" in text:
46
47
  date_text = text.replace("next collection - ", "")
47
48
  try:
@@ -51,34 +52,43 @@ class CouncilClass(AbstractGetBinDataClass):
51
52
 
52
53
  dict_data = {
53
54
  "type": title,
54
- "collectionDate": next_collection_date
55
+ "collectionDate": next_collection_date,
55
56
  }
56
57
  data["bins"].append(dict_data)
57
58
  except ValueError:
58
59
  # Skip if the date isn't a valid date
59
60
  print(f"Skipping invalid date: {date_text}")
60
-
61
+
61
62
  # Get future collections
62
63
  future_collections_section = row.find("ul", class_="u-mt-4")
63
64
  if future_collections_section:
64
- future_collections = future_collections_section.find_all("li")
65
+ future_collections = (
66
+ future_collections_section.find_all("li")
67
+ )
65
68
  for future_collection in future_collections:
66
- future_date_text = future_collection.get_text(strip=True)
69
+ future_date_text = future_collection.get_text(
70
+ strip=True
71
+ )
67
72
  try:
68
73
  future_collection_date = datetime.strptime(
69
74
  future_date_text, "%A, %d %B %Y"
70
75
  ).strftime(date_format)
71
76
 
72
77
  # Avoid duplicates of next collection date
73
- if future_collection_date != next_collection_date:
78
+ if (
79
+ future_collection_date
80
+ != next_collection_date
81
+ ):
74
82
  dict_data = {
75
83
  "type": title,
76
- "collectionDate": future_collection_date
84
+ "collectionDate": future_collection_date,
77
85
  }
78
86
  data["bins"].append(dict_data)
79
87
  except ValueError:
80
88
  # Skip if the future collection date isn't valid
81
- print(f"Skipping invalid future date: {future_date_text}")
89
+ print(
90
+ f"Skipping invalid future date: {future_date_text}"
91
+ )
82
92
 
83
93
  # Sort the collections by date
84
94
  data["bins"].sort(
@@ -56,7 +56,7 @@ class CouncilClass(AbstractGetBinDataClass):
56
56
  )
57
57
  )
58
58
  ).click()
59
-
59
+
60
60
  # Wait for the Show collection dates button to appear, then click it to get the collection dates
61
61
  inputElement_show_dates_button = WebDriverWait(driver, timeout).until(
62
62
  EC.presence_of_element_located(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uk_bin_collection
3
- Version: 0.101.0
3
+ Version: 0.103.0
4
4
  Summary: Python Lib to collect UK Bin Data
5
5
  Author: Robert Bradley
6
6
  Author-email: robbrad182@gmail.com
@@ -98,6 +98,24 @@ This integration can be installed directly via HACS. To install:
98
98
  1. Restart your Home Assistant.
99
99
  1. In the Home Assistant UI go to `Settings` > `Devices & Services` click `+ Add Integration` and search for `UK Bin Collection Data`.
100
100
 
101
+ ### Overriding the Bin Icon and Bin Colour
102
+ We realise it is difficult to set a colour from the councils text for the Bin Type and to keep the integration generic we dont capture colour from a council(not all councils supply this as a field), only bin type and next collection date.
103
+
104
+ When you configure the componenent on the first screen you can set a JSON string to map the bin type to the colour and icon
105
+
106
+ Here is an example to set the colour and icon for the type `Empty Standard General Waste`. This type is the type returned from the council for the bin. You can do this for multiple bins.
107
+
108
+ If you miss this on the first setup you can reconfigure it.
109
+
110
+ ```
111
+ {
112
+ "Empty Standard General Waste":
113
+ {
114
+ "icon": "mdi:trash-can",
115
+ "color": "blue"
116
+ }
117
+ }
118
+
101
119
  ---
102
120
 
103
121
  ## Standalone Usage
@@ -2,7 +2,7 @@ uk_bin_collection/README.rst,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
2
2
  uk_bin_collection/tests/council_feature_input_parity.py,sha256=DO6Mk4ImYgM5ZCZ-cutwz5RoYYWZRLYx2tr6zIs_9Rc,3843
3
3
  uk_bin_collection/tests/features/environment.py,sha256=VQZjJdJI_kZn08M0j5cUgvKT4k3iTw8icJge1DGOkoA,127
4
4
  uk_bin_collection/tests/features/validate_council_outputs.feature,sha256=SJK-Vc737hrf03tssxxbeg_JIvAH-ddB8f6gU1LTbuQ,251
5
- uk_bin_collection/tests/input.json,sha256=tzx_1v1h_XLo3-dJHd5vHnX97dIJVHFjjAkC4allAXc,71114
5
+ uk_bin_collection/tests/input.json,sha256=_AUgQoj2KfwyLGGCXBX-OkT0fjRVTpSxKcnpzbE2aS8,71114
6
6
  uk_bin_collection/tests/output.schema,sha256=ZwKQBwYyTDEM4G2hJwfLUVM-5v1vKRvRK9W9SS1sd18,1086
7
7
  uk_bin_collection/tests/step_defs/step_helpers/file_handler.py,sha256=Ygzi4V0S1MIHqbdstUlIqtRIwnynvhu4UtpweJ6-5N8,1474
8
8
  uk_bin_collection/tests/step_defs/test_validate_council.py,sha256=LrOSt_loA1Mw3vTqaO2LpaDMu7rYJy6k5Kr-EOBln7s,3424
@@ -19,16 +19,16 @@ uk_bin_collection/uk_bin_collection/councils/AylesburyValeCouncil.py,sha256=Louq
19
19
  uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py,sha256=W7QBx6Mgso8RYosuXsaYo3GGNAu-tiyBSmuYxr1JSOU,1707
20
20
  uk_bin_collection/uk_bin_collection/councils/BarnetCouncil.py,sha256=Sd4-pbv0QZsR7soxvXYqsfdOUIqZqS6notyoZthG77s,9182
21
21
  uk_bin_collection/uk_bin_collection/councils/BarnsleyMBCouncil.py,sha256=MgF_7XyIcIoNzFR0OJsjBkLCZKgWxBrV6nTcutMxO1Q,4244
22
- uk_bin_collection/uk_bin_collection/councils/BasildonCouncil.py,sha256=SBvAa0GZM3V7ygK8ARawbHAPH6R_303U30RH8WYPi5Q,3020
22
+ uk_bin_collection/uk_bin_collection/councils/BasildonCouncil.py,sha256=UBHINX8WknQfnHU43Wp5kXAqmHl00aWM0Fh8NQdWBZA,3244
23
23
  uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py,sha256=VPWGljnH4C3q8qs5ZmCtqjNjgWQvviALzjk00q3EZeQ,2632
24
24
  uk_bin_collection/uk_bin_collection/councils/BathAndNorthEastSomersetCouncil.py,sha256=N_TPiIv8VBzN3rY0p3JtLlxSEru-6k1wW4UNIhN5X1M,3709
25
25
  uk_bin_collection/uk_bin_collection/councils/BedfordBoroughCouncil.py,sha256=CvGB7w9HMn7XyEtwfd9MWZE_HlZ75pDcaKMsQJz0xhk,1669
26
26
  uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py,sha256=U1HOr9YLMAlFoZysfw5n04E0bVuCliO5Yj1FMiiwcHA,2549
27
- uk_bin_collection/uk_bin_collection/councils/BelfastCityCouncil.py,sha256=mspYVHO8fgoVIwogT6V2Go1tbf3PDbEmr8kZMJug5Gs,4235
28
- uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py,sha256=nzet8yKKzpiAQXDr0tmqXuJ4R-EdJqtxSNGiUCh79bA,5591
27
+ uk_bin_collection/uk_bin_collection/councils/BelfastCityCouncil.py,sha256=SGOv9mm3fByyR3TQDyNcSLeidX_7FlHelkxnh-NUTcY,4327
28
+ uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py,sha256=wqqCQZzu_q_pJrxTTlTrGGDgPB5EYgd4RiBy7nKnSHc,5835
29
29
  uk_bin_collection/uk_bin_collection/councils/BirminghamCityCouncil.py,sha256=now2xgpfshYM33UWC18j6xa6BuBydO5Sl7OrDQOo6b0,4687
30
30
  uk_bin_collection/uk_bin_collection/councils/BlackburnCouncil.py,sha256=jHbCK8sL09vdmdP7Xnh8lIrU5AHTnJLEZfOLephPvWg,4090
31
- uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py,sha256=r7wjrRPT2EinRMnYjGxmsCD6aMFhEOyRNd8_3R9PdQU,4117
31
+ uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py,sha256=WI68r8jB0IHPUT4CgmZMtng899AAMFTxkyTdPg9yLF8,4117
32
32
  uk_bin_collection/uk_bin_collection/councils/BracknellForestCouncil.py,sha256=Llo1rULaAZ8rChVYZqXFFLo7CN6vbT0ULUJD6ActouY,9015
33
33
  uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py,sha256=VFrdcqKpHPw8v77Ll9QzBz_4carUfC1XYnxqUvDihkA,4275
34
34
  uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py,sha256=k6qt4cds-Ejd97Z-__pw2BYvGVbFdc9SUfF73PPrTNA,5823
@@ -43,13 +43,13 @@ uk_bin_collection/uk_bin_collection/councils/CardiffCouncil.py,sha256=_k3sT_WR-g
43
43
  uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py,sha256=JVPYUIlU2ISgbUSr5AOOXNK6IFQFtQmhZyYIMAOedD4,3858
44
44
  uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py,sha256=tXfzMetN6wxahuGGRp2mIyCCDSL4F2aG61HhUxw6COQ,2172
45
45
  uk_bin_collection/uk_bin_collection/councils/ChelmsfordCityCouncil.py,sha256=EB88D0MNJwuDZ2GX1ENc5maGYx17mnHTCtNl6s-v11E,5090
46
- uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py,sha256=VZQsiSZX7Wdta3cXCBOcLv-muLb9EVmmUDEHerWyR3o,1146
46
+ uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py,sha256=aMqT5sy1Z1gklFO5Xl893OgeBmpf19OwpizWEKWQ3hg,1680
47
47
  uk_bin_collection/uk_bin_collection/councils/CheshireWestAndChesterCouncil.py,sha256=M58kIHCAZu1K5qBp6_M4sw8nMvHz38YkoxY_sNuhvOI,4780
48
48
  uk_bin_collection/uk_bin_collection/councils/ChichesterDistrictCouncil.py,sha256=HxrLcJves7ZsE8FbooymeecTUmScY4R7Oi71vwCePPo,4118
49
49
  uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py,sha256=M7HjuUaFq8aSnOf_9m1QS4MmPPMmPhF3mLHSrfDPtV0,5194
50
50
  uk_bin_collection/uk_bin_collection/councils/ColchesterCityCouncil.py,sha256=Mny-q2rQkWe2Tj1gINwEM1L4AkqQl1EDMAaKY0-deD4,3968
51
51
  uk_bin_collection/uk_bin_collection/councils/ConwyCountyBorough.py,sha256=el75qv2QyfWZBU09tJLvD8vLQZ9pCg73u1NBFs6ybo8,1034
52
- uk_bin_collection/uk_bin_collection/councils/CornwallCouncil.py,sha256=W7_wWHLbE0cyE90GkNaZHmR7Lbd2Aq48A1hKMUNO-vg,2806
52
+ uk_bin_collection/uk_bin_collection/councils/CornwallCouncil.py,sha256=WZiz50svwyZgO8QKUCLy7hfFuy2HmAx5h-TG3yAweRA,2836
53
53
  uk_bin_collection/uk_bin_collection/councils/CrawleyBoroughCouncil.py,sha256=_BEKZAjlS5Ad5DjyxqAEFSLn8F-KYox0zmn4BXaAD6A,2367
54
54
  uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py,sha256=QJH27plySbbmoNcLNUXq-hUiFmZ5zBlRS5mzOJgWSK8,11594
55
55
  uk_bin_collection/uk_bin_collection/councils/DacorumBoroughCouncil.py,sha256=Tm_6pvBPj-6qStbe6-02LXaoCOlnnDvVXAAocGVvf_E,3970
@@ -69,7 +69,7 @@ uk_bin_collection/uk_bin_collection/councils/EastRidingCouncil.py,sha256=oL-Nqri
69
69
  uk_bin_collection/uk_bin_collection/councils/EastSuffolkCouncil.py,sha256=qQ0oOfGd0sWcczse_B22YoeL9uj3og8v3UJLt_Sx29c,4353
70
70
  uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py,sha256=V4Vso4DvawFiezKlmXbTlJEK9Sjhz9nA8WeYjwtO2e4,2310
71
71
  uk_bin_collection/uk_bin_collection/councils/ElmbridgeBoroughCouncil.py,sha256=TgBOaReHWBbm0avV7HqRf0x7cxDe9cacTUcP9TFFprs,3005
72
- uk_bin_collection/uk_bin_collection/councils/EnfieldCouncil.py,sha256=HhKHlLciZKXViqcgkWme-wBUKlGhAs5LIpkKuRETvXM,6119
72
+ uk_bin_collection/uk_bin_collection/councils/EnfieldCouncil.py,sha256=2yR5p-kdApOm1gHiynNECP0jQDvaYHOiT6MAQJAvunE,6144
73
73
  uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py,sha256=_9QJYDHpdnYK5R6znvZk1w0F9GnPnI8G4b6I_p26h4U,1695
74
74
  uk_bin_collection/uk_bin_collection/councils/EppingForestDistrictCouncil.py,sha256=cKFllQ4zt6MGkwiz_HedZvw3iL1kRMLA6Ct2spUE5og,2085
75
75
  uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py,sha256=QTQA6NjZtTL2baDeerIQW1SQpawwu6kGDMGdVvYQRRo,2501
@@ -78,7 +78,7 @@ uk_bin_collection/uk_bin_collection/councils/FarehamBoroughCouncil.py,sha256=25Q
78
78
  uk_bin_collection/uk_bin_collection/councils/FenlandDistrictCouncil.py,sha256=sFrnKzIE2tIcz0YrC6A9HcevzgNdf6E6_HLGMWDKtGw,2513
79
79
  uk_bin_collection/uk_bin_collection/councils/ForestOfDeanDistrictCouncil.py,sha256=xO5gqgsN9K-cQsuDoQF7ycZkjNdCPAQwIYOCFWxFJ_Y,4504
80
80
  uk_bin_collection/uk_bin_collection/councils/GatesheadCouncil.py,sha256=SRCgYhYs6rv_8C1UEDVORHZgXxcJkoZBjzdYS4Lu-ew,4531
81
- uk_bin_collection/uk_bin_collection/councils/GedlingBoroughCouncil.py,sha256=IssL5CJSdcGPkJCB0q2kieUSEjfoS6nDKfeT7-9eKsQ,2183
81
+ uk_bin_collection/uk_bin_collection/councils/GedlingBoroughCouncil.py,sha256=XzfFMCwclh9zAJgsbaj4jywjdiH0wPaFicaVsLrN3ms,2297
82
82
  uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py,sha256=i7BympEhCm7D9yR0p5_QQICtWvNcDYNJIWB19SA0g2k,2303
83
83
  uk_bin_collection/uk_bin_collection/councils/GloucesterCityCouncil.py,sha256=8Wjvmdvg5blHVrREaEnhhWZaWhYVP4v_KdDVPLIUxaU,4889
84
84
  uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py,sha256=9pVrmQhZcK2AD8gX8mNvP--L4L9KaY6L3B822VX6fec,5695
@@ -90,7 +90,7 @@ uk_bin_collection/uk_bin_collection/councils/HighlandCouncil.py,sha256=GNxDU65Qu
90
90
  uk_bin_collection/uk_bin_collection/councils/HounslowCouncil.py,sha256=LXhJ47rujx7k3naz0tFiTT1l5k6gAYcVdekJN1t_HLY,4564
91
91
  uk_bin_collection/uk_bin_collection/councils/HullCityCouncil.py,sha256=UHcesBoctFVcXDYuwfag43KbcJcopkEDzJ-54NxtK0Q,1851
92
92
  uk_bin_collection/uk_bin_collection/councils/HuntingdonDistrictCouncil.py,sha256=dGyhhG6HRjQ2SPeiRwUPTGlk9dPIslagV2k0GjEOn1s,1587
93
- uk_bin_collection/uk_bin_collection/councils/IslingtonCouncil.py,sha256=O2o5-oueMm8jXES_2kQGHVo-JpBSq1Z-1mqq11eyi3s,1208
93
+ uk_bin_collection/uk_bin_collection/councils/IslingtonCouncil.py,sha256=xavzL6ZIU9DG8Xro3vN0CEnYmNU31OGnOvnq78wgpQc,1258
94
94
  uk_bin_collection/uk_bin_collection/councils/KingstonUponThamesCouncil.py,sha256=iZ7njIxccCGBhUUWWd9Azh7cxUAKaofebCm3lo-TuxA,3543
95
95
  uk_bin_collection/uk_bin_collection/councils/KirkleesCouncil.py,sha256=gtAtM6FappSZ0-BKid7_pnSrnqPPjHn_UYWqMg9wQlM,4499
96
96
  uk_bin_collection/uk_bin_collection/councils/KnowsleyMBCouncil.py,sha256=VdlWDESoHfr_X0r8-UMaLMUQhKZOa2BnpVPkX-1u3EQ,5605
@@ -114,7 +114,7 @@ uk_bin_collection/uk_bin_collection/councils/MidAndEastAntrimBoroughCouncil.py,s
114
114
  uk_bin_collection/uk_bin_collection/councils/MidSussexDistrictCouncil.py,sha256=AZgC9wmDLEjUOtIFvf0ehF5LHturXTH4DkE3ioPSVBA,6254
115
115
  uk_bin_collection/uk_bin_collection/councils/MidlothianCouncil.py,sha256=3K3X7kv1oOJkZm2ivnT8Lx0GAzPefgFSHjruwcFgO7I,2821
116
116
  uk_bin_collection/uk_bin_collection/councils/MiltonKeynesCityCouncil.py,sha256=3olsWa77L34vz-c7NgeGK9xmNuR4Ws_oAk5D4UpIkPw,2005
117
- uk_bin_collection/uk_bin_collection/councils/MoleValleyDistrictCouncil.py,sha256=bvCrC4Qcg0Uzp9zZGcC7-7-oJcMh2cb1VaXfdkB11oc,5257
117
+ uk_bin_collection/uk_bin_collection/councils/MoleValleyDistrictCouncil.py,sha256=xWR5S0gwQu9gXxjl788Wux1KaC0CT7ZFw0iXuRLZCEM,5599
118
118
  uk_bin_collection/uk_bin_collection/councils/NeathPortTalbotCouncil.py,sha256=ychYR2nsyk2UIb8tjWaKrLUT4hxSsHN558l3RqZ0mjw,5635
119
119
  uk_bin_collection/uk_bin_collection/councils/NewForestCouncil.py,sha256=ylTn9KmWITtaO9_Z8kJCN2w2ALfhrfGt3SeJ78lgw7M,5391
120
120
  uk_bin_collection/uk_bin_collection/councils/NewarkAndSherwoodDC.py,sha256=lAleYfCGUWCKOi7Ye_cjgfpI3pWwTcFctlYmh0hjebM,2140
@@ -130,7 +130,7 @@ uk_bin_collection/uk_bin_collection/councils/NorthLincolnshireCouncil.py,sha256=
130
130
  uk_bin_collection/uk_bin_collection/councils/NorthNorfolkDistrictCouncil.py,sha256=VV_zqVZYv8ekXcUHhrBlTX_W5qLYE9IA3mT2xmrZqoI,4315
131
131
  uk_bin_collection/uk_bin_collection/councils/NorthNorthamptonshireCouncil.py,sha256=kcMN-5GBjYDM9F1BKfHoYeydub8SuDxHamJbSvJRZ68,2337
132
132
  uk_bin_collection/uk_bin_collection/councils/NorthSomersetCouncil.py,sha256=EbFVnPYZIOkru5_Y75kjljM3Cr3HIJgP-SU6hxgf6tk,2754
133
- uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py,sha256=N_ZRk3LZbIin7K4Vr8Rx-tExMW6Av7-WNQu4OOG3cfE,5921
133
+ uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py,sha256=MHfcSrfWBIiWwVnhOqUrOkr0Ahguu_oU4pHrv_vSa-Y,5929
134
134
  uk_bin_collection/uk_bin_collection/councils/NorthWestLeicestershire.py,sha256=gJj0dyQc5QUefqusKGk2LLXfWbG5tlEXUOh8KAPh3RI,4584
135
135
  uk_bin_collection/uk_bin_collection/councils/NorthYorkshire.py,sha256=2wTrr3VrZDp9-YtDPmWd649gXeWH4hbm2-Hw8Vau5Xs,1933
136
136
  uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py,sha256=KEFsxEvQ159fkuFo-fza67YCnnCZ5ElwE80zTrqDEWI,4990
@@ -146,8 +146,8 @@ uk_bin_collection/uk_bin_collection/councils/ReigateAndBansteadBoroughCouncil.py
146
146
  uk_bin_collection/uk_bin_collection/councils/RenfrewshireCouncil.py,sha256=VlWm-w4d-UchoENe_hCTCGlfSHiMlS4wNEeMvxuNR2U,5109
147
147
  uk_bin_collection/uk_bin_collection/councils/RhonddaCynonTaffCouncil.py,sha256=wInyVG_0wRrX_dRO9qbAzPhlXDseXapj2zQhsISw8gg,3233
148
148
  uk_bin_collection/uk_bin_collection/councils/RochdaleCouncil.py,sha256=UTSwSw515VehGn4xkjjRhUlzS4lDj4hgna6y-4VW3uM,2379
149
- uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py,sha256=7J83wWoFYukSnVgpV_bDLZlAbeTi94xgjMe9x_su7m8,2646
150
- uk_bin_collection/uk_bin_collection/councils/RotherhamCouncil.py,sha256=sjZ00iBdqn2kOvGsWyMpY3h-HWmfC_K6LQ2wz1X5WGg,1761
149
+ uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py,sha256=rfhD66A9HfHL46ldF9sbxvV7fPaaoNxzIJbHjVT6A90,2621
150
+ uk_bin_collection/uk_bin_collection/councils/RotherhamCouncil.py,sha256=LtMPM8lj5bfReDR4buHEo-aRC_HTBIeo1nf8GE5-R80,1790
151
151
  uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py,sha256=a5ySLmFvvY56QMA7-bk6MVBxRp5tPBIBg4navH0eYas,4306
152
152
  uk_bin_collection/uk_bin_collection/councils/RushcliffeBoroughCouncil.py,sha256=wMtiYRirT585vtsEOIyXHugk7aEj3pvyVWBaAePdqtE,4005
153
153
  uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py,sha256=ZsGnXjoEaOS6U7fI0w7-uqxayAHdNVKsJi2fqIWEls8,3375
@@ -192,7 +192,7 @@ uk_bin_collection/uk_bin_collection/councils/TunbridgeWellsCouncil.py,sha256=s8N
192
192
  uk_bin_collection/uk_bin_collection/councils/UttlesfordDistrictCouncil.py,sha256=GSELWbSn5jtznv6FSLIMxK6CyQ27MW9FoY_m5jhTEBA,4175
193
193
  uk_bin_collection/uk_bin_collection/councils/ValeofGlamorganCouncil.py,sha256=Phgb_ECiUOOkqOx6OsfsTHMCW5VQfRmOC2zgYIQhuZA,5044
194
194
  uk_bin_collection/uk_bin_collection/councils/ValeofWhiteHorseCouncil.py,sha256=KBKGHcWAdPC_8-CfKnLOdP7Ww6RIvlxLIJGqBsq_77g,4208
195
- uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py,sha256=-xqJOzHTrT4jOB3rHPXFYeqLaHyK9XmCPi92whaYBhw,4671
195
+ uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py,sha256=vRfIU0Uloi1bgXqjOCpdb-EQ4oY-aismcANZRwOIFkc,4914
196
196
  uk_bin_collection/uk_bin_collection/councils/WalsallCouncil.py,sha256=_anovUnXMr40lZLHyX3opIP73BwauCllKy-Z2SBrzPw,2076
197
197
  uk_bin_collection/uk_bin_collection/councils/WalthamForest.py,sha256=P7MMw0EhpRmDbbnHb25tY5_yvYuZUFwJ1br4TOv24sY,4997
198
198
  uk_bin_collection/uk_bin_collection/councils/WarwickDistrictCouncil.py,sha256=3WQrAxzYzKoV4LyOqNTp9xINVsNi1xW9t8etducGeag,1146
@@ -211,15 +211,15 @@ uk_bin_collection/uk_bin_collection/councils/WinchesterCityCouncil.py,sha256=W2k
211
211
  uk_bin_collection/uk_bin_collection/councils/WindsorAndMaidenheadCouncil.py,sha256=7Qhznj95ktAQjpWm5C8pbD5UcvfXm7Mwb7_DQxwjGSM,1777
212
212
  uk_bin_collection/uk_bin_collection/councils/WirralCouncil.py,sha256=X_e9zXEZAl_Mp6nPORHc9CTmf3QHdoMY3BCnKrXEr1I,2131
213
213
  uk_bin_collection/uk_bin_collection/councils/WokingBoroughCouncil.py,sha256=37igH9g0xe4XIhRhcJ-ZJBU8MxTp5yzgpadWbdE33Yg,5205
214
- uk_bin_collection/uk_bin_collection/councils/WokinghamBoroughCouncil.py,sha256=qQOg5HjvHMTRuy3SVX8vbXhSqYBFZmN-htk_Dtoae0M,4126
214
+ uk_bin_collection/uk_bin_collection/councils/WokinghamBoroughCouncil.py,sha256=H8aFHlacwV07X-6T9RQua4irqDA0cIQrF4O1FfPR7yI,4114
215
215
  uk_bin_collection/uk_bin_collection/councils/WychavonDistrictCouncil.py,sha256=YuZdzEW0CZLwusm1VQcGRIKXAab_UDFLaCnN60itt_E,5776
216
216
  uk_bin_collection/uk_bin_collection/councils/WyreCouncil.py,sha256=zDDa7n4K_zm5PgDL08A26gD9yOOsOhuexI3x2seaBF4,3511
217
217
  uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py,sha256=I2kBYMlsD4bIdsvmoSzBjJAvTTi6yPfJa8xjJx1ys2w,1490
218
218
  uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py,sha256=4s9ODGPAwPqwXc8SrTX5Wlfmizs3_58iXUtHc4Ir86o,1162
219
219
  uk_bin_collection/uk_bin_collection/create_new_council.py,sha256=m-IhmWmeWQlFsTZC4OxuFvtw5ZtB8EAJHxJTH4O59lQ,1536
220
220
  uk_bin_collection/uk_bin_collection/get_bin_data.py,sha256=YvmHfZqanwrJ8ToGch34x-L-7yPe31nB_x77_Mgl_vo,4545
221
- uk_bin_collection-0.101.0.dist-info/LICENSE,sha256=vABBUOzcrgfaTKpzeo-si9YVEun6juDkndqA8RKdKGs,1071
222
- uk_bin_collection-0.101.0.dist-info/METADATA,sha256=fcu_gjSU7JVGJ-O_7ZXo9HUj1YVcS4JeKJQ-zZnhNDA,16844
223
- uk_bin_collection-0.101.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
224
- uk_bin_collection-0.101.0.dist-info/entry_points.txt,sha256=36WCSGMWSc916S3Hi1ZkazzDKHaJ6CD-4fCEFm5MIao,90
225
- uk_bin_collection-0.101.0.dist-info/RECORD,,
221
+ uk_bin_collection-0.103.0.dist-info/LICENSE,sha256=vABBUOzcrgfaTKpzeo-si9YVEun6juDkndqA8RKdKGs,1071
222
+ uk_bin_collection-0.103.0.dist-info/METADATA,sha256=a_GcUI_307Qq6PiurZcZ1WUzk4AoPRjQLZPLj9TPKVs,17630
223
+ uk_bin_collection-0.103.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
224
+ uk_bin_collection-0.103.0.dist-info/entry_points.txt,sha256=36WCSGMWSc916S3Hi1ZkazzDKHaJ6CD-4fCEFm5MIao,90
225
+ uk_bin_collection-0.103.0.dist-info/RECORD,,