uk_bin_collection 0.140.0__py3-none-any.whl → 0.141.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.
@@ -1493,6 +1493,15 @@
1493
1493
  "wiki_name": "Oxford City Council",
1494
1494
  "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
1495
1495
  },
1496
+ "PeterboroughCityCouncil": {
1497
+ "house_number": "7 Arundel Road, Peterborough, PE4 6JJ",
1498
+ "postcode": "PE4 6JJ",
1499
+ "skip_get_url": true,
1500
+ "url": "https://report.peterborough.gov.uk/waste",
1501
+ "web_driver": "http://selenium:4444",
1502
+ "wiki_name": "Peterborough City Council",
1503
+ "wiki_note": "Pass the full address as it appears o nthe Peterborough website and postcode in their respective parameters. This parser requires a Selenium webdriver."
1504
+ },
1496
1505
  "PerthAndKinrossCouncil": {
1497
1506
  "uprn": "124032322",
1498
1507
  "url": "https://www.pkc.gov.uk",
@@ -0,0 +1,167 @@
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
+
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
+ # import the wonderful Beautiful Soup and the URL grabber
13
+ class CouncilClass(AbstractGetBinDataClass):
14
+ """
15
+ Concrete classes have to implement all abstract operations of the
16
+ base class. They can also override some operations with a default
17
+ implementation.
18
+ """
19
+
20
+ def parse_data(self, page: str, **kwargs) -> dict:
21
+ driver = None
22
+ try:
23
+ user_poan = kwargs.get("paon")
24
+ user_postcode = kwargs.get("postcode")
25
+ if not user_postcode:
26
+ raise ValueError("No postcode provided.")
27
+ check_postcode(user_postcode)
28
+
29
+ headless = kwargs.get("headless")
30
+ web_driver = kwargs.get("web_driver")
31
+ driver = create_webdriver(web_driver, headless, None, __name__)
32
+ page = "https://report.peterborough.gov.uk/waste"
33
+
34
+ driver.get(page)
35
+
36
+ wait = WebDriverWait(driver, 30)
37
+
38
+ try:
39
+ # Cookies confirmed working in selenium
40
+ accept_cookies_button = wait.until(
41
+ EC.element_to_be_clickable(
42
+ (
43
+ By.XPATH,
44
+ "//button/span[contains(text(), 'I Accept Cookies')]",
45
+ )
46
+ )
47
+ )
48
+ accept_cookies_button.click()
49
+ except:
50
+ print(
51
+ "Accept cookies banner not found or clickable within the specified time."
52
+ )
53
+ pass
54
+
55
+ postcode_input = wait.until(
56
+ EC.presence_of_element_located((By.XPATH, '//input[@id="postcode"]'))
57
+ )
58
+
59
+ postcode_input.send_keys(user_postcode)
60
+
61
+ postcode_go_button = wait.until(
62
+ EC.element_to_be_clickable((By.XPATH, '//input[@id="go"]'))
63
+ )
64
+
65
+ postcode_go_button.click()
66
+
67
+ # Wait for the select address drop down to be present
68
+ select_address_input = wait.until(
69
+ EC.presence_of_element_located((By.XPATH, '//input[@id="address"]'))
70
+ )
71
+
72
+ select_address_input.click()
73
+ time.sleep(2)
74
+
75
+ select_address_input_item = wait.until(
76
+ EC.presence_of_element_located(
77
+ (By.XPATH, f"//li[contains(text(), '{user_poan}')]")
78
+ )
79
+ )
80
+
81
+ select_address_input_item.click()
82
+
83
+ address_continue_button = wait.until(
84
+ EC.element_to_be_clickable((By.XPATH, '//input[@value="Continue"]'))
85
+ )
86
+
87
+ address_continue_button.click()
88
+
89
+ your_collections_heading = wait.until(
90
+ EC.presence_of_element_located(
91
+ (By.XPATH, "//h2[contains(text(), 'Your collections')]")
92
+ )
93
+ )
94
+
95
+ results_page = wait.until(
96
+ EC.presence_of_element_located(
97
+ (By.XPATH, "//div[@class='waste__collections']")
98
+ )
99
+ )
100
+
101
+ soup = BeautifulSoup(results_page.get_attribute("innerHTML"), "html.parser")
102
+
103
+ data = {"bins": []}
104
+ output_date_format = "%d/%m/%Y"
105
+ input_date_format = "%A, %d %B %Y" # Expect: Thursday, 17 April 2025
106
+
107
+ # Each bin section is within a waste-service-wrapper div
108
+ collection_panels = soup.find_all("div", class_="waste-service-wrapper")
109
+
110
+ for panel in collection_panels:
111
+ try:
112
+ # Bin type
113
+ bin_type_tag = panel.find("h3", class_="waste-service-name")
114
+ if not bin_type_tag:
115
+ continue
116
+ bin_type = bin_type_tag.get_text(strip=True)
117
+
118
+ # Get 'Next collection' date
119
+ rows = panel.find_all("div", class_="govuk-summary-list__row")
120
+ next_collection = None
121
+ for row in rows:
122
+ key = row.find("dt", class_="govuk-summary-list__key")
123
+ value = row.find("dd", class_="govuk-summary-list__value")
124
+ if key and value and "Next collection" in key.get_text():
125
+ raw_date = " ".join(value.get_text().split())
126
+
127
+ # ✅ Remove st/nd/rd/th suffix from the day (e.g. 17th → 17)
128
+ cleaned_date = re.sub(
129
+ r"(\d{1,2})(st|nd|rd|th)", r"\1", raw_date
130
+ )
131
+ next_collection = cleaned_date
132
+ break
133
+
134
+ if not next_collection:
135
+ continue
136
+
137
+ print(f"Found next collection for {bin_type}: '{next_collection}'")
138
+
139
+ parsed_date = datetime.strptime(next_collection, input_date_format)
140
+ formatted_date = parsed_date.strftime(output_date_format)
141
+
142
+ data["bins"].append(
143
+ {
144
+ "type": bin_type,
145
+ "collectionDate": formatted_date,
146
+ }
147
+ )
148
+
149
+ except Exception as e:
150
+ print(
151
+ f"Error processing panel for bin '{bin_type if 'bin_type' in locals() else 'unknown'}': {e}"
152
+ )
153
+
154
+ # Sort the data
155
+ data["bins"].sort(
156
+ key=lambda x: datetime.strptime(x["collectionDate"], output_date_format)
157
+ )
158
+ except Exception as e:
159
+ # Here you can log the exception if needed
160
+ print(f"An error occurred: {e}")
161
+ # Optionally, re-raise the exception if you want it to propagate
162
+ raise
163
+ finally:
164
+ # This block ensures that the driver is closed regardless of an exception
165
+ if driver:
166
+ driver.quit()
167
+ return data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uk_bin_collection
3
- Version: 0.140.0
3
+ Version: 0.141.0
4
4
  Summary: Python Lib to collect UK Bin Data
5
5
  Author: Robert Bradley
6
6
  Author-email: robbrad182@gmail.com
@@ -3,7 +3,7 @@ uk_bin_collection/tests/check_selenium_url_in_input.json.py,sha256=Iecdja0I3XIiY
3
3
  uk_bin_collection/tests/council_feature_input_parity.py,sha256=DO6Mk4ImYgM5ZCZ-cutwz5RoYYWZRLYx2tr6zIs_9Rc,3843
4
4
  uk_bin_collection/tests/features/environment.py,sha256=VQZjJdJI_kZn08M0j5cUgvKT4k3iTw8icJge1DGOkoA,127
5
5
  uk_bin_collection/tests/features/validate_council_outputs.feature,sha256=SJK-Vc737hrf03tssxxbeg_JIvAH-ddB8f6gU1LTbuQ,251
6
- uk_bin_collection/tests/input.json,sha256=b6MsNMMQbs1-lW6g1_dYz5WTiwt8Puuu3q_BEopiulk,120060
6
+ uk_bin_collection/tests/input.json,sha256=uvi5_CrjVy26H4gkWdoRXCJ1wsJPgntzJB26hXwC5jI,120556
7
7
  uk_bin_collection/tests/output.schema,sha256=ZwKQBwYyTDEM4G2hJwfLUVM-5v1vKRvRK9W9SS1sd18,1086
8
8
  uk_bin_collection/tests/step_defs/step_helpers/file_handler.py,sha256=Ygzi4V0S1MIHqbdstUlIqtRIwnynvhu4UtpweJ6-5N8,1474
9
9
  uk_bin_collection/tests/step_defs/test_validate_council.py,sha256=VZ0a81sioJULD7syAYHjvK_-nT_Rd36tUyzPetSA0gk,3475
@@ -216,6 +216,7 @@ uk_bin_collection/uk_bin_collection/councils/OadbyAndWigstonBoroughCouncil.py,sh
216
216
  uk_bin_collection/uk_bin_collection/councils/OldhamCouncil.py,sha256=9dlesCxNoVXlmQaqZj7QFh00smnJbm1Gnjkr_Uvzurs,1771
217
217
  uk_bin_collection/uk_bin_collection/councils/OxfordCityCouncil.py,sha256=d_bY0cXRDH4kSoWGGCTNN61MNErapSOf2WSTYDJr2r8,2318
218
218
  uk_bin_collection/uk_bin_collection/councils/PerthAndKinrossCouncil.py,sha256=Kos5GzN2co3Ij3tSHOXB9S71Yt78RROCfVRtnh7M1VU,3657
219
+ uk_bin_collection/uk_bin_collection/councils/PeterboroughCityCouncil.py,sha256=lOrDD4jfJ-_C5UwCGqRcQ1G-U1F5X6rf255ypzYEBcg,6300
219
220
  uk_bin_collection/uk_bin_collection/councils/PlymouthCouncil.py,sha256=FJqpJ0GJhpjYeyZ9ioZPkKGl-zrqMD3y5iKa07e_i30,3202
220
221
  uk_bin_collection/uk_bin_collection/councils/PortsmouthCityCouncil.py,sha256=xogNgVvwM5FljCziiNLgZ_wzkOnrQkifi1dkPMDRMtg,5588
221
222
  uk_bin_collection/uk_bin_collection/councils/PowysCouncil.py,sha256=db3Y5FJz-LFDqmVZqPdzcBxh0Q26OFPrbUxlQ7r4vsQ,5896
@@ -325,8 +326,8 @@ uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py,sha256=I2kBYMlsD4bId
325
326
  uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py,sha256=EQWRhZ2pEejlvm0fPyOTsOHKvUZmPnxEYO_OWRGKTjs,1158
326
327
  uk_bin_collection/uk_bin_collection/create_new_council.py,sha256=m-IhmWmeWQlFsTZC4OxuFvtw5ZtB8EAJHxJTH4O59lQ,1536
327
328
  uk_bin_collection/uk_bin_collection/get_bin_data.py,sha256=YvmHfZqanwrJ8ToGch34x-L-7yPe31nB_x77_Mgl_vo,4545
328
- uk_bin_collection-0.140.0.dist-info/LICENSE,sha256=vABBUOzcrgfaTKpzeo-si9YVEun6juDkndqA8RKdKGs,1071
329
- uk_bin_collection-0.140.0.dist-info/METADATA,sha256=3Dw3sim4P-LwGOmRbcX7jSK15SFpT16G2lTDl_wcuj8,19851
330
- uk_bin_collection-0.140.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
331
- uk_bin_collection-0.140.0.dist-info/entry_points.txt,sha256=36WCSGMWSc916S3Hi1ZkazzDKHaJ6CD-4fCEFm5MIao,90
332
- uk_bin_collection-0.140.0.dist-info/RECORD,,
329
+ uk_bin_collection-0.141.0.dist-info/LICENSE,sha256=vABBUOzcrgfaTKpzeo-si9YVEun6juDkndqA8RKdKGs,1071
330
+ uk_bin_collection-0.141.0.dist-info/METADATA,sha256=wkZzAu4PUwGpBcnep3fvKUVjYSPYKVf4_wlShHwzfXM,19851
331
+ uk_bin_collection-0.141.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
332
+ uk_bin_collection-0.141.0.dist-info/entry_points.txt,sha256=36WCSGMWSc916S3Hi1ZkazzDKHaJ6CD-4fCEFm5MIao,90
333
+ uk_bin_collection-0.141.0.dist-info/RECORD,,