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,39 @@
1
+ # This script pulls (in one hit) the
2
+ # data from Warick District Council Bins Data
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
+ # Make a BS4 object
18
+ soup = BeautifulSoup(page.text, features="html.parser")
19
+ soup.prettify()
20
+
21
+ data = {"bins": []}
22
+
23
+ for bins in soup.select('div[class*="service-item"]'):
24
+ bin_type = bins.div.h3.text.strip()
25
+ binCollection = datetime.strptime(
26
+ bins.select("div > p")[1].get_text(strip=True), "%A, %d %B %Y"
27
+ )
28
+ # binImage = "https://myaccount.stockport.gov.uk" + bins.img['src']
29
+
30
+ # batteries don't have a service date or other
31
+ # info associated with them.
32
+ if binCollection:
33
+ dict_data = {
34
+ "type": bin_type,
35
+ "collectionDate": binCollection.strftime(date_format),
36
+ }
37
+ data["bins"].append(dict_data)
38
+
39
+ return data
@@ -0,0 +1,79 @@
1
+ from bs4 import BeautifulSoup
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
+ # 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
+ # Make a BS4 object
17
+ soup = BeautifulSoup(page.text, features="lxml-xml")
18
+ soup.prettify()
19
+
20
+ data = {"bins": []}
21
+ collections = []
22
+
23
+ # Match bin types from API to their actual type
24
+ bin_types = {
25
+ "RESIDUAL BIN": "Grey bin",
26
+ "RES 180": "Grey bin",
27
+ "RES 240 STD": "Grey bin",
28
+ "RES 360 STD+": "Grey bin",
29
+ "RES 360 STD": "Grey bin",
30
+ "RES 660": "Grey bin",
31
+ "RES 770": "Grey bin",
32
+ "RES BAG": "Grey bin",
33
+ "RES 140 SML": "Grey bin",
34
+ "REC 180 SML": "Blue bin",
35
+ "REC 240 STD": "Blue bin",
36
+ "REC 360": "Blue bin",
37
+ "REC 770": "Blue bin",
38
+ "MIXED REC 55 BOX": "Blue box",
39
+ "PAPER 44 BOX": "Blue box",
40
+ "PAPER BAG": "Paper bag",
41
+ "ORG 180": "Brown bin",
42
+ "ORG 240 STD": "Brown bin",
43
+ "PAID ORGANIC": "Brown bin",
44
+ "RES 1100": "Grey trade container",
45
+ "REC GL 770": "Blue trade container",
46
+ }
47
+ # If the API errors, throw the exception
48
+ if soup.find("Error") is not None:
49
+ raise ConnectionAbortedError(soup.find("Error").text.strip())
50
+
51
+ # Parse the XML and add to a list of collections
52
+ for item in soup.find_all("BinRound"):
53
+ try:
54
+ bin_type = bin_types.get(
55
+ item.find_next("Bin").text.replace("EMPTY BINS", "").strip()
56
+ )
57
+ bin_date = datetime.strptime(
58
+ item.find_next("DateTime").text, "%d/%m/%Y %H:%M:%S"
59
+ )
60
+ if bin_date >= datetime.now():
61
+ collections.append((bin_type, bin_date))
62
+ except:
63
+ raise SystemError(
64
+ "Error has been encountered parsing API. Please try again later and if the issue "
65
+ "persists, open a GitHub ticket!"
66
+ )
67
+
68
+ # Sort the collections list by date
69
+ ordered_data = sorted(collections, key=lambda x: x[1])
70
+
71
+ # Put the elements into the dictionary
72
+ for item in ordered_data:
73
+ dict_data = {
74
+ "type": item[0],
75
+ "collectionDate": item[1].strftime(date_format),
76
+ }
77
+ data["bins"].append(dict_data)
78
+
79
+ return data
@@ -0,0 +1,94 @@
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
+ base class. They can also override some operations with a default
11
+ implementation.
12
+ """
13
+
14
+ def parse_data(self, page: str, **kwargs) -> dict:
15
+ driver = None
16
+ try:
17
+ # Get postcode and UPRN from kwargs
18
+ # user_postcode = kwargs.get("postcode")
19
+ user_uprn = kwargs.get("uprn")
20
+ # check_postcode(user_postcode)
21
+ check_uprn(user_uprn)
22
+ url = "https://www.stratford.gov.uk/waste-recycling/when-we-collect.cfm/part/calendar"
23
+ payload = {
24
+ "frmAddress1": "",
25
+ "frmAddress2": "",
26
+ "frmAddress3": "",
27
+ "frmAddress4": "",
28
+ "frmPostcode": "",
29
+ "frmUPRN": user_uprn,
30
+ }
31
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
32
+
33
+ requests.packages.urllib3.disable_warnings()
34
+ response = requests.request("POST", url, data=payload, headers=headers)
35
+
36
+ # Make a BS4 object
37
+ soup = BeautifulSoup(response.content, features="html.parser")
38
+ soup.prettify()
39
+
40
+ # Find the table
41
+ table = soup.find("table", class_="table")
42
+
43
+ data = {"bins": []}
44
+
45
+ if table:
46
+ # Extract the column headers (bin names)
47
+ column_headers = [
48
+ header.text.strip()
49
+ for header in table.select("thead th.text-center strong")
50
+ ]
51
+
52
+ # Extract the rows containing collection information
53
+ collection_rows = table.select("tbody tr")
54
+
55
+ # Create a dictionary to store the next date for each bin
56
+ next_collection_dates = {bin: None for bin in column_headers}
57
+
58
+ # Iterate through the rows
59
+ for row in collection_rows:
60
+ # Get the date from the first cell
61
+ date_str = row.find("td").text.strip()
62
+ date_obj = datetime.strptime(date_str, "%A, %d/%m/%Y")
63
+
64
+ # Get the collection information for each bin (td elements with title attribute)
65
+ collection_info = [
66
+ cell["title"] if cell["title"] else "Not Collected"
67
+ for cell in row.select("td.text-center")
68
+ ]
69
+
70
+ # Iterate through each bin type and its collection date
71
+ for bin, status in zip(column_headers, collection_info):
72
+ # If the bin hasn't had a collection date yet or the new date is earlier, update it
73
+ if status != "Not Collected" and (
74
+ not next_collection_dates[bin]
75
+ or date_obj < next_collection_dates[bin]
76
+ ):
77
+ next_collection_dates[bin] = date_obj
78
+
79
+ data["bins"] = [
80
+ {"type": bin, "collectionDate": next_date.strftime(date_format)}
81
+ for bin, next_date in next_collection_dates.items()
82
+ ]
83
+ else:
84
+ print("Table not found in the HTML content.")
85
+ except Exception as e:
86
+ # Here you can log the exception if needed
87
+ print(f"An error occurred: {e}")
88
+ # Optionally, re-raise the exception if you want it to propagate
89
+ raise
90
+ finally:
91
+ # This block ensures that the driver is closed regardless of an exception
92
+ if driver:
93
+ driver.quit()
94
+ return data
@@ -0,0 +1,100 @@
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
+ 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.wait import WebDriverWait
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
+ driver = None
20
+ try:
21
+ data = {"bins": []}
22
+ collections = []
23
+
24
+ user_paon = kwargs.get("paon")
25
+ user_postcode = kwargs.get("postcode")
26
+ web_driver = kwargs.get("web_driver")
27
+ headless = kwargs.get("headless")
28
+ check_paon(user_paon)
29
+ check_postcode(user_postcode)
30
+
31
+ driver = create_webdriver(web_driver, headless)
32
+ # driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))
33
+ driver.get(
34
+ "https://webapps.sunderland.gov.uk/WEBAPPS/WSS/Sunderland_Portal/Forms/bindaychecker.aspx"
35
+ )
36
+
37
+ inputElement_postcode = WebDriverWait(driver, 30).until(
38
+ EC.presence_of_element_located(
39
+ (By.ID, "ContentPlaceHolder1_tbPostCode_controltext")
40
+ )
41
+ )
42
+ inputElement_postcode.send_keys(user_postcode)
43
+
44
+ inputElement_submit_button = WebDriverWait(driver, 30).until(
45
+ EC.element_to_be_clickable((By.ID, "ContentPlaceHolder1_btnLLPG"))
46
+ )
47
+ inputElement_submit_button.click()
48
+
49
+ addressList = WebDriverWait(driver, 30).until(
50
+ EC.presence_of_element_located(
51
+ (By.ID, "ContentPlaceHolder1_ddlAddresses")
52
+ )
53
+ )
54
+ selected_addressList = Select(addressList)
55
+ for idx, addr_option in enumerate(selected_addressList.options):
56
+ option_name = addr_option.accessible_name[0 : len(user_paon)]
57
+ if option_name == user_paon:
58
+ break
59
+ selected_addressList.select_by_index(idx)
60
+
61
+ # Make a BS4 object
62
+ soup = BeautifulSoup(driver.page_source, features="html.parser")
63
+ soup.prettify()
64
+
65
+ household_bin_date = datetime.strptime(
66
+ soup.find("span", {"id": "ContentPlaceHolder1_LabelHouse"}).get_text(
67
+ strip=True
68
+ ),
69
+ "%A %d %B %Y",
70
+ )
71
+ collections.append(("Household bin", household_bin_date))
72
+
73
+ recycling_bin_date = datetime.strptime(
74
+ soup.find("span", {"id": "ContentPlaceHolder1_LabelRecycle"}).get_text(
75
+ strip=True
76
+ ),
77
+ "%A %d %B %Y",
78
+ )
79
+ collections.append(("Recycling bin", recycling_bin_date))
80
+
81
+ ordered_data = sorted(collections, key=lambda x: x[1])
82
+ for item in ordered_data:
83
+ dict_data = {
84
+ "type": item[0].capitalize(),
85
+ "collectionDate": item[1].strftime(date_format),
86
+ }
87
+ data["bins"].append(dict_data)
88
+
89
+ except Exception as e:
90
+ # Here you can log the exception if needed
91
+ print(f"An error occurred: {e}")
92
+ # Optionally, re-raise the exception if you want it to propagate
93
+ raise
94
+
95
+ finally:
96
+ # This block ensures that the driver is closed regardless of an exception
97
+ if driver:
98
+ driver.quit()
99
+
100
+ return data
@@ -0,0 +1,52 @@
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
+
9
+
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
+ # Get postcode and UPRN from kwargs
19
+ user_postcode = kwargs.get("postcode")
20
+ user_uprn = kwargs.get("uprn")
21
+ check_postcode(user_postcode)
22
+ check_uprn(user_uprn)
23
+
24
+ # Build URL to parse
25
+ council_url = f"https://swale.gov.uk/bins-littering-and-the-environment/bins/collection-days?postcode={user_postcode.replace(' ', '+')}&addresses={user_uprn}&address-submit="
26
+
27
+ # Parse URL and read if connection successful
28
+ requests.packages.urllib3.disable_warnings()
29
+ response = requests.get(council_url, verify=False)
30
+ if response.status_code == 200:
31
+ soup = BeautifulSoup(response.text, features="html.parser")
32
+ soup.prettify()
33
+ else:
34
+ raise ConnectionAbortedError("Could not parse council website.")
35
+
36
+ data = {"bins": []}
37
+
38
+ # Get the collection bullet points on the page and parse them
39
+ form_area = soup.find("form", {"class": "integration bin-lookup"})
40
+ collections = [
41
+ item.text.strip().split(",") for item in form_area.find_all("li")
42
+ ]
43
+ for c in collections:
44
+ bin_type = c[0].strip()
45
+ # temp_date = c[2].strip() + " " + str(datetime.now().year)
46
+ bin_date = datetime.strptime(
47
+ c[2].strip() + " " + str(datetime.now().year), "%d %B %Y"
48
+ ).strftime(date_format)
49
+ dict_data = {"type": bin_type, "collectionDate": bin_date}
50
+ data["bins"].append(dict_data)
51
+
52
+ return data
@@ -0,0 +1,62 @@
1
+ import json
2
+ from datetime import datetime, timedelta
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
+ class CouncilClass(AbstractGetBinDataClass):
10
+ def parse_data(self, page: str, **kwargs) -> dict:
11
+ api_url = "http://lite.tameside.gov.uk/BinCollections/CollectionService.svc/GetBinCollection"
12
+ uprn = kwargs.get("uprn")
13
+ check_uprn(uprn)
14
+
15
+ params = {
16
+ "version": "3.1.4",
17
+ "uprn": uprn,
18
+ "token": "",
19
+ "notification": "1",
20
+ "operatingsystemid": "2",
21
+ "testmode": "true",
22
+ }
23
+
24
+ headers = {"content-type": "text/plain"}
25
+
26
+ requests.packages.urllib3.disable_warnings()
27
+ response = requests.post(api_url, json=params, headers=headers)
28
+
29
+ json_response = json.loads(response.content)["GetBinCollectionResult"]["Data"]
30
+
31
+ today = datetime.today()
32
+ eight_weeks = datetime.today() + timedelta(days=8 * 7)
33
+ data = {"bins": []}
34
+ collection_tuple = []
35
+
36
+ bin_friendly_names = {
37
+ "2": "Blue Bin",
38
+ "6": "Green Bin",
39
+ "5": "Black Bin",
40
+ "3": "Brown Bin",
41
+ }
42
+
43
+ for item in json_response:
44
+ collection_date = datetime.strptime(
45
+ item.get("CollectionDate"), "%d/%m/%Y %H:%M:%S"
46
+ )
47
+ if today.date() <= collection_date.date() <= eight_weeks.date():
48
+ bin_type = bin_friendly_names.get(item.get("BinType"))
49
+ collection_tuple.append(
50
+ (bin_type, collection_date.strftime(date_format))
51
+ )
52
+
53
+ ordered_data = sorted(collection_tuple, key=lambda x: x[1])
54
+
55
+ for item in ordered_data:
56
+ dict_data = {
57
+ "type": item[0],
58
+ "collectionDate": item[1],
59
+ }
60
+ data["bins"].append(dict_data)
61
+
62
+ return data
@@ -0,0 +1,60 @@
1
+ import requests
2
+ import json
3
+ import urllib.parse
4
+ from dateutil.relativedelta import relativedelta
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
+ user_uprn = kwargs.get("uprn")
20
+ check_uprn(user_uprn)
21
+
22
+ data = {"bins": []}
23
+
24
+ headers = {
25
+ "Accept": "application/json, text/javascript, */*; q=0.01",
26
+ "Accept-Language": "en-GB,en;q=0.9",
27
+ "Cache-Control": "no-cache",
28
+ "Connection": "keep-alive",
29
+ # Already added when you pass json=
30
+ # 'Content-Type': 'application/json',
31
+ # 'Cookie': 'ASP.NET_SessionId=n2kxv5ssap4gobb11va1oxge',
32
+ "Origin": "https://tdcws01.tandridge.gov.uk",
33
+ "Pragma": "no-cache",
34
+ "Referer": "https://tdcws01.tandridge.gov.uk/TDCWebAppsPublic/tfaBranded/408?utm_source=pressrelease&utm_medium=smposts&utm_campaign=check_my_bin_day",
35
+ "Sec-Fetch-Dest": "empty",
36
+ "Sec-Fetch-Mode": "cors",
37
+ "Sec-Fetch-Site": "same-origin",
38
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.186 Safari/537.36",
39
+ }
40
+
41
+ params = {
42
+ "UPRN": f"{user_uprn}",
43
+ }
44
+
45
+ json_data = requests.post(
46
+ "https://tdcws01.tandridge.gov.uk/TDCWebAppsPublic/TDCMiddleware/RESTAPI/WhiteSpaceAPI/GetCompleteRecordByUPRN",
47
+ headers=headers,
48
+ json=params,
49
+ ).json()["lstNextCollections"]
50
+
51
+ for item in json_data:
52
+ dict_data = {
53
+ "type": item.get("Service").replace("Collection Service", "").strip(),
54
+ "collectionDate": datetime.strptime(
55
+ item.get("Date"), "%d/%m/%Y %H:%M:%S"
56
+ ).strftime(date_format),
57
+ }
58
+ data["bins"].append(dict_data)
59
+
60
+ return data
@@ -0,0 +1,50 @@
1
+ import requests
2
+ import json
3
+
4
+ from dateutil.relativedelta import relativedelta
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
+ user_uprn = kwargs.get("uprn")
20
+ check_uprn(user_uprn)
21
+
22
+ data = {"bins": []}
23
+ collections = []
24
+ api_url = f"https://dac.telford.gov.uk/BinDayFinder/Find/PropertySearch?uprn={user_uprn}"
25
+
26
+ response = requests.get(api_url)
27
+ if response.status_code != 200:
28
+ raise ConnectionError("Could not get latest data!")
29
+
30
+ json_data = json.loads(response.text.replace("\\", "")[1:-1])["bincollections"]
31
+ for item in json_data:
32
+ collection_date = datetime.strptime(
33
+ remove_ordinal_indicator_from_date_string(item.get("nextDate")),
34
+ "%A %d %B",
35
+ )
36
+ next_collection = collection_date.replace(year=datetime.now().year)
37
+ if datetime.now().month == 12 and next_collection.month == 1:
38
+ next_collection = next_collection + relativedelta(years=1)
39
+
40
+ collections.append((item.get("name"), next_collection))
41
+
42
+ ordered_data = sorted(collections, key=lambda x: x[1])
43
+ for item in ordered_data:
44
+ dict_data = {
45
+ "type": item[0],
46
+ "collectionDate": item[1].strftime(date_format),
47
+ }
48
+ data["bins"].append(dict_data)
49
+
50
+ return data