uk_bin_collection 0.84.2__py3-none-any.whl → 0.85.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.
@@ -68,6 +68,13 @@
68
68
  "wiki_name": "Bedfordshire Council",
69
69
  "wiki_note": "In order to use this parser, you must provide a valid postcode and a uprn retrieved from the councils website for your specific address"
70
70
  },
71
+ "BelfastCityCouncil": {
72
+ "skip_get_url": true,
73
+ "url": "https://online.belfastcity.gov.uk/find-bin-collection-day/Default.aspx",
74
+ "wiki_name": "BelfastCityCouncil",
75
+ "postcode": "BT10 0GY",
76
+ "uprn": "185086469"
77
+ },
71
78
  "BexleyCouncil": {
72
79
  "house_number": "1 Dorchester Avenue, Bexley",
73
80
  "postcode": "DA5 3AH",
@@ -352,6 +359,14 @@
352
359
  "url": "https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn=",
353
360
  "wiki_name": "Eastleigh Borough Council"
354
361
  },
362
+ "EnfieldCouncil": {
363
+ "url": "https://www.enfield.gov.uk/services/rubbish-and-recycling/find-my-collection-day",
364
+ "wiki_name": "Enfield Council",
365
+ "skip_get_url": true,
366
+ "postcode": "N13 5AJ",
367
+ "house_number": "111",
368
+ "web_driver": "http://selenium:4444"
369
+ },
355
370
  "EnvironmentFirst": {
356
371
  "url": "https://environmentfirst.co.uk/house.php?uprn=100060055444",
357
372
  "wiki_command_url_override": "https://environmentfirst.co.uk/house.php?uprn=XXXXXXXXXX",
@@ -0,0 +1,100 @@
1
+ import logging
2
+ from datetime import datetime
3
+
4
+ import requests
5
+ import urllib
6
+
7
+ from bs4 import BeautifulSoup
8
+ from uk_bin_collection.uk_bin_collection.common import *
9
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
10
+
11
+
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 get_session_variable(self, soup, id) -> str:
22
+ """Extract ASP.NET variable from the HTML."""
23
+ element = soup.find("input", {"id": id})
24
+ if element:
25
+ return element.get("value")
26
+ else:
27
+ raise ValueError(f"Unable to find element with id: {id}")
28
+
29
+ def parse_data(self, page: str, **kwargs) -> dict:
30
+ bin_data = {"bins": []}
31
+ headers = {
32
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/119.0"
33
+ }
34
+
35
+ session = requests.Session()
36
+ session.headers.update(headers)
37
+
38
+ user_uprn = kwargs.get("uprn")
39
+ user_postcode = kwargs.get("postcode")
40
+ URL = "https://online.belfastcity.gov.uk/find-bin-collection-day/Default.aspx"
41
+
42
+ # Build initial ASP.NET variables for Postcode Find address
43
+ response = session.get(URL)
44
+ response.raise_for_status()
45
+ soup = BeautifulSoup(response.text, "html.parser")
46
+ form_data = {
47
+ "__EVENTTARGET": "",
48
+ "__EVENTARGUMENT": "",
49
+ "__VIEWSTATE": self.get_session_variable(soup, "__VIEWSTATE"),
50
+ "__VIEWSTATEGENERATOR": self.get_session_variable(soup, "__VIEWSTATEGENERATOR"),
51
+ "__SCROLLPOSITIONX": "0",
52
+ "__SCROLLPOSITIONY": "0",
53
+ "__EVENTVALIDATION": self.get_session_variable(soup, "__EVENTVALIDATION"),
54
+ "ctl00$MainContent$searchBy_radio": "P",
55
+ "ctl00$MainContent$Street_textbox": "",
56
+ "ctl00$MainContent$Postcode_textbox": user_postcode,
57
+ "ctl00$MainContent$AddressLookup_button": "Find address"
58
+ }
59
+
60
+ # Build intermediate ASP.NET variables for uprn Select address
61
+ response = session.post(URL, data=form_data)
62
+ response.raise_for_status()
63
+ soup = BeautifulSoup(response.text, "html.parser")
64
+ form_data = {
65
+ "__EVENTTARGET": "",
66
+ "__EVENTARGUMENT": "",
67
+ "__VIEWSTATE": self.get_session_variable(soup, "__VIEWSTATE"),
68
+ "__VIEWSTATEGENERATOR": self.get_session_variable(soup, "__VIEWSTATEGENERATOR"),
69
+ "__SCROLLPOSITIONX": "0",
70
+ "__SCROLLPOSITIONY": "0",
71
+ "__EVENTVALIDATION": self.get_session_variable(soup, "__EVENTVALIDATION"),
72
+ "ctl00$MainContent$searchBy_radio": "P",
73
+ "ctl00$MainContent$Street_textbox": "",
74
+ "ctl00$MainContent$Postcode_textbox": user_postcode,
75
+ "ctl00$MainContent$lstAddresses": user_uprn,
76
+ "ctl00$MainContent$SelectAddress_button": "Select address"
77
+ }
78
+
79
+ # Actual http call to get Bins Data
80
+ response = session.post(URL, data=form_data)
81
+ response.raise_for_status()
82
+ soup = BeautifulSoup(response.text, "html.parser")
83
+
84
+ # Find Bins table and data
85
+ table = soup.find("div", {"id": "binsGrid"})
86
+ if table:
87
+ rows = table.find_all("tr")
88
+ for row in rows:
89
+ columns = row.find_all("td")
90
+ if len(columns) >= 4:
91
+ collection_type = columns[0].get_text(strip=True)
92
+ collection_date_raw = columns[3].get_text(strip=True)
93
+ # 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')
95
+ bin_entry = {
96
+ "type": collection_type,
97
+ "collectionDate": collection_date.strftime(date_format),
98
+ }
99
+ bin_data["bins"].append(bin_entry)
100
+ return bin_data
@@ -0,0 +1,156 @@
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
+ import pdb
8
+
9
+ from uk_bin_collection.uk_bin_collection.common import *
10
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
11
+
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_postcode = kwargs.get("postcode")
25
+ if not user_postcode:
26
+ raise ValueError("No postcode provided.")
27
+ check_postcode(user_postcode)
28
+
29
+ user_paon = kwargs.get("paon")
30
+ check_paon(user_paon)
31
+ headless = kwargs.get("headless")
32
+ web_driver = kwargs.get("web_driver")
33
+ driver = create_webdriver(web_driver, headless, None, __name__)
34
+ page = "https://www.enfield.gov.uk/services/rubbish-and-recycling/find-my-collection-day"
35
+ driver.get(page)
36
+
37
+ time.sleep(5)
38
+
39
+ try:
40
+ accept_cookies = WebDriverWait(driver, timeout=10).until(
41
+ EC.presence_of_element_located((By.ID, "ccc-notify-reject"))
42
+ )
43
+ accept_cookies.click()
44
+ except:
45
+ print(
46
+ "Accept cookies banner not found or clickable within the specified time."
47
+ )
48
+ pass
49
+
50
+ postcode_input = WebDriverWait(driver, 10).until(
51
+ EC.presence_of_element_located(
52
+ (By.CSS_SELECTOR, '[aria-label="Enter your address"]')
53
+ )
54
+ )
55
+
56
+ postcode_input.send_keys(user_postcode)
57
+
58
+ find_address_button = WebDriverWait(driver, 10).until(
59
+ EC.presence_of_element_located(
60
+ (By.ID, 'submitButton0')
61
+ )
62
+ )
63
+ find_address_button.click()
64
+
65
+ time.sleep(15)
66
+ # Wait for address box to be visible
67
+ select_address_input = WebDriverWait(driver, 10).until(
68
+ EC.presence_of_element_located(
69
+ (
70
+ By.CSS_SELECTOR,
71
+ '[aria-label="Select full address"]',
72
+ )
73
+ )
74
+ )
75
+
76
+ # Select address based
77
+ select = Select(select_address_input)
78
+ # Grab the first option as a template
79
+ first_option = select.options[0].accessible_name
80
+ template_parts = first_option.split(", ")
81
+ template_parts[0] = user_paon # Replace the first part with user_paon
82
+
83
+ addr_label = ", ".join(template_parts)
84
+ for addr_option in select.options:
85
+ option_name = addr_option.accessible_name[0 : len(addr_label)]
86
+ if option_name == addr_label:
87
+ break
88
+ select.select_by_value(addr_option.text)
89
+
90
+ time.sleep(10)
91
+ # Wait for the specified div to be present
92
+ target_div_id = "FinalResults"
93
+ target_div = WebDriverWait(driver, 10).until(
94
+ EC.presence_of_element_located((By.ID, target_div_id))
95
+ )
96
+
97
+ time.sleep(5)
98
+ soup = BeautifulSoup(driver.page_source, "html.parser")
99
+
100
+ # Find the div with the specified id
101
+ target_div = soup.find("div", {"id": target_div_id})
102
+
103
+
104
+ # Check if the div is found
105
+ if target_div:
106
+ bin_data = {"bins": []}
107
+
108
+ for bin_div in target_div.find_all(
109
+ "div"
110
+ ):
111
+ # Extract the collection date from the message
112
+ try:
113
+ bin_collection_message = bin_div.find("p").text.strip()
114
+ date_pattern = r"\b\d{2}/\d{2}/\d{4}\b"
115
+
116
+ collection_date_string = (
117
+ re.search(date_pattern, bin_div.text)
118
+ .group(0)
119
+ .strip()
120
+ .replace(",", "")
121
+ )
122
+ except AttributeError:
123
+ continue
124
+
125
+ current_date = datetime.now()
126
+ parsed_date = datetime.strptime(
127
+ collection_date_string, "%d/%m/%Y"
128
+ )
129
+ # Check if the parsed date is in the past and not today
130
+ if parsed_date.date() < current_date.date():
131
+ # If so, set the year to the next year
132
+ parsed_date = parsed_date.replace(year=current_date.year + 1)
133
+ else:
134
+ # If not, set the year to the current year
135
+ parsed_date = parsed_date.replace(year=current_date.year)
136
+ formatted_date = parsed_date.strftime("%d/%m/%Y")
137
+ contains_date(formatted_date)
138
+
139
+ # Extract the bin type from the message
140
+ bin_type_match = re.search(r"Your next (.*?) collection", bin_collection_message)
141
+ if bin_type_match:
142
+ bin_info = {"type": bin_type_match.group(1), "collectionDate": formatted_date}
143
+ bin_data["bins"].append(bin_info)
144
+ else:
145
+ raise ValueError("Collection data not found.")
146
+
147
+ except Exception as e:
148
+ # Here you can log the exception if needed
149
+ print(f"An error occurred: {e}")
150
+ # Optionally, re-raise the exception if you want it to propagate
151
+ raise
152
+ finally:
153
+ # This block ensures that the driver is closed regardless of an exception
154
+ if driver:
155
+ driver.quit()
156
+ return bin_data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uk_bin_collection
3
- Version: 0.84.2
3
+ Version: 0.85.1
4
4
  Summary: Python Lib to collect UK Bin Data
5
5
  Author: Robert Bradley
6
6
  Author-email: robbrad182@gmail.com
@@ -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=OVMlr2sTqDMgQGlSgTQ1_LeuxXpVgXPvqRb_zGV95r8,59090
5
+ uk_bin_collection/tests/input.json,sha256=zEy759soyIGIG4vmYmIbKloY_kH4R0CDeEFT7ToWRRs,59654
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
@@ -20,6 +20,7 @@ uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py,sha256=VPWGlj
20
20
  uk_bin_collection/uk_bin_collection/councils/BathAndNorthEastSomersetCouncil.py,sha256=N_TPiIv8VBzN3rY0p3JtLlxSEru-6k1wW4UNIhN5X1M,3709
21
21
  uk_bin_collection/uk_bin_collection/councils/BedfordBoroughCouncil.py,sha256=CvGB7w9HMn7XyEtwfd9MWZE_HlZ75pDcaKMsQJz0xhk,1669
22
22
  uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py,sha256=U1HOr9YLMAlFoZysfw5n04E0bVuCliO5Yj1FMiiwcHA,2549
23
+ uk_bin_collection/uk_bin_collection/councils/BelfastCityCouncil.py,sha256=mspYVHO8fgoVIwogT6V2Go1tbf3PDbEmr8kZMJug5Gs,4235
23
24
  uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py,sha256=9MrbpfR17R6DYjDrItQL3CPF7ie1SJkaoNbwQumPgIQ,5399
24
25
  uk_bin_collection/uk_bin_collection/councils/BirminghamCityCouncil.py,sha256=yrO9dnSMdqVTcrzqXXfexFxSuESW01VPxXNAl0_lNOU,4669
25
26
  uk_bin_collection/uk_bin_collection/councils/BlackburnCouncil.py,sha256=jHbCK8sL09vdmdP7Xnh8lIrU5AHTnJLEZfOLephPvWg,4090
@@ -58,6 +59,7 @@ uk_bin_collection/uk_bin_collection/councils/EastLindseyDistrictCouncil.py,sha25
58
59
  uk_bin_collection/uk_bin_collection/councils/EastRidingCouncil.py,sha256=CsYdkmL-8Ty-Kz7uNdlnJnhiDMgOPah_swYgSKbaFqA,5218
59
60
  uk_bin_collection/uk_bin_collection/councils/EastSuffolkCouncil.py,sha256=qQ0oOfGd0sWcczse_B22YoeL9uj3og8v3UJLt_Sx29c,4353
60
61
  uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py,sha256=V4Vso4DvawFiezKlmXbTlJEK9Sjhz9nA8WeYjwtO2e4,2310
62
+ uk_bin_collection/uk_bin_collection/councils/EnfieldCouncil.py,sha256=HhKHlLciZKXViqcgkWme-wBUKlGhAs5LIpkKuRETvXM,6119
61
63
  uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py,sha256=_9QJYDHpdnYK5R6znvZk1w0F9GnPnI8G4b6I_p26h4U,1695
62
64
  uk_bin_collection/uk_bin_collection/councils/EppingForestDistrictCouncil.py,sha256=cKFllQ4zt6MGkwiz_HedZvw3iL1kRMLA6Ct2spUE5og,2085
63
65
  uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py,sha256=QTQA6NjZtTL2baDeerIQW1SQpawwu6kGDMGdVvYQRRo,2501
@@ -182,8 +184,8 @@ uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py,sha256=I2kBYMlsD4bId
182
184
  uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py,sha256=4s9ODGPAwPqwXc8SrTX5Wlfmizs3_58iXUtHc4Ir86o,1162
183
185
  uk_bin_collection/uk_bin_collection/create_new_council.py,sha256=m-IhmWmeWQlFsTZC4OxuFvtw5ZtB8EAJHxJTH4O59lQ,1536
184
186
  uk_bin_collection/uk_bin_collection/get_bin_data.py,sha256=YvmHfZqanwrJ8ToGch34x-L-7yPe31nB_x77_Mgl_vo,4545
185
- uk_bin_collection-0.84.2.dist-info/LICENSE,sha256=vABBUOzcrgfaTKpzeo-si9YVEun6juDkndqA8RKdKGs,1071
186
- uk_bin_collection-0.84.2.dist-info/METADATA,sha256=WnYh3FJj-AW2JCTk2tiFdDsudviMFab4iitJydrC94A,16231
187
- uk_bin_collection-0.84.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
188
- uk_bin_collection-0.84.2.dist-info/entry_points.txt,sha256=36WCSGMWSc916S3Hi1ZkazzDKHaJ6CD-4fCEFm5MIao,90
189
- uk_bin_collection-0.84.2.dist-info/RECORD,,
187
+ uk_bin_collection-0.85.1.dist-info/LICENSE,sha256=vABBUOzcrgfaTKpzeo-si9YVEun6juDkndqA8RKdKGs,1071
188
+ uk_bin_collection-0.85.1.dist-info/METADATA,sha256=SdozpBmBLib8V-KxdrKhdkBe-FH3-mkckz0woby8UCg,16231
189
+ uk_bin_collection-0.85.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
190
+ uk_bin_collection-0.85.1.dist-info/entry_points.txt,sha256=36WCSGMWSc916S3Hi1ZkazzDKHaJ6CD-4fCEFm5MIao,90
191
+ uk_bin_collection-0.85.1.dist-info/RECORD,,