uk_bin_collection 0.74.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 (171) hide show
  1. uk_bin_collection/README.rst +0 -0
  2. uk_bin_collection/tests/council_feature_input_parity.py +79 -0
  3. uk_bin_collection/tests/features/environment.py +7 -0
  4. uk_bin_collection/tests/features/validate_council_outputs.feature +767 -0
  5. uk_bin_collection/tests/input.json +1077 -0
  6. uk_bin_collection/tests/output.schema +41 -0
  7. uk_bin_collection/tests/step_defs/step_helpers/file_handler.py +46 -0
  8. uk_bin_collection/tests/step_defs/test_validate_council.py +87 -0
  9. uk_bin_collection/tests/test_collect_data.py +104 -0
  10. uk_bin_collection/tests/test_common_functions.py +342 -0
  11. uk_bin_collection/uk_bin_collection/collect_data.py +133 -0
  12. uk_bin_collection/uk_bin_collection/common.py +292 -0
  13. uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py +43 -0
  14. uk_bin_collection/uk_bin_collection/councils/ArunCouncil.py +97 -0
  15. uk_bin_collection/uk_bin_collection/councils/AylesburyValeCouncil.py +69 -0
  16. uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py +51 -0
  17. uk_bin_collection/uk_bin_collection/councils/BarnetCouncil.py +180 -0
  18. uk_bin_collection/uk_bin_collection/councils/BarnsleyMBCouncil.py +109 -0
  19. uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py +72 -0
  20. uk_bin_collection/uk_bin_collection/councils/BathAndNorthEastSomersetCouncil.py +100 -0
  21. uk_bin_collection/uk_bin_collection/councils/BedfordBoroughCouncil.py +49 -0
  22. uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py +70 -0
  23. uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py +147 -0
  24. uk_bin_collection/uk_bin_collection/councils/BirminghamCityCouncil.py +119 -0
  25. uk_bin_collection/uk_bin_collection/councils/BlackburnCouncil.py +105 -0
  26. uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py +104 -0
  27. uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py +103 -0
  28. uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py +137 -0
  29. uk_bin_collection/uk_bin_collection/councils/BristolCityCouncil.py +141 -0
  30. uk_bin_collection/uk_bin_collection/councils/BromleyBoroughCouncil.py +115 -0
  31. uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py +107 -0
  32. uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py +95 -0
  33. uk_bin_collection/uk_bin_collection/councils/BuryCouncil.py +65 -0
  34. uk_bin_collection/uk_bin_collection/councils/CalderdaleCouncil.py +123 -0
  35. uk_bin_collection/uk_bin_collection/councils/CannockChaseDistrictCouncil.py +65 -0
  36. uk_bin_collection/uk_bin_collection/councils/CardiffCouncil.py +172 -0
  37. uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py +96 -0
  38. uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py +54 -0
  39. uk_bin_collection/uk_bin_collection/councils/ChelmsfordCityCouncil.py +127 -0
  40. uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py +32 -0
  41. uk_bin_collection/uk_bin_collection/councils/CheshireWestAndChesterCouncil.py +125 -0
  42. uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py +134 -0
  43. uk_bin_collection/uk_bin_collection/councils/ConwyCountyBorough.py +27 -0
  44. uk_bin_collection/uk_bin_collection/councils/CrawleyBoroughCouncil.py +61 -0
  45. uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py +291 -0
  46. uk_bin_collection/uk_bin_collection/councils/DerbyshireDalesDistrictCouncil.py +100 -0
  47. uk_bin_collection/uk_bin_collection/councils/DoncasterCouncil.py +77 -0
  48. uk_bin_collection/uk_bin_collection/councils/DorsetCouncil.py +58 -0
  49. uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py +41 -0
  50. uk_bin_collection/uk_bin_collection/councils/DurhamCouncil.py +49 -0
  51. uk_bin_collection/uk_bin_collection/councils/EastCambridgeshireCouncil.py +44 -0
  52. uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py +74 -0
  53. uk_bin_collection/uk_bin_collection/councils/EastLindseyDistrictCouncil.py +108 -0
  54. uk_bin_collection/uk_bin_collection/councils/EastRidingCouncil.py +142 -0
  55. uk_bin_collection/uk_bin_collection/councils/EastSuffolkCouncil.py +112 -0
  56. uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py +70 -0
  57. uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py +48 -0
  58. uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py +61 -0
  59. uk_bin_collection/uk_bin_collection/councils/FenlandDistrictCouncil.py +65 -0
  60. uk_bin_collection/uk_bin_collection/councils/ForestOfDeanDistrictCouncil.py +113 -0
  61. uk_bin_collection/uk_bin_collection/councils/GatesheadCouncil.py +118 -0
  62. uk_bin_collection/uk_bin_collection/councils/GedlingBoroughCouncil.py +1580 -0
  63. uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py +55 -0
  64. uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py +150 -0
  65. uk_bin_collection/uk_bin_collection/councils/HaltonBoroughCouncil.py +142 -0
  66. uk_bin_collection/uk_bin_collection/councils/HaringeyCouncil.py +59 -0
  67. uk_bin_collection/uk_bin_collection/councils/HarrogateBoroughCouncil.py +63 -0
  68. uk_bin_collection/uk_bin_collection/councils/HighPeakCouncil.py +134 -0
  69. uk_bin_collection/uk_bin_collection/councils/HullCityCouncil.py +48 -0
  70. uk_bin_collection/uk_bin_collection/councils/HuntingdonDistrictCouncil.py +44 -0
  71. uk_bin_collection/uk_bin_collection/councils/KingstonUponThamesCouncil.py +84 -0
  72. uk_bin_collection/uk_bin_collection/councils/KirkleesCouncil.py +130 -0
  73. uk_bin_collection/uk_bin_collection/councils/KnowsleyMBCouncil.py +139 -0
  74. uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py +71 -0
  75. uk_bin_collection/uk_bin_collection/councils/LeedsCityCouncil.py +137 -0
  76. uk_bin_collection/uk_bin_collection/councils/LisburnCastlereaghCityCouncil.py +101 -0
  77. uk_bin_collection/uk_bin_collection/councils/LiverpoolCityCouncil.py +65 -0
  78. uk_bin_collection/uk_bin_collection/councils/LondonBoroughHounslow.py +82 -0
  79. uk_bin_collection/uk_bin_collection/councils/LondonBoroughRedbridge.py +161 -0
  80. uk_bin_collection/uk_bin_collection/councils/MaldonDistrictCouncil.py +52 -0
  81. uk_bin_collection/uk_bin_collection/councils/MalvernHillsDC.py +57 -0
  82. uk_bin_collection/uk_bin_collection/councils/ManchesterCityCouncil.py +106 -0
  83. uk_bin_collection/uk_bin_collection/councils/MansfieldDistrictCouncil.py +38 -0
  84. uk_bin_collection/uk_bin_collection/councils/MertonCouncil.py +58 -0
  85. uk_bin_collection/uk_bin_collection/councils/MidAndEastAntrimBoroughCouncil.py +128 -0
  86. uk_bin_collection/uk_bin_collection/councils/MidSussexDistrictCouncil.py +80 -0
  87. uk_bin_collection/uk_bin_collection/councils/MiltonKeynesCityCouncil.py +54 -0
  88. uk_bin_collection/uk_bin_collection/councils/MoleValleyDistrictCouncil.py +98 -0
  89. uk_bin_collection/uk_bin_collection/councils/NeathPortTalbotCouncil.py +139 -0
  90. uk_bin_collection/uk_bin_collection/councils/NewarkAndSherwoodDC.py +52 -0
  91. uk_bin_collection/uk_bin_collection/councils/NewcastleCityCouncil.py +57 -0
  92. uk_bin_collection/uk_bin_collection/councils/NewhamCouncil.py +58 -0
  93. uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py +203 -0
  94. uk_bin_collection/uk_bin_collection/councils/NorthEastDerbyshireDistrictCouncil.py +115 -0
  95. uk_bin_collection/uk_bin_collection/councils/NorthEastLincs.py +53 -0
  96. uk_bin_collection/uk_bin_collection/councils/NorthKestevenDistrictCouncil.py +45 -0
  97. uk_bin_collection/uk_bin_collection/councils/NorthLanarkshireCouncil.py +46 -0
  98. uk_bin_collection/uk_bin_collection/councils/NorthLincolnshireCouncil.py +58 -0
  99. uk_bin_collection/uk_bin_collection/councils/NorthNorfolkDistrictCouncil.py +108 -0
  100. uk_bin_collection/uk_bin_collection/councils/NorthNorthamptonshireCouncil.py +72 -0
  101. uk_bin_collection/uk_bin_collection/councils/NorthSomersetCouncil.py +76 -0
  102. uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py +220 -0
  103. uk_bin_collection/uk_bin_collection/councils/NorthWestLeicestershire.py +114 -0
  104. uk_bin_collection/uk_bin_collection/councils/NorthYorkshire.py +58 -0
  105. uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +123 -0
  106. uk_bin_collection/uk_bin_collection/councils/NottinghamCityCouncil.py +36 -0
  107. uk_bin_collection/uk_bin_collection/councils/OldhamCouncil.py +51 -0
  108. uk_bin_collection/uk_bin_collection/councils/PortsmouthCityCouncil.py +131 -0
  109. uk_bin_collection/uk_bin_collection/councils/PrestonCityCouncil.py +97 -0
  110. uk_bin_collection/uk_bin_collection/councils/ReadingBoroughCouncil.py +30 -0
  111. uk_bin_collection/uk_bin_collection/councils/ReigateAndBansteadBoroughCouncil.py +81 -0
  112. uk_bin_collection/uk_bin_collection/councils/RenfrewshireCouncil.py +135 -0
  113. uk_bin_collection/uk_bin_collection/councils/RhonddaCynonTaffCouncil.py +80 -0
  114. uk_bin_collection/uk_bin_collection/councils/RochdaleCouncil.py +69 -0
  115. uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py +60 -0
  116. uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py +93 -0
  117. uk_bin_collection/uk_bin_collection/councils/RushcliffeBoroughCouncil.py +100 -0
  118. uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py +81 -0
  119. uk_bin_collection/uk_bin_collection/councils/SalfordCityCouncil.py +70 -0
  120. uk_bin_collection/uk_bin_collection/councils/SevenoaksDistrictCouncil.py +106 -0
  121. uk_bin_collection/uk_bin_collection/councils/SheffieldCityCouncil.py +54 -0
  122. uk_bin_collection/uk_bin_collection/councils/ShropshireCouncil.py +45 -0
  123. uk_bin_collection/uk_bin_collection/councils/SolihullCouncil.py +48 -0
  124. uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py +203 -0
  125. uk_bin_collection/uk_bin_collection/councils/SouthAyrshireCouncil.py +73 -0
  126. uk_bin_collection/uk_bin_collection/councils/SouthCambridgeshireCouncil.py +65 -0
  127. uk_bin_collection/uk_bin_collection/councils/SouthGloucestershireCouncil.py +74 -0
  128. uk_bin_collection/uk_bin_collection/councils/SouthLanarkshireCouncil.py +78 -0
  129. uk_bin_collection/uk_bin_collection/councils/SouthNorfolkCouncil.py +91 -0
  130. uk_bin_collection/uk_bin_collection/councils/SouthOxfordshireCouncil.py +93 -0
  131. uk_bin_collection/uk_bin_collection/councils/SouthTynesideCouncil.py +98 -0
  132. uk_bin_collection/uk_bin_collection/councils/StAlbansCityAndDistrictCouncil.py +43 -0
  133. uk_bin_collection/uk_bin_collection/councils/StHelensBC.py +56 -0
  134. uk_bin_collection/uk_bin_collection/councils/StaffordshireMoorlandsDistrictCouncil.py +112 -0
  135. uk_bin_collection/uk_bin_collection/councils/StockportBoroughCouncil.py +39 -0
  136. uk_bin_collection/uk_bin_collection/councils/StokeOnTrentCityCouncil.py +79 -0
  137. uk_bin_collection/uk_bin_collection/councils/StratfordUponAvonCouncil.py +94 -0
  138. uk_bin_collection/uk_bin_collection/councils/SunderlandCityCouncil.py +100 -0
  139. uk_bin_collection/uk_bin_collection/councils/SwaleBoroughCouncil.py +52 -0
  140. uk_bin_collection/uk_bin_collection/councils/TamesideMBCouncil.py +62 -0
  141. uk_bin_collection/uk_bin_collection/councils/TandridgeDistrictCouncil.py +60 -0
  142. uk_bin_collection/uk_bin_collection/councils/TelfordAndWrekinCouncil.py +50 -0
  143. uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py +203 -0
  144. uk_bin_collection/uk_bin_collection/councils/TonbridgeAndMallingBC.py +101 -0
  145. uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py +51 -0
  146. uk_bin_collection/uk_bin_collection/councils/TorridgeDistrictCouncil.py +154 -0
  147. uk_bin_collection/uk_bin_collection/councils/ValeofGlamorganCouncil.py +119 -0
  148. uk_bin_collection/uk_bin_collection/councils/ValeofWhiteHorseCouncil.py +103 -0
  149. uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py +89 -0
  150. uk_bin_collection/uk_bin_collection/councils/WarwickDistrictCouncil.py +34 -0
  151. uk_bin_collection/uk_bin_collection/councils/WaverleyBoroughCouncil.py +119 -0
  152. uk_bin_collection/uk_bin_collection/councils/WealdenDistrictCouncil.py +86 -0
  153. uk_bin_collection/uk_bin_collection/councils/WelhatCouncil.py +73 -0
  154. uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py +134 -0
  155. uk_bin_collection/uk_bin_collection/councils/WestLindseyDistrictCouncil.py +118 -0
  156. uk_bin_collection/uk_bin_collection/councils/WestLothianCouncil.py +103 -0
  157. uk_bin_collection/uk_bin_collection/councils/WestNorthamptonshireCouncil.py +34 -0
  158. uk_bin_collection/uk_bin_collection/councils/WestSuffolkCouncil.py +64 -0
  159. uk_bin_collection/uk_bin_collection/councils/WiganBoroughCouncil.py +97 -0
  160. uk_bin_collection/uk_bin_collection/councils/WiltshireCouncil.py +135 -0
  161. uk_bin_collection/uk_bin_collection/councils/WindsorAndMaidenheadCouncil.py +134 -0
  162. uk_bin_collection/uk_bin_collection/councils/WokingBoroughCouncil.py +114 -0
  163. uk_bin_collection/uk_bin_collection/councils/WyreCouncil.py +89 -0
  164. uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py +45 -0
  165. uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py +33 -0
  166. uk_bin_collection/uk_bin_collection/get_bin_data.py +165 -0
  167. uk_bin_collection-0.74.0.dist-info/LICENSE +21 -0
  168. uk_bin_collection-0.74.0.dist-info/METADATA +247 -0
  169. uk_bin_collection-0.74.0.dist-info/RECORD +171 -0
  170. uk_bin_collection-0.74.0.dist-info/WHEEL +4 -0
  171. uk_bin_collection-0.74.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,180 @@
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
7
+ from selenium.webdriver.support.ui import WebDriverWait
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
+ def get_seasonal_overrides():
14
+ url = "https://www.barnet.gov.uk/recycling-and-waste/bin-collections/find-your-bin-collection-day"
15
+ response = requests.get(url)
16
+ if response.status_code == 200:
17
+ soup = BeautifulSoup(response.text, "html.parser")
18
+ body_div = soup.find("div", class_="field--name-body")
19
+ ul_element = body_div.find("ul")
20
+ if ul_element:
21
+ li_elements = ul_element.find_all("li")
22
+ overrides_dict = {}
23
+ for li_element in li_elements:
24
+ li_text = li_element.text.strip()
25
+ li_text = re.sub(r"\([^)]*\)", "", li_text).strip()
26
+ if "Collections for" in li_text and "will be revised to" in li_text:
27
+ parts = li_text.split("will be revised to")
28
+ original_date = (
29
+ parts[0]
30
+ .replace("Collections for", "")
31
+ .replace("\xa0", " ")
32
+ .strip()
33
+ )
34
+ revised_date = parts[1].strip()
35
+
36
+ # Extract day and month
37
+ date_parts = original_date.split()[1:]
38
+ if len(date_parts) == 2:
39
+ day, month = date_parts
40
+ # Ensure original_date has leading zeros for single-digit days
41
+ day = day.zfill(2)
42
+ original_date = f"{original_date.split()[0]} {day} {month}"
43
+
44
+ # Store the information in the dictionary
45
+ overrides_dict[original_date] = revised_date
46
+ return overrides_dict
47
+ else:
48
+ print("UL element not found within the specified div.")
49
+ else:
50
+ print(f"Failed to retrieve the page. Status code: {response.status_code}")
51
+
52
+
53
+ # import the wonderful Beautiful Soup and the URL grabber
54
+ class CouncilClass(AbstractGetBinDataClass):
55
+ """
56
+ Concrete classes have to implement all abstract operations of the
57
+ base class. They can also override some operations with a default
58
+ implementation.
59
+ """
60
+
61
+ def parse_data(self, page: str, **kwargs) -> dict:
62
+ driver = None
63
+ try:
64
+ user_postcode = kwargs.get("postcode")
65
+ if not user_postcode:
66
+ raise ValueError("No postcode provided.")
67
+ check_postcode(user_postcode)
68
+
69
+ user_paon = kwargs.get("paon")
70
+ check_paon(user_paon)
71
+ headless = kwargs.get("headless")
72
+ web_driver = kwargs.get("web_driver")
73
+ driver = create_webdriver(web_driver, headless)
74
+ page = "https://account.barnet.gov.uk/Forms/Home/Redirector/Index/?id=6a2ac067-3322-46e5-96e4-16c0c214454a&mod=OA&casetype=BAR&formname=BNTCOLDATE"
75
+ driver.get(page)
76
+
77
+ time.sleep(5)
78
+
79
+ postcode_input = WebDriverWait(driver, 10).until(
80
+ EC.presence_of_element_located(
81
+ (By.CSS_SELECTOR, '[aria-label="Postcode"]')
82
+ )
83
+ )
84
+
85
+ postcode_input.send_keys(user_postcode)
86
+
87
+ find_address_button = WebDriverWait(driver, 10).until(
88
+ EC.presence_of_element_located(
89
+ (By.CSS_SELECTOR, '[value="Find address"]')
90
+ )
91
+ )
92
+ find_address_button.click()
93
+
94
+ time.sleep(15)
95
+ # Wait for address box to be visible
96
+ select_address_input = WebDriverWait(driver, 10).until(
97
+ EC.presence_of_element_located(
98
+ (
99
+ By.ID,
100
+ "MainContent_CUSTOM_FIELD_808562d4b07f437ea751317cabd19d9eeaf8742f49cb4f7fa9bef99405b859f2",
101
+ )
102
+ )
103
+ )
104
+
105
+ # Select address based
106
+ select = Select(select_address_input)
107
+ addr_label = f"{user_postcode}, {user_paon},"
108
+ for addr_option in select.options:
109
+ option_name = addr_option.accessible_name[0 : len(addr_label)]
110
+ if option_name == addr_label:
111
+ break
112
+ select.select_by_value(addr_option.text)
113
+
114
+ time.sleep(10)
115
+ # Wait for the specified div to be present
116
+ target_div_id = "MainContent_CUSTOM_FIELD_808562d4b07f437ea751317cabd19d9ed93a174c32b14f839b65f6abc42d8108_div"
117
+ target_div = WebDriverWait(driver, 10).until(
118
+ EC.presence_of_element_located((By.ID, target_div_id))
119
+ )
120
+
121
+ time.sleep(5)
122
+ soup = BeautifulSoup(driver.page_source, "html.parser")
123
+
124
+ # Find the div with the specified id
125
+ target_div = soup.find("div", {"id": target_div_id})
126
+
127
+ # Handle the additional table of info for xmas
128
+ try:
129
+ overrides_dict = get_seasonal_overrides()
130
+ except Exception as e:
131
+ overrides_dict = {}
132
+
133
+ # Check if the div is found
134
+ if target_div:
135
+ bin_data = {"bins": []}
136
+
137
+ for bin_div in target_div.find_all(
138
+ "div",
139
+ {"style": re.compile("background-color:.*; padding-left: 4px;")},
140
+ ):
141
+ bin_type = bin_div.find("strong").text.strip()
142
+ collection_date_string = (
143
+ re.search(r"Next collection date:\s+(.*)", bin_div.text)
144
+ .group(1)
145
+ .strip()
146
+ .replace(",", "")
147
+ )
148
+ if collection_date_string in overrides_dict:
149
+ # Replace with the revised date from overrides_dict
150
+ collection_date_string = overrides_dict[collection_date_string]
151
+
152
+ current_date = datetime.now()
153
+ parsed_date = datetime.strptime(
154
+ collection_date_string + f" {current_date.year}", "%A %d %B %Y"
155
+ )
156
+ # Check if the parsed date is in the past and not today
157
+ if parsed_date.date() < current_date.date():
158
+ # If so, set the year to the next year
159
+ parsed_date = parsed_date.replace(year=current_date.year + 1)
160
+ else:
161
+ # If not, set the year to the current year
162
+ parsed_date = parsed_date.replace(year=current_date.year)
163
+ formatted_date = parsed_date.strftime("%d/%m/%Y")
164
+
165
+ contains_date(formatted_date)
166
+ bin_info = {"type": bin_type, "collectionDate": formatted_date}
167
+ bin_data["bins"].append(bin_info)
168
+ else:
169
+ raise ValueError("Collection data not found.")
170
+
171
+ except Exception as e:
172
+ # Here you can log the exception if needed
173
+ print(f"An error occurred: {e}")
174
+ # Optionally, re-raise the exception if you want it to propagate
175
+ raise
176
+ finally:
177
+ # This block ensures that the driver is closed regardless of an exception
178
+ if driver:
179
+ driver.quit()
180
+ return bin_data
@@ -0,0 +1,109 @@
1
+ from typing import Dict, Any
2
+ from bs4 import BeautifulSoup
3
+ from dateutil.relativedelta import relativedelta
4
+ import requests
5
+ from datetime import datetime
6
+ from uk_bin_collection.uk_bin_collection.common import (
7
+ check_postcode,
8
+ check_uprn,
9
+ date_format,
10
+ )
11
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
12
+
13
+
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: Any) -> Dict[str, Any]:
22
+ data: Dict[str, Any] = {"bins": []}
23
+
24
+ # Get UPRN and postcode from kwargs
25
+ user_uprn = str(kwargs.get("uprn"))
26
+ user_postcode = str(kwargs.get("postcode"))
27
+ check_postcode(user_postcode)
28
+ check_uprn(user_uprn)
29
+
30
+ # Pass in form data and make the POST request
31
+ headers = {
32
+ "authority": "waste.barnsley.gov.uk",
33
+ "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",
34
+ "accept-language": "en-GB,en;q=0.9",
35
+ "cache-control": "no-cache",
36
+ "content-type": "application/x-www-form-urlencoded",
37
+ "origin": "https://waste.barnsley.gov.uk",
38
+ "pragma": "no-cache",
39
+ "referer": "https://waste.barnsley.gov.uk/ViewCollection/SelectAddress",
40
+ "sec-ch-ua": '"Chromium";v="118", "Opera GX";v="104", "Not=A?Brand";v="99"',
41
+ "sec-ch-ua-mobile": "?0",
42
+ "sec-ch-ua-platform": '"Windows"',
43
+ "sec-fetch-dest": "document",
44
+ "sec-fetch-mode": "navigate",
45
+ "sec-fetch-site": "same-origin",
46
+ "sec-fetch-user": "?1",
47
+ "upgrade-insecure-requests": "1",
48
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.5993.118 Safari/537.36",
49
+ }
50
+ form_data = {
51
+ "personInfo.person1.HouseNumberOrName": "",
52
+ "personInfo.person1.Postcode": f"{user_postcode}",
53
+ "personInfo.person1.UPRN": f"{user_uprn}",
54
+ "person1_SelectAddress": "Select address",
55
+ }
56
+ response = requests.post(
57
+ "https://waste.barnsley.gov.uk/ViewCollection/SelectAddress",
58
+ headers=headers,
59
+ data=form_data,
60
+ )
61
+
62
+ if response.status_code != 200:
63
+ raise ConnectionRefusedError(
64
+ "Error getting results from website! Please open an issue on GitHub!"
65
+ )
66
+
67
+ soup = BeautifulSoup(response.text, features="html.parser")
68
+
69
+ results = soup.find_all("fieldset")
70
+
71
+ # Next collection details
72
+ highlight_content = results[0].find("div", {"class": "highlight-content"})
73
+ bin_date_str = highlight_content.find(
74
+ "em", {"class": "ui-bin-next-date"}
75
+ ).text.strip()
76
+ bin_type = (
77
+ highlight_content.find("p", {"class": "ui-bin-next-type"}).text.strip()
78
+ + " bin"
79
+ )
80
+
81
+ if bin_date_str == "Today":
82
+ bin_date = datetime.today()
83
+ elif bin_date_str == "Tomorrow":
84
+ bin_date = datetime.today() + relativedelta(days=1)
85
+ else:
86
+ bin_date = datetime.strptime(bin_date_str, "%A, %B %d, %Y")
87
+
88
+ dict_data = {
89
+ "type": bin_type,
90
+ "collectionDate": bin_date.strftime(date_format),
91
+ }
92
+ data["bins"].append(dict_data)
93
+
94
+ # Upcoming collections
95
+ upcoming_collections = results[1].find("tbody").find_all("tr")
96
+ for row in upcoming_collections:
97
+ columns = row.find_all("td")
98
+ bin_date_str = columns[0].text.strip()
99
+ bin_types = columns[1].text.strip().split(", ")
100
+
101
+ for bin_type in bin_types:
102
+ bin_date = datetime.strptime(bin_date_str, "%A, %B %d, %Y")
103
+ dict_data = {
104
+ "type": bin_type.strip() + " bin",
105
+ "collectionDate": bin_date.strftime(date_format),
106
+ }
107
+ data["bins"].append(dict_data)
108
+
109
+ return data
@@ -0,0 +1,72 @@
1
+ from bs4 import BeautifulSoup
2
+ from datetime import datetime
3
+ import requests
4
+ from uk_bin_collection.uk_bin_collection.common import *
5
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
6
+
7
+ COLLECTION_KINDS = {
8
+ "waste": "rteelem_ctl03_pnlCollections_Refuse",
9
+ "recycling": "rteelem_ctl03_pnlCollections_Recycling",
10
+ "glass": "rteelem_ctl03_pnlCollections_Glass",
11
+ # Garden waste data is only returned if the property is subscribed to the Garden Waste service
12
+ "garden": "rteelem_ctl03_pnlCollections_GardenWaste",
13
+ }
14
+
15
+
16
+ class CouncilClass(AbstractGetBinDataClass):
17
+ def parse_data(self, page: str, **kwargs) -> dict:
18
+ requests.packages.urllib3.disable_warnings()
19
+
20
+ user_uprn = kwargs.get("uprn")
21
+ check_uprn(user_uprn)
22
+
23
+ cookies = {
24
+ "cookie_control_popup": "A",
25
+ "WhenAreMyBinsCollected": f"{user_uprn}",
26
+ }
27
+
28
+ headers = {
29
+ "Accept": "*/*",
30
+ "Accept-Language": "en-GB,en;q=0.9",
31
+ "Referer": "https://www.basingstoke.gov.uk/",
32
+ "Sec-Fetch-Dest": "document",
33
+ "Sec-Fetch-Mode": "navigate",
34
+ "Sec-Fetch-Site": "cross-site",
35
+ "Sec-Fetch-User": "?1",
36
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.188 Safari/537.36",
37
+ }
38
+
39
+ response = requests.get(
40
+ "https://www.basingstoke.gov.uk/bincollections",
41
+ cookies=cookies,
42
+ headers=headers,
43
+ verify=False,
44
+ )
45
+
46
+ if response.status_code != 200 or response.text == "0|error|500||":
47
+ raise SystemError(
48
+ "Error retrieving data! Please try again or raise an issue on GitHub!"
49
+ )
50
+
51
+ # Make a BS4 object
52
+ soup = BeautifulSoup(response.text, features="html.parser")
53
+ soup.prettify()
54
+
55
+ bins = []
56
+
57
+ for collection_type, collection_class in COLLECTION_KINDS.items():
58
+ for date in soup.select(f"div#{collection_class} li"):
59
+ date_pattern = r"\d{1,2}\s\w+\s\d{4}" # Regex pattern to extract date
60
+ match = re.search(date_pattern, date.get_text(strip=True))
61
+
62
+ if match:
63
+ extracted_date = match.group()
64
+ formatted_date = datetime.strptime(
65
+ extracted_date, "%d %B %Y"
66
+ ).strftime(date_format)
67
+
68
+ bins.append(
69
+ {"type": collection_type, "collectionDate": formatted_date}
70
+ )
71
+
72
+ return {"bins": bins}
@@ -0,0 +1,100 @@
1
+ import json
2
+ import requests
3
+ from bs4 import BeautifulSoup
4
+ from uk_bin_collection.uk_bin_collection.common import *
5
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
6
+ import ssl
7
+ import urllib3
8
+
9
+
10
+ class CustomHttpAdapter(requests.adapters.HTTPAdapter):
11
+ """Transport adapter" that allows us to use custom ssl_context."""
12
+
13
+ def __init__(self, ssl_context=None, **kwargs):
14
+ self.ssl_context = ssl_context
15
+ super().__init__(**kwargs)
16
+
17
+ def init_poolmanager(self, connections, maxsize, block=False):
18
+ self.poolmanager = urllib3.poolmanager.PoolManager(
19
+ num_pools=connections,
20
+ maxsize=maxsize,
21
+ block=block,
22
+ ssl_context=self.ssl_context,
23
+ )
24
+
25
+
26
+ class CouncilClass(AbstractGetBinDataClass):
27
+ """
28
+ Concrete classes have to implement all abstract operations of the
29
+ base class. They can also override some operations with a default
30
+ implementation.
31
+ """
32
+
33
+ def parse_data(self, page: str, **kwargs) -> dict:
34
+ user_uprn = kwargs.get("uprn")
35
+ check_uprn(user_uprn)
36
+
37
+ headers = {
38
+ "Accept": "application/json, text/javascript, */*; q=0.01",
39
+ "Accept-Language": "en-GB,en;q=0.9",
40
+ "Cache-Control": "no-cache",
41
+ "Connection": "keep-alive",
42
+ "Content-Type": "application/json; charset=utf-8",
43
+ "Pragma": "no-cache",
44
+ "Referer": "https://www.bathnes.gov.uk/webforms/waste/collectionday/",
45
+ "Sec-Fetch-Dest": "empty",
46
+ "Sec-Fetch-Mode": "cors",
47
+ "Sec-Fetch-Site": "same-origin",
48
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.188 Safari/537.36",
49
+ "X-Requested-With": "XMLHttpRequest",
50
+ }
51
+
52
+ session = requests.Session()
53
+ ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
54
+ ctx.options |= 0x4
55
+ session.mount("https://", CustomHttpAdapter(ctx))
56
+
57
+ requests.packages.urllib3.disable_warnings()
58
+ response = session.get(
59
+ f"https://www.bathnes.gov.uk/webapi/api/BinsAPI/v2/getbartecroute/{user_uprn}/true",
60
+ headers=headers,
61
+ )
62
+ if response.text == "":
63
+ raise ValueError(
64
+ "Error parsing data. Please check the provided UPRN. "
65
+ "If this error continues please open an issue on GitHub."
66
+ )
67
+ json_data = json.loads(response.text)
68
+
69
+ data = {"bins": []}
70
+
71
+ if len(json_data["residualNextDate"]) > 0:
72
+ dict_data = {
73
+ "type": "Black Rubbish Bin",
74
+ "collectionDate": datetime.strptime(
75
+ json_data["residualNextDate"], "%Y-%m-%dT%H:%M:%S"
76
+ ).strftime(date_format),
77
+ }
78
+ data["bins"].append(dict_data)
79
+ if len(json_data["recyclingNextDate"]) > 0:
80
+ dict_data = {
81
+ "type": "Recycling Containers",
82
+ "collectionDate": datetime.strptime(
83
+ json_data["recyclingNextDate"], "%Y-%m-%dT%H:%M:%S"
84
+ ).strftime(date_format),
85
+ }
86
+ data["bins"].append(dict_data)
87
+ if len(json_data["organicNextDate"]) > 0:
88
+ dict_data = {
89
+ "type": "Garden Waste",
90
+ "collectionDate": datetime.strptime(
91
+ json_data["organicNextDate"], "%Y-%m-%dT%H:%M:%S"
92
+ ).strftime(date_format),
93
+ }
94
+ data["bins"].append(dict_data)
95
+
96
+ data["bins"].sort(
97
+ key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
98
+ )
99
+
100
+ return data
@@ -0,0 +1,49 @@
1
+ import json
2
+ import requests
3
+ from bs4 import BeautifulSoup
4
+ from uk_bin_collection.uk_bin_collection.common import *
5
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
6
+
7
+
8
+ # import the wonderful Beautiful Soup and the URL grabber
9
+ class CouncilClass(AbstractGetBinDataClass):
10
+ """
11
+ Concrete classes have to implement all abstract operations of the
12
+ base class. They can also override some operations with a default
13
+ implementation.
14
+ """
15
+
16
+ def parse_data(self, page: str, **kwargs) -> dict:
17
+ user_uprn = kwargs.get("uprn")
18
+ check_uprn(user_uprn)
19
+
20
+ api_url = f"https://bbaz-as-prod-bartecapi.azurewebsites.net/api/bincollections/residential/getbyuprn/{user_uprn}/35"
21
+
22
+ requests.packages.urllib3.disable_warnings()
23
+ response = requests.get(api_url)
24
+
25
+ if response.status_code != 200:
26
+ raise ConnectionError("Could not get latest data!")
27
+
28
+ json_data = json.loads(response.text)["BinCollections"]
29
+ data = {"bins": []}
30
+ collections = []
31
+
32
+ for day in json_data:
33
+ for bin in day:
34
+ bin_type = bin["BinType"]
35
+ next_date = datetime.strptime(
36
+ bin["JobScheduledStart"], "%Y-%m-%dT%H:%M:%S"
37
+ )
38
+ collections.append((bin_type, next_date))
39
+
40
+ ordered_data = sorted(collections, key=lambda x: x[1])
41
+ data = {"bins": []}
42
+ for item in ordered_data:
43
+ dict_data = {
44
+ "type": item[0],
45
+ "collectionDate": item[1].strftime(date_format),
46
+ }
47
+ data["bins"].append(dict_data)
48
+
49
+ return data
@@ -0,0 +1,70 @@
1
+ from datetime import datetime
2
+
3
+ import requests
4
+ from bs4 import BeautifulSoup
5
+ from uk_bin_collection.uk_bin_collection.common import *
6
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
7
+
8
+
9
+ class CouncilClass(AbstractGetBinDataClass):
10
+ """
11
+ Concrete classes have to implement all abstract operations of the
12
+ base class. They can also override some operations with a default
13
+ implementation.
14
+ """
15
+
16
+ def parse_data(self, page: str, **kwargs) -> dict:
17
+ user_uprn = kwargs.get("uprn")
18
+ user_postcode = kwargs.get("postcode")
19
+
20
+ check_uprn(user_uprn)
21
+ check_postcode(user_postcode)
22
+
23
+ # Start a new session to walk through the form
24
+ requests.packages.urllib3.disable_warnings()
25
+ s = requests.session()
26
+
27
+ headers = {
28
+ "Origin": "https://www.centralbedfordshire.gov.uk",
29
+ "Referer": "https://www.centralbedfordshire.gov.uk/info/163/bins_and_waste_collections_-_check_bin_collection_day",
30
+ }
31
+
32
+ files = {
33
+ "postcode": (None, user_postcode),
34
+ "address": (None, user_uprn),
35
+ }
36
+
37
+ response = requests.post(
38
+ "https://www.centralbedfordshire.gov.uk/info/163/bins_and_waste_collections_-_check_bin_collection_day#my_bin_collections",
39
+ headers=headers,
40
+ files=files,
41
+ )
42
+
43
+ # Make that BS4 object and use it to prettify the response
44
+ soup = BeautifulSoup(response.content, features="html.parser")
45
+ soup.prettify()
46
+
47
+ collections_div = soup.find(id="collections")
48
+
49
+ # Get the collection items on the page and strip the bits of text that we don't care for
50
+ collections = []
51
+ for bin in collections_div.find_all("h3"):
52
+ next_bin = bin.next_sibling
53
+
54
+ while next_bin.name != "h3" and next_bin.name != "p":
55
+ if next_bin.name != "br":
56
+ collection_date = datetime.strptime(bin.text, "%A, %d %B %Y")
57
+ collections.append((next_bin, collection_date))
58
+ next_bin = next_bin.next_sibling
59
+
60
+ # Sort the collections by date order rather than bin type, then return as a dictionary (with str date)
61
+ ordered_data = sorted(collections, key=lambda x: x[1])
62
+ data = {"bins": []}
63
+ for item in ordered_data:
64
+ dict_data = {
65
+ "type": item[0],
66
+ "collectionDate": item[1].strftime(date_format),
67
+ }
68
+ data["bins"].append(dict_data)
69
+
70
+ return data