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,108 @@
1
+ from bs4 import BeautifulSoup
2
+ from selenium.webdriver.common.by import By
3
+ from selenium.webdriver.support import expected_conditions as EC
4
+ from selenium.webdriver.support.ui import Select
5
+ from selenium.webdriver.support.wait import WebDriverWait
6
+
7
+ from uk_bin_collection.uk_bin_collection.common import *
8
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
9
+
10
+
11
+ # import the wonderful Beautiful Soup and the URL grabber
12
+ class CouncilClass(AbstractGetBinDataClass):
13
+ """
14
+ Concrete classes have to implement all abstract operations of the
15
+ base class. They can also override some operations with a default
16
+ implementation.
17
+ """
18
+
19
+ def parse_data(self, page: str, **kwargs) -> dict:
20
+ driver = None
21
+ try:
22
+ page = "https://forms.north-norfolk.gov.uk/outreach/BinCollectionDays.ofml"
23
+
24
+ data = {"bins": []}
25
+
26
+ user_paon = kwargs.get("paon")
27
+ user_postcode = kwargs.get("postcode")
28
+ web_driver = kwargs.get("web_driver")
29
+ headless = kwargs.get("headless")
30
+ check_paon(user_paon)
31
+ check_postcode(user_postcode)
32
+
33
+ # Create Selenium webdriver
34
+ driver = create_webdriver(web_driver, headless)
35
+ driver.get(page)
36
+
37
+ # Populate postcode field
38
+ inputElement_postcode = driver.find_element(
39
+ By.ID,
40
+ "F_Address_subform:Postcode",
41
+ )
42
+ inputElement_postcode.send_keys(user_postcode)
43
+
44
+ # Click search button
45
+ driver.find_element(
46
+ By.ID,
47
+ "BA_Address_subform:Search_button",
48
+ ).click()
49
+
50
+ # Wait for the 'Select address' dropdown to appear
51
+ dropdown = WebDriverWait(driver, 10).until(
52
+ EC.presence_of_element_located(
53
+ (By.XPATH, "//select[@id='F_Address_subform:Id']")
54
+ )
55
+ )
56
+ # Create a 'Select' for it, then select the matching house number/name option
57
+ dropdownSelect = Select(dropdown)
58
+ matchingOptions = [
59
+ o for o in dropdownSelect.options if user_paon.lower() in o.text.lower()
60
+ ]
61
+ if matchingOptions:
62
+ matchingOptions[0].click()
63
+
64
+ # Wait for the results to appear
65
+ WebDriverWait(driver, 10).until(
66
+ EC.presence_of_element_located(
67
+ (By.XPATH, "//div[contains(@class, 'fieldmergedcolumn')]/ul")
68
+ )
69
+ )
70
+
71
+ soup = BeautifulSoup(driver.page_source, features="html.parser")
72
+
73
+ bins_text = soup.find("div", id="Search_result_details_cps_hd")
74
+
75
+ if bins_text:
76
+ results = re.findall(
77
+ "Your next (.*?) Bin collection is ([A-Za-z]+ \\d\\d? [A-Za-z]+)",
78
+ bins_text.get_text(),
79
+ )
80
+ if results:
81
+ for result in results:
82
+ collection_date = datetime.strptime(
83
+ result[1] + " " + datetime.now().strftime("%Y"),
84
+ "%A %d %B %Y",
85
+ )
86
+ dict_data = {
87
+ "type": result[0],
88
+ "collectionDate": collection_date.strftime(date_format),
89
+ }
90
+ data["bins"].append(dict_data)
91
+
92
+ data["bins"].sort(
93
+ key=lambda x: datetime.strptime(
94
+ x.get("collectionDate"), date_format
95
+ )
96
+ )
97
+ else:
98
+ raise ValueError("No matching address for house number/name found.")
99
+ except Exception as e:
100
+ # Here you can log the exception if needed
101
+ print(f"An error occurred: {e}")
102
+ # Optionally, re-raise the exception if you want it to propagate
103
+ raise
104
+ finally:
105
+ # This block ensures that the driver is closed regardless of an exception
106
+ if driver:
107
+ driver.quit()
108
+ return data
@@ -0,0 +1,72 @@
1
+ import hashlib
2
+ import math
3
+ import time
4
+ from datetime import datetime as dtm, timedelta
5
+
6
+ from uk_bin_collection.uk_bin_collection.common import *
7
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
8
+
9
+
10
+ def myFunc(e):
11
+ return e["start"]
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) -> dict:
22
+ data = {"bins": []}
23
+ uprn = kwargs.get("uprn")
24
+ check_uprn(uprn)
25
+ today = int(datetime.now().timestamp()) * 1000
26
+ dateforurl = datetime.now().strftime("%Y-%m-%d")
27
+ dateforurl2 = (datetime.now() + timedelta(days=42)).strftime("%Y-%m-%d")
28
+ headers = {
29
+ "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)",
30
+ }
31
+ requests.packages.urllib3.disable_warnings()
32
+
33
+ # Get variables for workings
34
+ response = requests.get(
35
+ f"https://cms.northnorthants.gov.uk/bin-collection-search/calendarevents/{uprn}/{dateforurl}/{dateforurl2}",
36
+ headers=headers,
37
+ )
38
+ if response.status_code != 200:
39
+ raise ValueError("No bin data found for provided UPRN..")
40
+
41
+ json_response = json.loads(response.text)
42
+
43
+ output_dict = [
44
+ x
45
+ for x in json_response
46
+ if int("".join(filter(str.isdigit, x["start"]))) >= today
47
+ ]
48
+
49
+ output_json = output_dict
50
+ output_json.sort(key=myFunc)
51
+
52
+ i = 0
53
+ while i < len(output_json):
54
+ sov = output_json[i]["title"].lower()
55
+ if "recycling" in sov:
56
+ bin_type = "Recycling"
57
+ elif "garden" in sov:
58
+ bin_type = "Garden"
59
+ elif "refuse" in sov:
60
+ bin_type = "General"
61
+ else:
62
+ bin_type = "Unknown"
63
+ dateofbin = int("".join(filter(str.isdigit, output_json[i]["start"])))
64
+ day = dtm.fromtimestamp(dateofbin / 1000)
65
+ collection_data = {
66
+ "type": bin_type,
67
+ "collectionDate": day.strftime(date_format),
68
+ }
69
+ data["bins"].append(collection_data)
70
+ i += 1
71
+
72
+ return data
@@ -0,0 +1,76 @@
1
+ from bs4 import BeautifulSoup
2
+ from uk_bin_collection.uk_bin_collection.common import *
3
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
4
+
5
+
6
+ class CouncilClass(AbstractGetBinDataClass):
7
+ """
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.
11
+ """
12
+
13
+ def parse_data(self, page: str, **kwargs) -> dict:
14
+ api_url = "https://forms.n-somerset.gov.uk/Waste/CollectionSchedule"
15
+ uprn = kwargs.get("uprn")
16
+ postcode = kwargs.get("postcode")
17
+ check_uprn(uprn)
18
+ check_postcode(postcode)
19
+
20
+ # Get schedule from API
21
+ values = {
22
+ "PreviousHouse": "",
23
+ "PreviousPostcode": postcode,
24
+ "Postcode": postcode,
25
+ "SelectedUprn": uprn,
26
+ }
27
+ headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"}
28
+ requests.packages.urllib3.disable_warnings()
29
+ response = requests.request("POST", api_url, headers=headers, data=values)
30
+
31
+ soup = BeautifulSoup(response.text, features="html.parser")
32
+
33
+ rows = soup.find("table", {"class": re.compile("table")}).find_all("tr")
34
+
35
+ # Form a JSON wrapper
36
+ data = {"bins": []}
37
+
38
+ # Loops the Rows
39
+ for row in rows:
40
+ cells = row.find_all("td")
41
+ if cells:
42
+ binType = cells[0].get_text(strip=True)
43
+ collectionDate = (
44
+ cells[1].get_text(strip=True) + " " + datetime.now().strftime("%Y")
45
+ )
46
+ nextCollectionDate = (
47
+ cells[2].get_text(strip=True) + " " + datetime.now().strftime("%Y")
48
+ )
49
+
50
+ # Make each Bin element in the JSON
51
+ dict_data = {
52
+ "type": binType,
53
+ "collectionDate": get_next_occurrence_from_day_month(
54
+ datetime.strptime(collectionDate, "%A %d %B %Y")
55
+ ).strftime(date_format),
56
+ }
57
+
58
+ # Add data to the main JSON Wrapper
59
+ data["bins"].append(dict_data)
60
+
61
+ # Make each next Bin element in the JSON
62
+ dict_data = {
63
+ "type": binType,
64
+ "collectionDate": get_next_occurrence_from_day_month(
65
+ datetime.strptime(nextCollectionDate, "%A %d %B %Y")
66
+ ).strftime(date_format),
67
+ }
68
+
69
+ # Add data to the main JSON Wrapper
70
+ data["bins"].append(dict_data)
71
+
72
+ data["bins"].sort(
73
+ key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
74
+ )
75
+
76
+ return data
@@ -0,0 +1,220 @@
1
+ import math
2
+ from datetime import *
3
+
4
+ import requests
5
+ from bs4 import BeautifulSoup
6
+ from uk_bin_collection.uk_bin_collection.common import *
7
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
8
+
9
+
10
+ # import the wonderful Beautiful Soup and the URL grabber
11
+ class CouncilClass(AbstractGetBinDataClass):
12
+ """
13
+ Concrete classes have to implement all abstract operations of the
14
+ base class. They can also override some operations with a default
15
+ implementation.
16
+ """
17
+
18
+ def parse_data(self, page: str, **kwargs) -> dict:
19
+ requests.packages.urllib3.disable_warnings()
20
+ data = {"bins": []}
21
+ user_uprn = kwargs.get("uprn")
22
+ user_postcode = kwargs.get("postcode")
23
+ check_uprn(user_uprn)
24
+ check_postcode(user_postcode)
25
+
26
+ # Get form data
27
+ s = requests.session()
28
+ cookies = {
29
+ "ntc-cookie-policy": "1",
30
+ "SSESS6ec6d5d2d471c0357053d5993a839bce": "qBdR7XhmSMd5_PDBIqG0It2R0Fq67igrejRY-WOcskE",
31
+ "has_js": "1",
32
+ }
33
+ headers = {
34
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
35
+ "Accept-Language": "en-GB,en;q=0.7",
36
+ "Cache-Control": "max-age=0",
37
+ "Connection": "keep-alive",
38
+ "Origin": "https://my.northtyneside.gov.uk",
39
+ "Referer": "https://my.northtyneside.gov.uk/category/81/bin-collection-dates",
40
+ "Sec-Fetch-Dest": "document",
41
+ "Sec-Fetch-Mode": "navigate",
42
+ "Sec-Fetch-Site": "same-origin",
43
+ "Sec-Fetch-User": "?1",
44
+ "Sec-GPC": "1",
45
+ "Upgrade-Insecure-Requests": "1",
46
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
47
+ "sec-ch-ua": '"Not?A_Brand";v="8", "Chromium";v="108", "Brave";v="108"',
48
+ "sec-ch-ua-mobile": "?0",
49
+ "sec-ch-ua-platform": '"Windows"',
50
+ }
51
+ ajax_data = {
52
+ "postcode": user_postcode,
53
+ "form_build_id": "form-BQ47tM0NKADE0s8toYkdSef3QBn6lDM-yBseqIOho80",
54
+ "form_id": "ntc_address_wizard",
55
+ "_triggering_element_name": "op",
56
+ "_triggering_element_value": "Find",
57
+ "ajax_html_ids[]": [
58
+ "ntc-web-my",
59
+ "skip-link",
60
+ "navbar",
61
+ "navbar-collapse",
62
+ "search-block-form",
63
+ "ntc-web-search-input-label",
64
+ "ntc-web-search-input",
65
+ "ui-id-1",
66
+ "ntc-web-main",
67
+ "main-content",
68
+ "block-system-main",
69
+ "web-drupal-content",
70
+ "web-drupal-content-main",
71
+ "node-4024",
72
+ "block-ntc-address-ntc-address-finder",
73
+ "wizard-form-wrapper",
74
+ "ntc-address-wizard",
75
+ "edit-postcode",
76
+ "edit-find",
77
+ "backtotop",
78
+ ],
79
+ "ajax_page_state[theme]": "ntc_bootstrap",
80
+ "ajax_page_state[theme_token]": "LN05JIzI6rocWDiBpDyVeywYveuS4jlxD_N0_hhp2Ko",
81
+ "ajax_page_state[css][0]": "1",
82
+ "ajax_page_state[css][modules/system/system.base.css]": "1",
83
+ "ajax_page_state[css][misc/ui/jquery.ui.core.css]": "1",
84
+ "ajax_page_state[css][misc/ui/jquery.ui.theme.css]": "1",
85
+ "ajax_page_state[css][misc/ui/jquery.ui.menu.css]": "1",
86
+ "ajax_page_state[css][misc/ui/jquery.ui.autocomplete.css]": "1",
87
+ "ajax_page_state[css][sites/all/modules/calendar/css/calendar_multiday.css]": "1",
88
+ "ajax_page_state[css][sites/all/modules/date/date_repeat_field/date_repeat_field.css]": "1",
89
+ "ajax_page_state[css][modules/field/theme/field.css]": "1",
90
+ "ajax_page_state[css][modules/node/node.css]": "1",
91
+ "ajax_page_state[css][sites/all/modules/youtube/css/youtube.css]": "1",
92
+ "ajax_page_state[css][sites/all/modules/views/css/views.css]": "1",
93
+ "ajax_page_state[css][sites/all/modules/back_to_top/css/back_to_top.css]": "1",
94
+ "ajax_page_state[css][sites/all/modules/ckeditor/css/ckeditor.css]": "1",
95
+ "ajax_page_state[css][sites/all/modules/ctools/css/ctools.css]": "1",
96
+ "ajax_page_state[css][sites/all/modules/panels/css/panels.css]": "1",
97
+ "ajax_page_state[css][sites/all/modules/taxonomy_access/taxonomy_access.css]": "1",
98
+ "ajax_page_state[css][sites/all/modules/search_autocomplete/css/themes/minimal.css]": "1",
99
+ "ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/bootstrap.css]": "1",
100
+ "ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/generic.css]": "1",
101
+ "ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/custom.css]": "1",
102
+ "ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/components.css]": "1",
103
+ "ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/modules.css]": "1",
104
+ "ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/fostering.css]": "1",
105
+ "ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/responsive.css]": "1",
106
+ "ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/ie10.css]": "1",
107
+ "ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/ie.css]": "1",
108
+ "ajax_page_state[js][0]": "1",
109
+ "ajax_page_state[js][1]": "1",
110
+ "ajax_page_state[js][sites/all/themes/bootstrap/js/bootstrap.js]": "1",
111
+ "ajax_page_state[js][//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js]": "1",
112
+ "ajax_page_state[js][misc/jquery-extend-3.4.0.js]": "1",
113
+ "ajax_page_state[js][misc/jquery-html-prefilter-3.5.0-backport.js]": "1",
114
+ "ajax_page_state[js][misc/jquery.once.js]": "1",
115
+ "ajax_page_state[js][misc/drupal.js]": "1",
116
+ "ajax_page_state[js][//ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js]": "1",
117
+ "ajax_page_state[js][sites/all/modules/jquery_update/replace/ui/external/jquery.cookie.js]": "1",
118
+ "ajax_page_state[js][sites/all/modules/jquery_update/replace/misc/jquery.form.min.js]": "1",
119
+ "ajax_page_state[js][misc/ajax.js]": "1",
120
+ "ajax_page_state[js][sites/all/modules/jquery_update/js/jquery_update.js]": "1",
121
+ "ajax_page_state[js][sites/all/modules/back_to_top/js/back_to_top.js]": "1",
122
+ "ajax_page_state[js][sites/all/themes/bootstrap/js/misc/_progress.js]": "1",
123
+ "ajax_page_state[js][sites/all/modules/field_group/field_group.js]": "1",
124
+ "ajax_page_state[js][sites/all/modules/search_autocomplete/js/jquery.autocomplete.js]": "1",
125
+ "ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/NTC.jquery.contentMenuScroller.js]": "1",
126
+ "ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/NTC.jquery.alertClose.js]": "1",
127
+ "ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/NTC.jquery.activeTrail.js]": "1",
128
+ "ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/NTC.jquery.expandLinkToDiv.js]": "1",
129
+ "ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/NTC.jquery.events.js]": "1",
130
+ "ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/cookieconsent.js]": "1",
131
+ "ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/google-analytics.js]": "1",
132
+ "ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/ios-orientationchange-fix.js]": "1",
133
+ "ajax_page_state[js][sites/all/themes/bootstrap/js/misc/ajax.js]": "1",
134
+ "ajax_page_state[jquery_version]": "1.10",
135
+ }
136
+ uprn_data = {
137
+ "house_number": "0000" + f"{user_uprn}",
138
+ "op": "Use",
139
+ "form_build_id": "form-BQ47tM0NKADE0s8toYkdSef3QBn6lDM-yBseqIOho80",
140
+ "form_id": "ntc_address_wizard",
141
+ }
142
+ collections = []
143
+
144
+ response = s.post(
145
+ "https://my.northtyneside.gov.uk/system/ajax",
146
+ # cookies=cookies,
147
+ headers=headers,
148
+ data=ajax_data,
149
+ verify=False,
150
+ )
151
+ response = s.post(
152
+ "https://my.northtyneside.gov.uk/category/81/bin-collection-dates",
153
+ # cookies=cookies,
154
+ headers=headers,
155
+ data=uprn_data,
156
+ verify=False,
157
+ )
158
+ response = s.get(
159
+ "https://my.northtyneside.gov.uk/category/81/bin-collection-dates",
160
+ # cookies=cookies,
161
+ headers=headers,
162
+ data=uprn_data,
163
+ verify=False,
164
+ )
165
+
166
+ # Parse form page and get the day of week text
167
+ soup = BeautifulSoup(response.text, features="html.parser")
168
+ soup.prettify()
169
+ bin_text = soup.find("section", {"class": "block block-ntc-bins clearfix"})
170
+ regular_text = bin_text.select("p:nth-child(2) > strong")[0].text.strip()
171
+ x = bin_text.select("p:nth-child(4) > strong")
172
+ if len(bin_text.select("p:nth-child(4) > strong")) == 1:
173
+ special_text = bin_text.select("p:nth-child(4) > strong")[0].text.strip()
174
+ else:
175
+ special_text = bin_text.select("p:nth-child(5) > strong")[0].text.strip()
176
+
177
+ # Since calendar only shows until end of March 2024, work out how many weeks that is
178
+ weeks_total = math.floor((datetime(2024, 4, 1) - datetime.now()).days / 7)
179
+
180
+ # Convert day text to series of dates using previous calculation
181
+ regular_collections = get_weekday_dates_in_period(
182
+ datetime.today(),
183
+ days_of_week.get(regular_text.capitalize()),
184
+ amount=weeks_total,
185
+ )
186
+ special_collections = get_weekday_dates_in_period(
187
+ datetime.today(), days_of_week.get(special_text.capitalize())
188
+ )
189
+
190
+ # Differentiate between regular and recycling bins
191
+ for item in regular_collections:
192
+ item_as_date = datetime.strptime(item, date_format)
193
+ # Check if holiday (calendar only has one day that's a holiday, and it's moved to the next day)
194
+ if is_holiday(item_as_date, Region.ENG):
195
+ item_as_date += timedelta(days=1)
196
+ # Use the isoweek number to separate collections - at the time of writing 11th Jan is week 2, which
197
+ # is for the grey bin
198
+ if (item_as_date.date().isocalendar()[1] % 2) == 0:
199
+ collections.append(("Regular bin (green)", item_as_date))
200
+
201
+ else:
202
+ collections.append(("Recycling bin (grey)", item_as_date))
203
+
204
+ # Add the special collection dates to the collection tuple
205
+ collections += [
206
+ ("Special collection (bookable)", datetime.strptime(item, date_format))
207
+ for item in special_collections
208
+ ]
209
+
210
+ # Sort the collections tuple by date, the add to dictionary and return
211
+ ordered_data = sorted(collections, key=lambda x: x[1])
212
+ data = {"bins": []}
213
+ for item in ordered_data:
214
+ dict_data = {
215
+ "type": item[0],
216
+ "collectionDate": item[1].strftime(date_format),
217
+ }
218
+ data["bins"].append(dict_data)
219
+
220
+ return data
@@ -0,0 +1,114 @@
1
+ from bs4 import BeautifulSoup
2
+ from datetime import datetime, timedelta
3
+ from selenium.webdriver.common.by import By
4
+ from selenium.webdriver.support import expected_conditions as EC
5
+ from selenium.webdriver.support.ui import Select
6
+ from selenium.webdriver.support.wait import WebDriverWait
7
+ import re # Import regular expressions
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
+
15
+
16
+ class CouncilClass(AbstractGetBinDataClass):
17
+ """
18
+ Concrete classes have to implement all abstract operations of the
19
+ base class. They can also override some operations with a default
20
+ implementation.
21
+ """
22
+
23
+ def parse_data(self, page: str, **kwargs) -> dict:
24
+ driver = None
25
+ try:
26
+ data = {"bins": []}
27
+
28
+ user_uprn = kwargs.get("uprn")
29
+ user_postcode = kwargs.get("postcode")
30
+ web_driver = kwargs.get("web_driver")
31
+ headless = kwargs.get("headless")
32
+ check_uprn(user_uprn)
33
+ check_postcode(user_postcode)
34
+ # Create Selenium webdriver
35
+ page = f"https://my.nwleics.gov.uk/my-property-finder?address={user_postcode}&go=1"
36
+
37
+ driver = create_webdriver(web_driver, headless)
38
+ driver.get(page)
39
+
40
+ # If you bang in the house number (or property name) and postcode in the box it should find your property
41
+
42
+ # iframe_presense = WebDriverWait(driver, 30).until(
43
+ # EC.presence_of_element_located((By.ID, "fillform-frame-1"))
44
+ # )
45
+
46
+ # driver.switch_to.frame(iframe_presense)
47
+ wait = WebDriverWait(driver, 60)
48
+
49
+ address_link = wait.until(
50
+ EC.element_to_be_clickable(
51
+ (By.XPATH, f'//a[contains(@href, "{user_uprn}")]')
52
+ )
53
+ )
54
+
55
+ address_link.click()
56
+
57
+ refuse_element = wait.until(
58
+ EC.presence_of_element_located(
59
+ (By.XPATH, f'//h3[contains(text(), "Refuse Collection Dates")]')
60
+ )
61
+ )
62
+
63
+ soup = BeautifulSoup(driver.page_source, features="html.parser")
64
+
65
+ # Find the unordered list containing refuse collection details
66
+ refuse_list = soup.find("ul", class_="refuse")
67
+
68
+ current_year = datetime.now().year
69
+
70
+ if refuse_list:
71
+ # Iterate through list items within the unordered list
72
+ for li in refuse_list.find_all("li"):
73
+ date = li.find(
74
+ "strong", class_="date"
75
+ ).text.strip() # Extract the date
76
+ waste_type = li.find("a").text.strip() # Extract the waste type
77
+
78
+ # Parse the date from the string
79
+ # check for today and tomorrow
80
+ if date.lower() == "today":
81
+ parsed_date = datetime.now().date()
82
+ elif date.lower() == "tomorrow":
83
+ parsed_date = (datetime.now() + timedelta(days=1)).date()
84
+ else:
85
+ date = re.sub(r"(st|nd|rd|th)", "", date)
86
+ parsed_date = datetime.strptime(date, "%a %d %b").date()
87
+
88
+ current_date = datetime.now().date()
89
+
90
+ # double check we've got a year and if not the current one
91
+ if parsed_date.year < current_date.year:
92
+ parsed_date = parsed_date.replace(year=current_date.year)
93
+
94
+ # check if the date is in the past and if so add a year
95
+ if parsed_date < current_date:
96
+ parsed_date = parsed_date.replace(year=current_date.year + 1)
97
+
98
+ # Append data to your 'bins' list (this replicates your existing logic)
99
+ data["bins"].append(
100
+ {
101
+ "type": waste_type,
102
+ "collectionDate": parsed_date.strftime("%d/%m/%Y"),
103
+ }
104
+ )
105
+ except Exception as e:
106
+ # Here you can log the exception if needed
107
+ print(f"An error occurred: {e}")
108
+ # Optionally, re-raise the exception if you want it to propagate
109
+ raise
110
+ finally:
111
+ # This block ensures that the driver is closed regardless of an exception
112
+ if driver:
113
+ driver.quit()
114
+ return data
@@ -0,0 +1,58 @@
1
+ from bs4 import BeautifulSoup
2
+ from uk_bin_collection.uk_bin_collection.common import *
3
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
4
+
5
+
6
+ class CouncilClass(AbstractGetBinDataClass):
7
+ """
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.
11
+ """
12
+
13
+ def parse_data(self, page: str, **kwargs) -> dict:
14
+ uprn = kwargs.get("uprn")
15
+ check_uprn(uprn)
16
+
17
+ # Figure bin data URL from UPRN
18
+ url = "https://www.northyorks.gov.uk/bin-calendar/lookup"
19
+
20
+ payload = {
21
+ "selected_address": uprn,
22
+ "submit": "Continue",
23
+ "form_id": "bin_calendar_lookup_form",
24
+ }
25
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
26
+
27
+ # This endpoint redirects to the data url.
28
+ response = requests.request("POST", url, headers=headers, data=payload)
29
+ bin_data_url = f"{response.url}/ajax"
30
+
31
+ # Get bin data
32
+ response = requests.request("GET", bin_data_url)
33
+ bin_data = response.json()
34
+
35
+ # Parse bin data
36
+ soup = BeautifulSoup(bin_data[1]["data"], "html.parser")
37
+
38
+ # All collection info is in the table
39
+ table = (
40
+ soup.find("div", {"id": "upcoming-collection"}).find("table").find("tbody")
41
+ )
42
+ rows = table.find_all("tr")
43
+
44
+ data = {"bins": []}
45
+ for row in rows:
46
+ cols = row.find_all("td")
47
+ # First column is date
48
+ bin_date = datetime.strptime(cols[0].text.strip(), "%d %B %Y")
49
+ # Third column is type
50
+ bin_type = cols[2].text.strip()
51
+ # This bin
52
+ this_bin = {
53
+ "type": bin_type,
54
+ "collectionDate": bin_date.strftime(date_format),
55
+ }
56
+ data["bins"].append(this_bin)
57
+
58
+ return data