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,74 @@
1
+ from datetime import timedelta
2
+
3
+ from uk_bin_collection.uk_bin_collection.common import *
4
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
5
+
6
+
7
+ def format_bin_data(key: str, date: datetime):
8
+ formatted_date = date.strftime(date_format)
9
+
10
+ if re.match(r"^R\d+$", key) is not None:
11
+ # RX matches both general waste and recycling
12
+ return [
13
+ ("General Waste (Black Bin)", formatted_date),
14
+ ("Recycling & Food Waste", formatted_date),
15
+ ]
16
+ elif re.match(r"^G\d+$", key) is not None:
17
+ return [("Garden Waste (Green Bin)", formatted_date)]
18
+ elif re.match(r"^C\d+$", key) is not None:
19
+ return [("Recycling & Food Waste", formatted_date)]
20
+ else:
21
+ return None
22
+
23
+
24
+ class CouncilClass(AbstractGetBinDataClass):
25
+ def parse_data(self, page: str, **kwargs) -> dict:
26
+ uprn = kwargs.get("uprn")
27
+ check_uprn(uprn)
28
+
29
+ api_url = (
30
+ f"https://webapps.southglos.gov.uk/Webservices/SGC.RefuseCollectionService/RefuseCollectionService"
31
+ f".svc/getCollections/{uprn}"
32
+ )
33
+
34
+ headers = {"content-type": "application/json"}
35
+
36
+ response = requests.get(api_url, headers=headers)
37
+
38
+ json_response = json.loads(response.content)
39
+ if not json_response:
40
+ raise ValueError("No collection data found for provided UPRN.")
41
+
42
+ collection_data = json_response[0]
43
+
44
+ today = datetime.today()
45
+ eight_weeks = datetime.today() + timedelta(days=8 * 7)
46
+ data = {"bins": []}
47
+ collection_tuple = []
48
+
49
+ for key in collection_data:
50
+ if key == "CalendarName":
51
+ continue
52
+
53
+ item = collection_data[key]
54
+
55
+ if item == "":
56
+ continue
57
+
58
+ collection_date = datetime.strptime(item, date_format)
59
+ if today.date() <= collection_date.date() <= eight_weeks.date():
60
+ bin_data = format_bin_data(key, collection_date)
61
+ if bin_data is not None:
62
+ for bin_date in bin_data:
63
+ collection_tuple.append(bin_date)
64
+
65
+ ordered_data = sorted(collection_tuple, key=lambda x: x[1])
66
+
67
+ for item in ordered_data:
68
+ dict_data = {
69
+ "type": item[0],
70
+ "collectionDate": item[1],
71
+ }
72
+ data["bins"].append(dict_data)
73
+
74
+ return data
@@ -0,0 +1,78 @@
1
+ import time
2
+ from datetime import timedelta
3
+
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
+ # import the wonderful Beautiful Soup and the URL grabber
10
+ class CouncilClass(AbstractGetBinDataClass):
11
+ """
12
+ Concrete classes have to implement all abstract operations of the
13
+ base class. They can also override some operations with a default
14
+ implementation.
15
+ """
16
+
17
+ def parse_data(self, page: str, **kwargs) -> dict:
18
+ data = {"bins": []}
19
+ collection_types = [
20
+ "non recyclable waste",
21
+ "food and garden",
22
+ "paper and card",
23
+ "glass, cans and plastics",
24
+ ]
25
+
26
+ # Make a BS4 object
27
+ soup = BeautifulSoup(page.text, features="html.parser")
28
+ soup.prettify()
29
+
30
+ week_details = soup.find("div", {"class": "bin-dir-snip"})
31
+ week_dates = week_details.find("div", {"class": "clearfix"}).find("p")
32
+ week_collections = week_details.find_all_next("h4")
33
+
34
+ results = re.search(
35
+ "([A-Za-z0-9 ]+) to ([A-Za-z0-9 ]+)", week_dates.get_text().strip()
36
+ )
37
+ if results:
38
+ week_start = datetime.strptime(results.groups()[0], "%A %d %B %Y")
39
+ week_end = datetime.strptime(results.groups()[1], "%A %d %B %Y")
40
+ week_days = (
41
+ week_start + timedelta(days=i)
42
+ for i in range((week_end - week_start).days + 1)
43
+ )
44
+
45
+ week_collection_types = []
46
+ for week_collection in week_collections:
47
+ week_collection = (
48
+ week_collection.get_text().strip().lower().replace("-", " ")
49
+ )
50
+ for collection_type in collection_types:
51
+ if collection_type in week_collection:
52
+ week_collection_types.append(collection_type)
53
+
54
+ collection_schedule = (
55
+ soup.find("div", {"class": "serviceDetails"})
56
+ .find("table")
57
+ .find_all_next("tr")
58
+ )
59
+
60
+ for day in week_days:
61
+ for row in collection_schedule:
62
+ schedule_type = row.find("th").get_text().strip()
63
+ results2 = re.search("([^(]+)", row.find("td").get_text().strip())
64
+ if results2:
65
+ schedule_day = results2[1].strip()
66
+ for collection_type in week_collection_types:
67
+ if collection_type in schedule_type.lower():
68
+ if (
69
+ day.weekday()
70
+ == time.strptime(schedule_day, "%A").tm_wday
71
+ ):
72
+ dict_data = {
73
+ "type": schedule_type,
74
+ "collectionDate": day.strftime(date_format),
75
+ }
76
+ data["bins"].append(dict_data)
77
+
78
+ return data
@@ -0,0 +1,91 @@
1
+ from xml.etree import ElementTree
2
+
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
+ class CouncilClass(AbstractGetBinDataClass):
9
+ """
10
+ Concrete classes have to implement all abstract operations of the
11
+ baseclass. They can also override some
12
+ operations with a default implementation.
13
+ """
14
+
15
+ def parse_data(self, page: str, **kwargs) -> dict:
16
+ uprn = kwargs.get("uprn")
17
+ check_uprn(uprn)
18
+ council = "SNO"
19
+
20
+ # Make SOAP request
21
+ headers = {
22
+ "Accept": "*/*",
23
+ "Accept-Encoding": "gzip, deflate, br",
24
+ "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8",
25
+ "Connection": "keep-alive",
26
+ "Content-Type": "text/xml; charset=UTF-8",
27
+ "Host": "collections-southnorfolk.azurewebsites.net",
28
+ "Origin": "https://collections-southnorfolk.azurewebsites.net",
29
+ "Referer": "https://collections-southnorfolk.azurewebsites.net/calendar.html",
30
+ "sec-ch-ua": '"Chromium";v="110", "Not A(Brand";v="24", "Google Chrome";v="110"',
31
+ "sec-ch-ua-mobile": "?0",
32
+ "sec-ch-ua-platform": '"Windows"',
33
+ "Sec-Fetch-Dest": "empty",
34
+ "Sec-Fetch-Mode": "cors",
35
+ "Sec-Fetch-Site": "same-origin",
36
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
37
+ }
38
+ requests.packages.urllib3.disable_warnings()
39
+ post_data = (
40
+ '<?xml version="1.0" encoding="utf-8"?>'
41
+ '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">'
42
+ '<soap:Body><getRoundCalendarForUPRN xmlns="http://webaspx-collections.azurewebsites.net/">'
43
+ "<council>" + council + "</council><UPRN>" + uprn + "</UPRN>"
44
+ "<from>Chtml</from></getRoundCalendarForUPRN></soap:Body></soap:Envelope>"
45
+ )
46
+ response = requests.post(
47
+ "https://collections-southnorfolk.azurewebsites.net/WSCollExternal.asmx",
48
+ headers=headers,
49
+ data=post_data,
50
+ )
51
+ if response.status_code != 200:
52
+ raise ValueError("No bin data found for provided UPRN.")
53
+
54
+ # Get HTML from SOAP response
55
+ xmltree = ElementTree.fromstring(response.text)
56
+ html = xmltree.find(
57
+ ".//{http://webaspx-collections.azurewebsites.net/}getRoundCalendarForUPRNResult"
58
+ ).text
59
+ # Parse with BS4
60
+ soup = BeautifulSoup(html, features="html.parser")
61
+ soup.prettify()
62
+
63
+ data = {"bins": []}
64
+ for bin_type in ["RefuseBin", "RecycleBin", "GardenBin"]:
65
+ bin_el = soup.find("b", string=bin_type)
66
+ if bin_el:
67
+ bin_info = bin_el.next_sibling.split(": ")[1]
68
+ collection_date = ""
69
+ results = re.search("([A-Za-z]+ \\d\\d? [A-Za-z]+) then", bin_info)
70
+ if results:
71
+ date = get_next_occurrence_from_day_month(
72
+ datetime.strptime(
73
+ results[1] + " " + datetime.now().strftime("%Y"),
74
+ "%a %d %b %Y",
75
+ )
76
+ )
77
+ if date:
78
+ collection_date = date.strftime(date_format)
79
+ else:
80
+ results2 = re.search("([A-Za-z]+) then", bin_info)
81
+ if results2:
82
+ collection_date = results2[1]
83
+
84
+ if collection_date != "":
85
+ dict_data = {
86
+ "type": bin_type,
87
+ "collectionDate": collection_date,
88
+ }
89
+ data["bins"].append(dict_data)
90
+
91
+ return data
@@ -0,0 +1,93 @@
1
+ import requests
2
+ from bs4 import BeautifulSoup
3
+ from uk_bin_collection.uk_bin_collection.common import *
4
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
5
+
6
+
7
+ # import the wonderful Beautiful Soup and the URL grabber
8
+ class CouncilClass(AbstractGetBinDataClass):
9
+ """
10
+ Concrete classes have to implement all abstract operations of the
11
+ base class. They can also override some operations with a default
12
+ implementation.
13
+ """
14
+
15
+ def parse_data(self, page: str, **kwargs) -> dict:
16
+ user_uprn = kwargs.get("uprn")
17
+ check_uprn(user_uprn)
18
+
19
+ # UPRN is passed in via a cookie. Set cookies/params and GET the page
20
+ cookies = {
21
+ # 'JSESSIONID': '96F2A15C14569B2ED2BBEB140FE86532',
22
+ "SVBINZONE": f"SOUTH%3AUPRN%40{user_uprn}",
23
+ }
24
+ headers = {
25
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
26
+ "Accept-Language": "en-GB,en;q=0.7",
27
+ "Cache-Control": "max-age=0",
28
+ "Connection": "keep-alive",
29
+ "Referer": "https://eform.southoxon.gov.uk/ebase/BINZONE_DESKTOP.eb?SOVA_TAG=SOUTH&ebd=0&ebz=1_1668467255368",
30
+ "Sec-Fetch-Dest": "document",
31
+ "Sec-Fetch-Mode": "navigate",
32
+ "Sec-Fetch-Site": "same-origin",
33
+ "Sec-Fetch-User": "?1",
34
+ "Sec-GPC": "1",
35
+ "Upgrade-Insecure-Requests": "1",
36
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",
37
+ }
38
+ params = {
39
+ "SOVA_TAG": "SOUTH",
40
+ "ebd": "0",
41
+ # 'ebz': '1_1668467255368',
42
+ }
43
+ requests.packages.urllib3.disable_warnings()
44
+ response = requests.get(
45
+ "https://eform.southoxon.gov.uk/ebase/BINZONE_DESKTOP.eb",
46
+ params=params,
47
+ headers=headers,
48
+ cookies=cookies,
49
+ )
50
+
51
+ # Parse response text for super speedy finding
52
+ soup = BeautifulSoup(response.text, features="html.parser")
53
+ soup.prettify()
54
+
55
+ data = {"bins": []}
56
+
57
+ # Page has slider info side by side, which are two instances of this class
58
+ for bin in soup.find_all("div", {"class": "binextra"}):
59
+ bin_info = list(bin.stripped_strings)
60
+ try:
61
+ # On standard collection schedule, date will be contained in the first stripped string
62
+ if contains_date(bin_info[0]):
63
+ bin_date = get_next_occurrence_from_day_month(
64
+ datetime.strptime(
65
+ bin_info[0] + " " + datetime.today().strftime("%Y"),
66
+ "%A %d %B - %Y",
67
+ )
68
+ ).strftime(date_format)
69
+ bin_type = str.capitalize(bin_info[1])
70
+ # On exceptional collection schedule (e.g. around English Bank Holidays), date will be contained in the second stripped string
71
+ else:
72
+ bin_date = get_next_occurrence_from_day_month(
73
+ datetime.strptime(
74
+ bin_info[1] + " " + datetime.today().strftime("%Y"),
75
+ "%A %d %B - %Y",
76
+ )
77
+ ).strftime(date_format)
78
+ bin_type = str.capitalize(bin_info[2])
79
+ except Exception as ex:
80
+ raise ValueError(f"Error parsing bin data: {ex}")
81
+
82
+ # Build data dict for each entry
83
+ dict_data = {
84
+ "type": bin_type,
85
+ "collectionDate": bin_date,
86
+ }
87
+ data["bins"].append(dict_data)
88
+
89
+ data["bins"].sort(
90
+ key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
91
+ )
92
+
93
+ return data
@@ -0,0 +1,98 @@
1
+ import json
2
+ from datetime import datetime
3
+
4
+ import requests
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
+ def get_address_uprn(postcode: str, paon: str, api_url: str) -> str:
10
+ """
11
+ Gets the UPRN and address in desired format
12
+ :rtype: str
13
+ :param postcode: Postcode to use
14
+ :param paon: House number to use
15
+ :param api_url: API to POST
16
+ :return: UPRN and postcode in str format
17
+ """
18
+ addr = ""
19
+ payload = json.dumps(
20
+ {
21
+ "jsonrpc": "2.0",
22
+ "id": "1689431267990",
23
+ "method": "stc.common.snippets.getAddressList",
24
+ "params": {"postcode": f"{postcode.replace(' ', '')}", "localonly": "true"},
25
+ }
26
+ )
27
+ headers = {"Content-Type": "application/json"}
28
+ response = requests.post(api_url, data=payload, headers=headers)
29
+
30
+ json_response = json.loads(response.content)
31
+ results = json_response["result"]["ReturnedList"]
32
+
33
+ for item in results:
34
+ if item["Address"].split()[0] == paon.strip():
35
+ addr = item["UPRN"] + "|" + item["Address"]
36
+ break
37
+
38
+ return addr
39
+
40
+
41
+ class CouncilClass(AbstractGetBinDataClass):
42
+ """
43
+ Concrete classes have to implement all abstract operations of the
44
+ base class. They can also override some operations with a default
45
+ implementation.
46
+ """
47
+
48
+ def parse_data(self, page: str, **kwargs) -> dict:
49
+ requests.packages.urllib3.disable_warnings()
50
+ api_url = "https://www.southtyneside.gov.uk/apiserver/ajaxlibrary/"
51
+ user_postcode = kwargs.get("postcode")
52
+ user_paon = kwargs.get("paon")
53
+ data = {"bins": []}
54
+
55
+ check_postcode(user_postcode)
56
+ check_paon(user_paon)
57
+
58
+ try:
59
+ if user_paon is None:
60
+ raise ValueError("Invalid house number")
61
+ except Exception as ex:
62
+ print(f"Exception encountered: {ex}")
63
+ print(
64
+ "Please check the provided house number. If this error continues, please first trying setting the "
65
+ "house number manually on line 25 before raising an issue."
66
+ )
67
+ exit(1)
68
+
69
+ # Get the "UPRN" (actually the UPRN + address)
70
+ uprn = get_address_uprn(user_postcode, user_paon, api_url)
71
+
72
+ # Set up payload and headers, then post to API to get schedule
73
+ payload = json.dumps(
74
+ {
75
+ "jsonrpc": "2.0",
76
+ "id": "1689431609779",
77
+ "method": "stc.waste.collections.getDates",
78
+ "params": {"addresscode": uprn},
79
+ }
80
+ )
81
+ headers = {"Content-Type": "application/json"}
82
+ response = requests.request("POST", api_url, headers=headers, data=payload)
83
+
84
+ # Break down the resulting JSON and load into dictionary
85
+ json_result = json.loads(response.text)["result"]
86
+ months = json_result["SortedCollections"]
87
+ for month in months:
88
+ collections_in_month = month["Collections"]
89
+ for item in collections_in_month:
90
+ dict_data = {
91
+ "type": item["Type"],
92
+ "collectionDate": datetime.strptime(
93
+ item["DateString"], "%d %B %Y"
94
+ ).strftime(date_format),
95
+ }
96
+ data["bins"].append(dict_data)
97
+
98
+ return data
@@ -0,0 +1,43 @@
1
+ from uk_bin_collection.uk_bin_collection.common import *
2
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
3
+ from dateutil import parser
4
+
5
+
6
+ class CouncilClass(AbstractGetBinDataClass):
7
+ def parse_data(self, page: str, **kwargs) -> dict:
8
+ user_uprn = kwargs.get("uprn")
9
+ check_uprn(user_uprn)
10
+
11
+ data = {"bins": []}
12
+
13
+ headers = {
14
+ "Content-Type": "application/json; charset=UTF-8",
15
+ }
16
+
17
+ req_data = {"uprn": user_uprn, "noticeBoard": "default"}
18
+
19
+ url = "https://gis.stalbans.gov.uk/NoticeBoard9/VeoliaProxy.NoticeBoard.asmx/GetServicesByUprnAndNoticeBoard"
20
+
21
+ response = requests.post(url, json=req_data, headers=headers)
22
+
23
+ collections_response = response.json()
24
+
25
+ collections = []
26
+
27
+ for collection in collections_response["d"]:
28
+ collection_data = collection["ServiceHeaders"][0]
29
+ bin_type = collection_data["TaskType"]
30
+ collection_date = collection_data["Next"]
31
+ next_collection = parser.isoparser().isoparse(collection_date)
32
+ collections.append((bin_type, next_collection))
33
+
34
+ ordered_data = sorted(collections, key=lambda x: x[1])
35
+
36
+ for item in ordered_data:
37
+ dict_data = {
38
+ "type": item[0],
39
+ "collectionDate": item[1].strftime(date_format),
40
+ }
41
+ data["bins"].append(dict_data)
42
+
43
+ return data
@@ -0,0 +1,56 @@
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
+ # import the wonderful Beautiful Soup and the URL grabber
7
+ class CouncilClass(AbstractGetBinDataClass):
8
+ """
9
+ Concrete classes have to implement all abstract operations of the
10
+ baseclass. They can also override some
11
+ operations with a default implementation.
12
+ """
13
+
14
+ def parse_data(self, page: str, **kwargs) -> dict:
15
+ uprn = kwargs.get("uprn")
16
+ # Check the UPRN is valid
17
+ check_uprn(uprn)
18
+
19
+ # Request URL
20
+ url = f"https://secure.sthelens.net/website/CollectionDates.nsf/servlet.xsp/NextCollections?source=1&refid={uprn}"
21
+
22
+ # Make Request
23
+ requests.packages.urllib3.disable_warnings()
24
+ s = requests.session()
25
+ page = s.get(url)
26
+
27
+ # Make a BS4 object
28
+ soup = BeautifulSoup(
29
+ re.sub("<div([^>]+)>", "", page.text).replace("</div>", ""),
30
+ features="html.parser",
31
+ )
32
+ soup.prettify()
33
+
34
+ data = {"bins": []}
35
+ collection_rows = (
36
+ soup.find("table", {"class": "multitable"}).find("tbody").find_all("tr")
37
+ )
38
+
39
+ for collection_row in collection_rows:
40
+ # Get bin collection type
41
+ bin_type = collection_row.find("th")
42
+ if bin_type:
43
+ bin_type = bin_type.get_text(strip=True)
44
+ # Get bin collection dates
45
+ for bin_date in collection_row.find_all("td"):
46
+ if bin_date.get_text(strip=True) != "Dates not allocated":
47
+ collection_date = datetime.strptime(
48
+ bin_date.get_text(strip=True), "%a %d %b %Y"
49
+ )
50
+ dict_data = {
51
+ "type": bin_type,
52
+ "collectionDate": collection_date.strftime(date_format),
53
+ }
54
+ data["bins"].append(dict_data)
55
+
56
+ return data
@@ -0,0 +1,112 @@
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
+ data = {"bins": []}
21
+ user_uprn = kwargs.get("uprn")
22
+ user_postcode = kwargs.get("postcode")
23
+ web_driver = kwargs.get("web_driver")
24
+ headless = kwargs.get("headless")
25
+ check_uprn(user_uprn)
26
+ check_postcode(user_postcode)
27
+
28
+ # Create Selenium webdriver
29
+ driver = create_webdriver(web_driver, headless)
30
+ driver.get("https://www.staffsmoorlands.gov.uk/findyourbinday")
31
+
32
+ # Close cookies banner
33
+ cookieAccept = WebDriverWait(driver, 10).until(
34
+ EC.presence_of_element_located(
35
+ (By.CSS_SELECTOR, ".cookiemessage__link--close")
36
+ )
37
+ )
38
+ cookieAccept.click()
39
+
40
+ # Wait for the postcode field to appear then populate it
41
+ inputElement_postcode = WebDriverWait(driver, 30).until(
42
+ EC.presence_of_element_located(
43
+ (By.ID, "FINDBINDAYSSTAFFORDSHIREMOORLANDS_POSTCODESELECT_POSTCODE")
44
+ )
45
+ )
46
+ inputElement_postcode.send_keys(user_postcode)
47
+
48
+ # Click search button
49
+ findAddress = WebDriverWait(driver, 10).until(
50
+ EC.presence_of_element_located(
51
+ (
52
+ By.ID,
53
+ "FINDBINDAYSSTAFFORDSHIREMOORLANDS_POSTCODESELECT_PAGE1NEXT_NEXT",
54
+ )
55
+ )
56
+ )
57
+ findAddress.click()
58
+
59
+ # Wait for the 'Select address' dropdown to appear and select option matching UPRN
60
+ dropdown = WebDriverWait(driver, 30).until(
61
+ EC.presence_of_element_located(
62
+ (By.ID, "FINDBINDAYSSTAFFORDSHIREMOORLANDS_ADDRESSSELECT_ADDRESS")
63
+ )
64
+ )
65
+ # Create a 'Select' for it, then select the matching URPN option
66
+ dropdownSelect = Select(dropdown)
67
+ dropdownSelect.select_by_value(user_uprn)
68
+
69
+ # Wait for the submit button to appear, then click it to get the collection dates
70
+ submit = WebDriverWait(driver, 10).until(
71
+ EC.presence_of_element_located(
72
+ (
73
+ By.ID,
74
+ "FINDBINDAYSSTAFFORDSHIREMOORLANDS_ADDRESSSELECT_ADDRESSSELECTNEXTBTN_NEXT",
75
+ )
76
+ )
77
+ )
78
+ submit.click()
79
+
80
+ soup = BeautifulSoup(driver.page_source, features="html.parser")
81
+
82
+ # Quit Selenium webdriver to release session
83
+ driver.quit()
84
+
85
+ # Get months
86
+ for month_wrapper in soup.find_all("div", {"class": "bin-collection__month"}):
87
+ if month_wrapper:
88
+ month_year = month_wrapper.find(
89
+ "h3", {"class": "bin-collection__title"}
90
+ ).get_text(strip=True)
91
+ # Get collections
92
+ for collection in month_wrapper.find_all(
93
+ "li", {"class": "bin-collection__item"}
94
+ ):
95
+ day = collection.find(
96
+ "span", {"class": "bin-collection__number"}
97
+ ).get_text(strip=True)
98
+ if month_year and day:
99
+ bin_date = datetime.strptime(day + " " + month_year, "%d %B %Y")
100
+ dict_data = {
101
+ "type": collection.find(
102
+ "span", {"class": "bin-collection__type"}
103
+ ).get_text(strip=True),
104
+ "collectionDate": bin_date.strftime(date_format),
105
+ }
106
+ data["bins"].append(dict_data)
107
+
108
+ data["bins"].sort(
109
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
110
+ )
111
+
112
+ return data