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.
- uk_bin_collection/README.rst +0 -0
- uk_bin_collection/tests/council_feature_input_parity.py +79 -0
- uk_bin_collection/tests/features/environment.py +7 -0
- uk_bin_collection/tests/features/validate_council_outputs.feature +767 -0
- uk_bin_collection/tests/input.json +1077 -0
- uk_bin_collection/tests/output.schema +41 -0
- uk_bin_collection/tests/step_defs/step_helpers/file_handler.py +46 -0
- uk_bin_collection/tests/step_defs/test_validate_council.py +87 -0
- uk_bin_collection/tests/test_collect_data.py +104 -0
- uk_bin_collection/tests/test_common_functions.py +342 -0
- uk_bin_collection/uk_bin_collection/collect_data.py +133 -0
- uk_bin_collection/uk_bin_collection/common.py +292 -0
- uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py +43 -0
- uk_bin_collection/uk_bin_collection/councils/ArunCouncil.py +97 -0
- uk_bin_collection/uk_bin_collection/councils/AylesburyValeCouncil.py +69 -0
- uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py +51 -0
- uk_bin_collection/uk_bin_collection/councils/BarnetCouncil.py +180 -0
- uk_bin_collection/uk_bin_collection/councils/BarnsleyMBCouncil.py +109 -0
- uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py +72 -0
- uk_bin_collection/uk_bin_collection/councils/BathAndNorthEastSomersetCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/BedfordBoroughCouncil.py +49 -0
- uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py +70 -0
- uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py +147 -0
- uk_bin_collection/uk_bin_collection/councils/BirminghamCityCouncil.py +119 -0
- uk_bin_collection/uk_bin_collection/councils/BlackburnCouncil.py +105 -0
- uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py +104 -0
- uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py +103 -0
- uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py +137 -0
- uk_bin_collection/uk_bin_collection/councils/BristolCityCouncil.py +141 -0
- uk_bin_collection/uk_bin_collection/councils/BromleyBoroughCouncil.py +115 -0
- uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py +107 -0
- uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py +95 -0
- uk_bin_collection/uk_bin_collection/councils/BuryCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/CalderdaleCouncil.py +123 -0
- uk_bin_collection/uk_bin_collection/councils/CannockChaseDistrictCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/CardiffCouncil.py +172 -0
- uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py +96 -0
- uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py +54 -0
- uk_bin_collection/uk_bin_collection/councils/ChelmsfordCityCouncil.py +127 -0
- uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py +32 -0
- uk_bin_collection/uk_bin_collection/councils/CheshireWestAndChesterCouncil.py +125 -0
- uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/ConwyCountyBorough.py +27 -0
- uk_bin_collection/uk_bin_collection/councils/CrawleyBoroughCouncil.py +61 -0
- uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py +291 -0
- uk_bin_collection/uk_bin_collection/councils/DerbyshireDalesDistrictCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/DoncasterCouncil.py +77 -0
- uk_bin_collection/uk_bin_collection/councils/DorsetCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py +41 -0
- uk_bin_collection/uk_bin_collection/councils/DurhamCouncil.py +49 -0
- uk_bin_collection/uk_bin_collection/councils/EastCambridgeshireCouncil.py +44 -0
- uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py +74 -0
- uk_bin_collection/uk_bin_collection/councils/EastLindseyDistrictCouncil.py +108 -0
- uk_bin_collection/uk_bin_collection/councils/EastRidingCouncil.py +142 -0
- uk_bin_collection/uk_bin_collection/councils/EastSuffolkCouncil.py +112 -0
- uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py +70 -0
- uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py +48 -0
- uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py +61 -0
- uk_bin_collection/uk_bin_collection/councils/FenlandDistrictCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/ForestOfDeanDistrictCouncil.py +113 -0
- uk_bin_collection/uk_bin_collection/councils/GatesheadCouncil.py +118 -0
- uk_bin_collection/uk_bin_collection/councils/GedlingBoroughCouncil.py +1580 -0
- uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py +55 -0
- uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py +150 -0
- uk_bin_collection/uk_bin_collection/councils/HaltonBoroughCouncil.py +142 -0
- uk_bin_collection/uk_bin_collection/councils/HaringeyCouncil.py +59 -0
- uk_bin_collection/uk_bin_collection/councils/HarrogateBoroughCouncil.py +63 -0
- uk_bin_collection/uk_bin_collection/councils/HighPeakCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/HullCityCouncil.py +48 -0
- uk_bin_collection/uk_bin_collection/councils/HuntingdonDistrictCouncil.py +44 -0
- uk_bin_collection/uk_bin_collection/councils/KingstonUponThamesCouncil.py +84 -0
- uk_bin_collection/uk_bin_collection/councils/KirkleesCouncil.py +130 -0
- uk_bin_collection/uk_bin_collection/councils/KnowsleyMBCouncil.py +139 -0
- uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py +71 -0
- uk_bin_collection/uk_bin_collection/councils/LeedsCityCouncil.py +137 -0
- uk_bin_collection/uk_bin_collection/councils/LisburnCastlereaghCityCouncil.py +101 -0
- uk_bin_collection/uk_bin_collection/councils/LiverpoolCityCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/LondonBoroughHounslow.py +82 -0
- uk_bin_collection/uk_bin_collection/councils/LondonBoroughRedbridge.py +161 -0
- uk_bin_collection/uk_bin_collection/councils/MaldonDistrictCouncil.py +52 -0
- uk_bin_collection/uk_bin_collection/councils/MalvernHillsDC.py +57 -0
- uk_bin_collection/uk_bin_collection/councils/ManchesterCityCouncil.py +106 -0
- uk_bin_collection/uk_bin_collection/councils/MansfieldDistrictCouncil.py +38 -0
- uk_bin_collection/uk_bin_collection/councils/MertonCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/MidAndEastAntrimBoroughCouncil.py +128 -0
- uk_bin_collection/uk_bin_collection/councils/MidSussexDistrictCouncil.py +80 -0
- uk_bin_collection/uk_bin_collection/councils/MiltonKeynesCityCouncil.py +54 -0
- uk_bin_collection/uk_bin_collection/councils/MoleValleyDistrictCouncil.py +98 -0
- uk_bin_collection/uk_bin_collection/councils/NeathPortTalbotCouncil.py +139 -0
- uk_bin_collection/uk_bin_collection/councils/NewarkAndSherwoodDC.py +52 -0
- uk_bin_collection/uk_bin_collection/councils/NewcastleCityCouncil.py +57 -0
- uk_bin_collection/uk_bin_collection/councils/NewhamCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py +203 -0
- uk_bin_collection/uk_bin_collection/councils/NorthEastDerbyshireDistrictCouncil.py +115 -0
- uk_bin_collection/uk_bin_collection/councils/NorthEastLincs.py +53 -0
- uk_bin_collection/uk_bin_collection/councils/NorthKestevenDistrictCouncil.py +45 -0
- uk_bin_collection/uk_bin_collection/councils/NorthLanarkshireCouncil.py +46 -0
- uk_bin_collection/uk_bin_collection/councils/NorthLincolnshireCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/NorthNorfolkDistrictCouncil.py +108 -0
- uk_bin_collection/uk_bin_collection/councils/NorthNorthamptonshireCouncil.py +72 -0
- uk_bin_collection/uk_bin_collection/councils/NorthSomersetCouncil.py +76 -0
- uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py +220 -0
- uk_bin_collection/uk_bin_collection/councils/NorthWestLeicestershire.py +114 -0
- uk_bin_collection/uk_bin_collection/councils/NorthYorkshire.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +123 -0
- uk_bin_collection/uk_bin_collection/councils/NottinghamCityCouncil.py +36 -0
- uk_bin_collection/uk_bin_collection/councils/OldhamCouncil.py +51 -0
- uk_bin_collection/uk_bin_collection/councils/PortsmouthCityCouncil.py +131 -0
- uk_bin_collection/uk_bin_collection/councils/PrestonCityCouncil.py +97 -0
- uk_bin_collection/uk_bin_collection/councils/ReadingBoroughCouncil.py +30 -0
- uk_bin_collection/uk_bin_collection/councils/ReigateAndBansteadBoroughCouncil.py +81 -0
- uk_bin_collection/uk_bin_collection/councils/RenfrewshireCouncil.py +135 -0
- uk_bin_collection/uk_bin_collection/councils/RhonddaCynonTaffCouncil.py +80 -0
- uk_bin_collection/uk_bin_collection/councils/RochdaleCouncil.py +69 -0
- uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py +60 -0
- uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py +93 -0
- uk_bin_collection/uk_bin_collection/councils/RushcliffeBoroughCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py +81 -0
- uk_bin_collection/uk_bin_collection/councils/SalfordCityCouncil.py +70 -0
- uk_bin_collection/uk_bin_collection/councils/SevenoaksDistrictCouncil.py +106 -0
- uk_bin_collection/uk_bin_collection/councils/SheffieldCityCouncil.py +54 -0
- uk_bin_collection/uk_bin_collection/councils/ShropshireCouncil.py +45 -0
- uk_bin_collection/uk_bin_collection/councils/SolihullCouncil.py +48 -0
- uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py +203 -0
- uk_bin_collection/uk_bin_collection/councils/SouthAyrshireCouncil.py +73 -0
- uk_bin_collection/uk_bin_collection/councils/SouthCambridgeshireCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/SouthGloucestershireCouncil.py +74 -0
- uk_bin_collection/uk_bin_collection/councils/SouthLanarkshireCouncil.py +78 -0
- uk_bin_collection/uk_bin_collection/councils/SouthNorfolkCouncil.py +91 -0
- uk_bin_collection/uk_bin_collection/councils/SouthOxfordshireCouncil.py +93 -0
- uk_bin_collection/uk_bin_collection/councils/SouthTynesideCouncil.py +98 -0
- uk_bin_collection/uk_bin_collection/councils/StAlbansCityAndDistrictCouncil.py +43 -0
- uk_bin_collection/uk_bin_collection/councils/StHelensBC.py +56 -0
- uk_bin_collection/uk_bin_collection/councils/StaffordshireMoorlandsDistrictCouncil.py +112 -0
- uk_bin_collection/uk_bin_collection/councils/StockportBoroughCouncil.py +39 -0
- uk_bin_collection/uk_bin_collection/councils/StokeOnTrentCityCouncil.py +79 -0
- uk_bin_collection/uk_bin_collection/councils/StratfordUponAvonCouncil.py +94 -0
- uk_bin_collection/uk_bin_collection/councils/SunderlandCityCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/SwaleBoroughCouncil.py +52 -0
- uk_bin_collection/uk_bin_collection/councils/TamesideMBCouncil.py +62 -0
- uk_bin_collection/uk_bin_collection/councils/TandridgeDistrictCouncil.py +60 -0
- uk_bin_collection/uk_bin_collection/councils/TelfordAndWrekinCouncil.py +50 -0
- uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py +203 -0
- uk_bin_collection/uk_bin_collection/councils/TonbridgeAndMallingBC.py +101 -0
- uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py +51 -0
- uk_bin_collection/uk_bin_collection/councils/TorridgeDistrictCouncil.py +154 -0
- uk_bin_collection/uk_bin_collection/councils/ValeofGlamorganCouncil.py +119 -0
- uk_bin_collection/uk_bin_collection/councils/ValeofWhiteHorseCouncil.py +103 -0
- uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py +89 -0
- uk_bin_collection/uk_bin_collection/councils/WarwickDistrictCouncil.py +34 -0
- uk_bin_collection/uk_bin_collection/councils/WaverleyBoroughCouncil.py +119 -0
- uk_bin_collection/uk_bin_collection/councils/WealdenDistrictCouncil.py +86 -0
- uk_bin_collection/uk_bin_collection/councils/WelhatCouncil.py +73 -0
- uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/WestLindseyDistrictCouncil.py +118 -0
- uk_bin_collection/uk_bin_collection/councils/WestLothianCouncil.py +103 -0
- uk_bin_collection/uk_bin_collection/councils/WestNorthamptonshireCouncil.py +34 -0
- uk_bin_collection/uk_bin_collection/councils/WestSuffolkCouncil.py +64 -0
- uk_bin_collection/uk_bin_collection/councils/WiganBoroughCouncil.py +97 -0
- uk_bin_collection/uk_bin_collection/councils/WiltshireCouncil.py +135 -0
- uk_bin_collection/uk_bin_collection/councils/WindsorAndMaidenheadCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/WokingBoroughCouncil.py +114 -0
- uk_bin_collection/uk_bin_collection/councils/WyreCouncil.py +89 -0
- uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py +45 -0
- uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py +33 -0
- uk_bin_collection/uk_bin_collection/get_bin_data.py +165 -0
- uk_bin_collection-0.74.0.dist-info/LICENSE +21 -0
- uk_bin_collection-0.74.0.dist-info/METADATA +247 -0
- uk_bin_collection-0.74.0.dist-info/RECORD +171 -0
- uk_bin_collection-0.74.0.dist-info/WHEEL +4 -0
- uk_bin_collection-0.74.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
import re
|
2
|
+
import requests
|
3
|
+
from bs4 import BeautifulSoup
|
4
|
+
|
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
|
+
# This script pulls (in one hit) the data from Bromley Council Bins Data
|
9
|
+
import datetime
|
10
|
+
from datetime import datetime
|
11
|
+
from selenium.webdriver.common.by import By
|
12
|
+
from selenium.webdriver.support import expected_conditions as EC
|
13
|
+
from selenium.webdriver.support.ui import Select
|
14
|
+
from selenium.webdriver.support.wait import WebDriverWait
|
15
|
+
from selenium.webdriver.common.keys import Keys
|
16
|
+
import time
|
17
|
+
|
18
|
+
|
19
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
20
|
+
class CouncilClass(AbstractGetBinDataClass):
|
21
|
+
"""
|
22
|
+
Concrete classes have to implement all abstract operations of the
|
23
|
+
base class. They can also override some operations with a default
|
24
|
+
implementation.
|
25
|
+
"""
|
26
|
+
|
27
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
28
|
+
driver = None
|
29
|
+
try:
|
30
|
+
data = {"bins": []}
|
31
|
+
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"}
|
32
|
+
|
33
|
+
uprn = kwargs.get("uprn")
|
34
|
+
user_paon = kwargs.get("paon")
|
35
|
+
postcode = kwargs.get("postcode")
|
36
|
+
web_driver = kwargs.get("web_driver")
|
37
|
+
headless = kwargs.get("headless")
|
38
|
+
driver = create_webdriver(web_driver, headless)
|
39
|
+
driver.get(kwargs.get("url"))
|
40
|
+
|
41
|
+
wait = WebDriverWait(driver, 60)
|
42
|
+
post_code_search = wait.until(
|
43
|
+
EC.presence_of_element_located((By.CLASS_NAME, "form-control"))
|
44
|
+
)
|
45
|
+
post_code_search.send_keys(postcode)
|
46
|
+
|
47
|
+
submit_btn = wait.until(
|
48
|
+
EC.presence_of_element_located(
|
49
|
+
(By.XPATH, f"//button[contains(@class, 'mx-name-actionButton3')]")
|
50
|
+
)
|
51
|
+
)
|
52
|
+
|
53
|
+
submit_btn.send_keys(Keys.ENTER)
|
54
|
+
|
55
|
+
dropdown_options = wait.until(
|
56
|
+
EC.presence_of_element_located(
|
57
|
+
(By.XPATH, f'//option[contains(text(), "{user_paon}")]')
|
58
|
+
)
|
59
|
+
)
|
60
|
+
parent_element = dropdown_options.find_element(
|
61
|
+
By.XPATH, ".."
|
62
|
+
) # Using ".." to move up to the parent element
|
63
|
+
|
64
|
+
# Create a 'Select' for it, then select the first address in the list
|
65
|
+
# (Index 0 is "Make a selection from the list")
|
66
|
+
dropdownSelect = Select(parent_element)
|
67
|
+
dropdownSelect.select_by_visible_text(str(user_paon))
|
68
|
+
|
69
|
+
submit_btn = wait.until(
|
70
|
+
EC.presence_of_element_located(
|
71
|
+
(By.XPATH, f"//button[contains(@class, 'mx-name-actionButton5')]")
|
72
|
+
)
|
73
|
+
)
|
74
|
+
|
75
|
+
submit_btn.send_keys(Keys.ENTER)
|
76
|
+
|
77
|
+
results = wait.until(
|
78
|
+
EC.presence_of_element_located(
|
79
|
+
(By.XPATH, f'//span[contains(@class,"collection-sub")]')
|
80
|
+
)
|
81
|
+
)
|
82
|
+
|
83
|
+
# Make a BS4 object
|
84
|
+
soup = BeautifulSoup(driver.page_source, features="html.parser")
|
85
|
+
# Initialize current month and year (you can modify these values based on your requirement)
|
86
|
+
data = {"bins": []}
|
87
|
+
|
88
|
+
current_date = datetime.now()
|
89
|
+
|
90
|
+
# Find all elements with class starting with 'mx-name-index-'
|
91
|
+
bins = soup.find_all(class_=lambda x: x and x.startswith("mx-name-index-"))
|
92
|
+
|
93
|
+
for bin_item in bins:
|
94
|
+
bin_type = bin_item.find(class_="collection-main").text.strip()
|
95
|
+
day_of_week_elements = bin_item.find_all(class_="collection-header")
|
96
|
+
bin_date = None
|
97
|
+
|
98
|
+
for elem in day_of_week_elements:
|
99
|
+
if (
|
100
|
+
elem.text.strip() != bin_type
|
101
|
+
): # Avoid taking the bin type as the date
|
102
|
+
next_sibling = elem.find_next_sibling()
|
103
|
+
if next_sibling:
|
104
|
+
bin_date_str = next_sibling.text.strip()
|
105
|
+
try:
|
106
|
+
# Try parsing the date string in the format 'dd Month' (e.g., '30 Dec', '5 January')
|
107
|
+
bin_date = datetime.strptime(bin_date_str, "%d %b")
|
108
|
+
except ValueError:
|
109
|
+
try:
|
110
|
+
# If the above format fails, try 'dd MonthName' (e.g., '30 December', '5 January')
|
111
|
+
bin_date = datetime.strptime(bin_date_str, "%d %B")
|
112
|
+
except ValueError:
|
113
|
+
pass
|
114
|
+
|
115
|
+
if bin_date:
|
116
|
+
# Set the year based on the logic provided
|
117
|
+
if bin_date.month < current_date.month:
|
118
|
+
bin_date = bin_date.replace(
|
119
|
+
year=current_date.year + 1
|
120
|
+
)
|
121
|
+
else:
|
122
|
+
bin_date = bin_date.replace(year=current_date.year)
|
123
|
+
# Format the date to the desired format
|
124
|
+
bin_date = bin_date.strftime("%d/%m/%Y")
|
125
|
+
break
|
126
|
+
dict_data = {"type": bin_type, "collectionDate": bin_date}
|
127
|
+
data["bins"].append(dict_data)
|
128
|
+
except Exception as e:
|
129
|
+
# Here you can log the exception if needed
|
130
|
+
print(f"An error occurred: {e}")
|
131
|
+
# Optionally, re-raise the exception if you want it to propagate
|
132
|
+
raise
|
133
|
+
finally:
|
134
|
+
# This block ensures that the driver is closed regardless of an exception
|
135
|
+
if driver:
|
136
|
+
driver.quit()
|
137
|
+
return data
|
@@ -0,0 +1,141 @@
|
|
1
|
+
import ast
|
2
|
+
|
3
|
+
import requests
|
4
|
+
from bs4 import BeautifulSoup
|
5
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
6
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
7
|
+
|
8
|
+
|
9
|
+
# 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
|
+
user_uprn = kwargs.get("uprn")
|
19
|
+
check_uprn(user_uprn)
|
20
|
+
|
21
|
+
requests.packages.urllib3.disable_warnings()
|
22
|
+
s = requests.session()
|
23
|
+
|
24
|
+
service_type_headers = {
|
25
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,"
|
26
|
+
"image/webp,image/apng,*/*;q=0.8",
|
27
|
+
"Accept-Language": "en-GB,en;q=0.9",
|
28
|
+
"Cache-Control": "max-age=0",
|
29
|
+
"Connection": "keep-alive",
|
30
|
+
"Referer": "https://www.bristol.gov.uk/",
|
31
|
+
"Sec-Fetch-Dest": "document",
|
32
|
+
"Sec-Fetch-Mode": "navigate",
|
33
|
+
"Sec-Fetch-Site": "cross-site",
|
34
|
+
"Sec-Fetch-User": "?1",
|
35
|
+
"Sec-GPC": "1",
|
36
|
+
"Upgrade-Insecure-Requests": "1",
|
37
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
|
38
|
+
"like Gecko) Chrome/105.0.0.0 Safari/537.36",
|
39
|
+
}
|
40
|
+
service_type_params = {
|
41
|
+
"servicetypeid": "7dce896c-b3ba-ea11-a812-000d3a7f1cdc",
|
42
|
+
}
|
43
|
+
response = s.get(
|
44
|
+
"https://bristolcouncil.powerappsportals.com/completedynamicformunauth/",
|
45
|
+
params=service_type_params,
|
46
|
+
headers=service_type_headers,
|
47
|
+
)
|
48
|
+
|
49
|
+
llpg_headers = {
|
50
|
+
"Accept": "*/*",
|
51
|
+
"Accept-Language": "en-GB,en;q=0.9",
|
52
|
+
"Connection": "keep-alive",
|
53
|
+
"Ocp-Apim-Subscription-Key": "47ffd667d69c4a858f92fc38dc24b150",
|
54
|
+
"Ocp-Apim-Trace": "true",
|
55
|
+
"Origin": "https://bristolcouncil.powerappsportals.com",
|
56
|
+
"Referer": "https://bristolcouncil.powerappsportals.com/",
|
57
|
+
"Sec-Fetch-Dest": "empty",
|
58
|
+
"Sec-Fetch-Mode": "cors",
|
59
|
+
"Sec-Fetch-Site": "cross-site",
|
60
|
+
"Sec-GPC": "1",
|
61
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
|
62
|
+
"like Gecko) Chrome/105.0.0.0 Safari/537.36",
|
63
|
+
}
|
64
|
+
llpg_uprn = "UPRN" + user_uprn
|
65
|
+
llpg_json_data = {
|
66
|
+
"Uprn": llpg_uprn,
|
67
|
+
}
|
68
|
+
response = s.post(
|
69
|
+
"https://bcprdapidyna002.azure-api.net/bcprdfundyna001-llpg/DetailedLLPG",
|
70
|
+
headers=llpg_headers,
|
71
|
+
json=llpg_json_data,
|
72
|
+
)
|
73
|
+
|
74
|
+
headers = {
|
75
|
+
"Accept": "*/*",
|
76
|
+
"Accept-Language": "en-GB,en;q=0.9",
|
77
|
+
"Connection": "keep-alive",
|
78
|
+
# Already added when you pass json=
|
79
|
+
# 'Content-Type': 'application/json',
|
80
|
+
"Ocp-Apim-Subscription-Key": "47ffd667d69c4a858f92fc38dc24b150",
|
81
|
+
"Ocp-Apim-Trace": "true",
|
82
|
+
"Origin": "https://bristolcouncil.powerappsportals.com",
|
83
|
+
"Referer": "https://bristolcouncil.powerappsportals.com/",
|
84
|
+
"Sec-Fetch-Dest": "empty",
|
85
|
+
"Sec-Fetch-Mode": "cors",
|
86
|
+
"Sec-Fetch-Site": "cross-site",
|
87
|
+
"Sec-GPC": "1",
|
88
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
|
89
|
+
}
|
90
|
+
json_data = {
|
91
|
+
"uprn": user_uprn,
|
92
|
+
}
|
93
|
+
response = s.post(
|
94
|
+
"https://bcprdapidyna002.azure-api.net/bcprdfundyna001-alloy/NextCollectionDates",
|
95
|
+
headers=headers,
|
96
|
+
json=json_data,
|
97
|
+
)
|
98
|
+
|
99
|
+
# Make a BS4 object
|
100
|
+
soup = BeautifulSoup(response.text, features="html.parser")
|
101
|
+
soup.prettify()
|
102
|
+
|
103
|
+
# Soup returns API response rather than HTML, so parse those strings
|
104
|
+
string_data = soup.text.split("data")[1]
|
105
|
+
collection_data = string_data.split("]}")
|
106
|
+
|
107
|
+
# Remove the spare ] and , characters at the of each list element
|
108
|
+
fixed_data = [i[1:] for i in collection_data]
|
109
|
+
|
110
|
+
# Remove the last list element since it's garbage (funny since this is a bin project)
|
111
|
+
fixed_data.pop()
|
112
|
+
collection_data.clear()
|
113
|
+
|
114
|
+
# Make some more changes:
|
115
|
+
idx = 0
|
116
|
+
for i in fixed_data:
|
117
|
+
if idx == 0:
|
118
|
+
# Remove two extra characters if it's the first element
|
119
|
+
i = i[2:]
|
120
|
+
# Append some characters to the end of each line to make to dict
|
121
|
+
i = i + "]}"
|
122
|
+
idx += 1
|
123
|
+
# Reuse the collection_data list to make a list of dictionaries - one for each bin
|
124
|
+
collection_data.append(ast.literal_eval(i))
|
125
|
+
|
126
|
+
collections = []
|
127
|
+
for bin in collection_data:
|
128
|
+
bin_type = bin["containerName"]
|
129
|
+
next_collection = datetime.strptime(
|
130
|
+
bin["collection"][0]["nextCollectionDate"], "%Y-%m-%dT%H:%M:%S"
|
131
|
+
).strftime(date_format)
|
132
|
+
# Could work out next date using the roundDescription and the is_holiday function in common.py
|
133
|
+
collections.append((bin_type, next_collection))
|
134
|
+
|
135
|
+
ordered_data = sorted(collections, key=lambda x: x[1])
|
136
|
+
data = {"bins": []}
|
137
|
+
for item in ordered_data:
|
138
|
+
dict_data = {"type": item[0], "collectionDate": item[1]}
|
139
|
+
data["bins"].append(dict_data)
|
140
|
+
|
141
|
+
return data
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# This script pulls (in one hit) the data from Bromley Council Bins Data
|
2
|
+
import datetime
|
3
|
+
from bs4 import BeautifulSoup
|
4
|
+
from datetime import datetime
|
5
|
+
from selenium.webdriver.common.by import By
|
6
|
+
from selenium.webdriver.support import expected_conditions as EC
|
7
|
+
from selenium.webdriver.support.ui import Select
|
8
|
+
from selenium.webdriver.support.wait import WebDriverWait
|
9
|
+
from selenium.webdriver.common.keys import Keys
|
10
|
+
import time
|
11
|
+
|
12
|
+
from dateutil.relativedelta import relativedelta
|
13
|
+
from bs4 import BeautifulSoup
|
14
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
15
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
16
|
+
|
17
|
+
|
18
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
19
|
+
class CouncilClass(AbstractGetBinDataClass):
|
20
|
+
"""
|
21
|
+
Concrete classes have to implement all abstract operations of the
|
22
|
+
base class. They can also override some operations with a default
|
23
|
+
implementation.
|
24
|
+
"""
|
25
|
+
|
26
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
27
|
+
# Make a BS4 object
|
28
|
+
driver = None
|
29
|
+
try:
|
30
|
+
bin_data_dict = {"bins": []}
|
31
|
+
collections = []
|
32
|
+
web_driver = kwargs.get("web_driver")
|
33
|
+
headless = kwargs.get("headless")
|
34
|
+
|
35
|
+
data = {"bins": []}
|
36
|
+
|
37
|
+
# Get our initial session running
|
38
|
+
driver = create_webdriver(web_driver, headless)
|
39
|
+
driver.get(kwargs.get("url"))
|
40
|
+
|
41
|
+
wait = WebDriverWait(driver, 30)
|
42
|
+
results = wait.until(
|
43
|
+
EC.presence_of_element_located((By.CLASS_NAME, "waste-service-image"))
|
44
|
+
)
|
45
|
+
# Search for the specific bins in the table using BS
|
46
|
+
# Parse the HTML content
|
47
|
+
# Find all elements with the class 'container-name' to extract bin types
|
48
|
+
# Parse the HTML content
|
49
|
+
soup = BeautifulSoup(driver.page_source, "html.parser")
|
50
|
+
soup.prettify
|
51
|
+
|
52
|
+
# Find all elements with class 'govuk-summary-list'
|
53
|
+
bin_info = []
|
54
|
+
waste_services = soup.find_all(
|
55
|
+
"h3", class_="govuk-heading-m waste-service-name"
|
56
|
+
)
|
57
|
+
|
58
|
+
for service in waste_services:
|
59
|
+
service_title = service.get_text(strip=True)
|
60
|
+
next_collection = service.find_next_sibling().find(
|
61
|
+
"dt", text="Next collection"
|
62
|
+
)
|
63
|
+
|
64
|
+
if next_collection:
|
65
|
+
next_collection_date = next_collection.find_next_sibling().get_text(
|
66
|
+
strip=True
|
67
|
+
)
|
68
|
+
# Extract date part and remove the suffix
|
69
|
+
next_collection_date_parse = next_collection_date.split(",")[
|
70
|
+
1
|
71
|
+
].strip()
|
72
|
+
day = next_collection_date_parse.split()[0]
|
73
|
+
month = next_collection_date_parse.split()[1]
|
74
|
+
|
75
|
+
# Remove the suffix (e.g., 'th', 'nd', 'rd', 'st') from the day
|
76
|
+
if day.endswith(("th", "nd", "rd", "st")):
|
77
|
+
day = day[:-2] # Remove the last two characters
|
78
|
+
|
79
|
+
# Reconstruct the date string without the suffix
|
80
|
+
date_without_suffix = f"{day} {month}"
|
81
|
+
|
82
|
+
# Parse the date string to a datetime object
|
83
|
+
date_object = datetime.strptime(date_without_suffix, "%d %B")
|
84
|
+
|
85
|
+
# Get the current year
|
86
|
+
current_year = datetime.now().year
|
87
|
+
|
88
|
+
# Check if the parsed date is in the past compared to the current date
|
89
|
+
if date_object < datetime.now():
|
90
|
+
# If the parsed date is in the past, assume it's for the next year
|
91
|
+
current_year += 1
|
92
|
+
# Append the year to the date
|
93
|
+
date_with_year = date_object.replace(year=current_year)
|
94
|
+
|
95
|
+
# Format the date with the year
|
96
|
+
date_with_year_formatted = date_with_year.strftime(
|
97
|
+
"%d/%m/%Y"
|
98
|
+
) # Format the date as '%d/%m/%Y'
|
99
|
+
|
100
|
+
# Create the dictionary with the formatted data
|
101
|
+
dict_data = {
|
102
|
+
"type": service_title,
|
103
|
+
"collectionDate": date_with_year_formatted,
|
104
|
+
}
|
105
|
+
data["bins"].append(dict_data)
|
106
|
+
except Exception as e:
|
107
|
+
# Here you can log the exception if needed
|
108
|
+
print(f"An error occurred: {e}")
|
109
|
+
# Optionally, re-raise the exception if you want it to propagate
|
110
|
+
raise
|
111
|
+
finally:
|
112
|
+
# This block ensures that the driver is closed regardless of an exception
|
113
|
+
if driver:
|
114
|
+
driver.quit()
|
115
|
+
return data
|
@@ -0,0 +1,107 @@
|
|
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://selfservice.broxtowe.gov.uk/renderform.aspx?t=217&k=9D2EF214E144EE796430597FB475C3892C43C528"
|
23
|
+
|
24
|
+
data = {"bins": []}
|
25
|
+
|
26
|
+
user_uprn = kwargs.get("uprn")
|
27
|
+
user_postcode = kwargs.get("postcode")
|
28
|
+
web_driver = kwargs.get("web_driver")
|
29
|
+
headless = kwargs.get("headless")
|
30
|
+
check_uprn(user_uprn)
|
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
|
+
"ctl00_ContentPlaceHolder1_FF5683TB",
|
41
|
+
)
|
42
|
+
inputElement_postcode.send_keys(user_postcode)
|
43
|
+
|
44
|
+
# Click search button
|
45
|
+
driver.find_element(
|
46
|
+
By.ID,
|
47
|
+
"ctl00_ContentPlaceHolder1_FF5683BTN",
|
48
|
+
).click()
|
49
|
+
|
50
|
+
# Wait for the 'Select address' dropdown to appear and select option matching UPRN
|
51
|
+
dropdown = WebDriverWait(driver, 10).until(
|
52
|
+
EC.presence_of_element_located(
|
53
|
+
(By.ID, "ctl00_ContentPlaceHolder1_FF5683DDL")
|
54
|
+
)
|
55
|
+
)
|
56
|
+
# Create a 'Select' for it, then select the matching URPN option
|
57
|
+
dropdownSelect = Select(dropdown)
|
58
|
+
dropdownSelect.select_by_value("U" + user_uprn)
|
59
|
+
|
60
|
+
# Wait for the submit button to appear, then click it to get the collection dates
|
61
|
+
submit = WebDriverWait(driver, 10).until(
|
62
|
+
EC.presence_of_element_located(
|
63
|
+
(By.ID, "ctl00_ContentPlaceHolder1_btnSubmit")
|
64
|
+
)
|
65
|
+
)
|
66
|
+
submit.click()
|
67
|
+
|
68
|
+
soup = BeautifulSoup(driver.page_source, features="html.parser")
|
69
|
+
|
70
|
+
bins_div = soup.find("div", id="ctl00_ContentPlaceHolder1_FF5686FormGroup")
|
71
|
+
if bins_div:
|
72
|
+
bins_table = bins_div.find("table")
|
73
|
+
if bins_table:
|
74
|
+
# Get table rows
|
75
|
+
for row in bins_table.find_all("tr"):
|
76
|
+
# Get the rows cells
|
77
|
+
cells = row.find_all("td")
|
78
|
+
bin_type = cells[0].get_text(strip=True)
|
79
|
+
# Skip header row
|
80
|
+
if bin_type and cells[3] and bin_type != "Bin Type":
|
81
|
+
if len(cells[3].get_text(strip=True)) > 0:
|
82
|
+
collection_date = datetime.strptime(
|
83
|
+
cells[3].get_text(strip=True), "%A, %d %B %Y"
|
84
|
+
)
|
85
|
+
dict_data = {
|
86
|
+
"type": bin_type,
|
87
|
+
"collectionDate": collection_date.strftime(
|
88
|
+
date_format
|
89
|
+
),
|
90
|
+
}
|
91
|
+
data["bins"].append(dict_data)
|
92
|
+
|
93
|
+
data["bins"].sort(
|
94
|
+
key=lambda x: datetime.strptime(
|
95
|
+
x.get("collectionDate"), "%d/%m/%Y"
|
96
|
+
)
|
97
|
+
)
|
98
|
+
except Exception as e:
|
99
|
+
# Here you can log the exception if needed
|
100
|
+
print(f"An error occurred: {e}")
|
101
|
+
# Optionally, re-raise the exception if you want it to propagate
|
102
|
+
raise
|
103
|
+
finally:
|
104
|
+
# This block ensures that the driver is closed regardless of an exception
|
105
|
+
if driver:
|
106
|
+
driver.quit()
|
107
|
+
return data
|
@@ -0,0 +1,95 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
import time
|
3
|
+
from selenium.webdriver.common.by import By
|
4
|
+
from selenium.webdriver.common.keys import Keys
|
5
|
+
from selenium.webdriver.support.ui import Select
|
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
|
+
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 get_data(self, df) -> dict:
|
19
|
+
# Create dictionary of data to be returned
|
20
|
+
data = {"bins": []}
|
21
|
+
|
22
|
+
# Output collection data into dictionary
|
23
|
+
for i, row in df.iterrows():
|
24
|
+
dict_data = {
|
25
|
+
"type": row["Collection Name"],
|
26
|
+
"collectionDate": row["Next Collection Due"],
|
27
|
+
}
|
28
|
+
|
29
|
+
data["bins"].append(dict_data)
|
30
|
+
|
31
|
+
return data
|
32
|
+
|
33
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
34
|
+
driver = None
|
35
|
+
try:
|
36
|
+
page = "https://chiltern.gov.uk/collection-dates"
|
37
|
+
|
38
|
+
# Assign user info
|
39
|
+
user_postcode = kwargs.get("postcode")
|
40
|
+
user_paon = kwargs.get("paon")
|
41
|
+
web_driver = kwargs.get("web_driver")
|
42
|
+
headless = kwargs.get("headless")
|
43
|
+
|
44
|
+
# Create Selenium webdriver
|
45
|
+
driver = create_webdriver(web_driver, headless)
|
46
|
+
driver.get(page)
|
47
|
+
|
48
|
+
# Enter postcode in text box and wait
|
49
|
+
inputElement_pc = driver.find_element(
|
50
|
+
By.ID,
|
51
|
+
"COPYOFECHOCOLLECTIONDATES_ADDRESSSELECTION_ADDRESSSELECTIONPOSTCODE",
|
52
|
+
)
|
53
|
+
inputElement_pc.send_keys(user_postcode)
|
54
|
+
inputElement_pc.send_keys(Keys.ENTER)
|
55
|
+
|
56
|
+
time.sleep(4)
|
57
|
+
|
58
|
+
# Select address from dropdown and wait
|
59
|
+
inputElement_ad = Select(
|
60
|
+
driver.find_element(
|
61
|
+
By.ID,
|
62
|
+
"COPYOFECHOCOLLECTIONDATES_ADDRESSSELECTION_ADDRESSSELECTIONADDRESS",
|
63
|
+
)
|
64
|
+
)
|
65
|
+
|
66
|
+
inputElement_ad.select_by_visible_text(user_paon)
|
67
|
+
|
68
|
+
time.sleep(4)
|
69
|
+
|
70
|
+
# Submit address information and wait
|
71
|
+
inputElement_bn = driver.find_element(
|
72
|
+
By.ID, "COPYOFECHOCOLLECTIONDATES_ADDRESSSELECTION_NAV1_NEXT"
|
73
|
+
).click()
|
74
|
+
|
75
|
+
time.sleep(4)
|
76
|
+
|
77
|
+
# Read next collection information into Pandas
|
78
|
+
table = driver.find_element(
|
79
|
+
By.ID, "COPYOFECHOCOLLECTIONDATES_PAGE1_DATES2"
|
80
|
+
).get_attribute("outerHTML")
|
81
|
+
df = pd.read_html(table, header=[1])
|
82
|
+
df = df[0]
|
83
|
+
|
84
|
+
# Parse data into dict
|
85
|
+
data = self.get_data(df)
|
86
|
+
except Exception as e:
|
87
|
+
# Here you can log the exception if needed
|
88
|
+
print(f"An error occurred: {e}")
|
89
|
+
# Optionally, re-raise the exception if you want it to propagate
|
90
|
+
raise
|
91
|
+
finally:
|
92
|
+
# This block ensures that the driver is closed regardless of an exception
|
93
|
+
if driver:
|
94
|
+
driver.quit()
|
95
|
+
return data
|
@@ -0,0 +1,65 @@
|
|
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
|
+
# Make a BS4 object
|
16
|
+
collections = []
|
17
|
+
data = {"bins": []}
|
18
|
+
|
19
|
+
# Get and check postcode and PAON
|
20
|
+
postcode = kwargs.get("postcode")
|
21
|
+
paon = kwargs.get("paon")
|
22
|
+
check_postcode(postcode)
|
23
|
+
check_paon(paon)
|
24
|
+
|
25
|
+
# Make API call to get property info using postcode
|
26
|
+
addr_response = requests.get(
|
27
|
+
f'https://www.bury.gov.uk/app-services/getProperties?postcode={postcode.replace(" ", "")}'
|
28
|
+
)
|
29
|
+
if addr_response.status_code != 200:
|
30
|
+
raise ConnectionAbortedError("Issue encountered getting addresses.")
|
31
|
+
address_json = json.loads(addr_response.text)["response"]
|
32
|
+
|
33
|
+
# This makes addr the next item that has the house number. Since these are ordered by house number, a single
|
34
|
+
# number like 3 wouldn't return 33
|
35
|
+
addr = next(item for item in address_json if paon in item["addressLine1"])
|
36
|
+
|
37
|
+
# Make API call to get bin data using property ID
|
38
|
+
response = requests.get(
|
39
|
+
f'https://www.bury.gov.uk/app-services/getPropertyById?id={addr.get("id")}'
|
40
|
+
)
|
41
|
+
if response.status_code != 200:
|
42
|
+
raise ConnectionAbortedError("Issue encountered getting bin data.")
|
43
|
+
bin_list = json.loads(response.text)["response"]["bins"]
|
44
|
+
|
45
|
+
# The JSON actually returns the next collections and a large calendar. But I opted just for the next dates.
|
46
|
+
for bin_colour, collection_data in bin_list.items():
|
47
|
+
bin_type = bin_colour
|
48
|
+
bin_date = datetime.strptime(
|
49
|
+
remove_ordinal_indicator_from_date_string(
|
50
|
+
collection_data.get("nextCollection")
|
51
|
+
),
|
52
|
+
"%A %d %B %Y",
|
53
|
+
)
|
54
|
+
collections.append((bin_type, bin_date))
|
55
|
+
|
56
|
+
# Dates are ordered correctly - soonest first
|
57
|
+
ordered_data = sorted(collections, key=lambda x: x[1])
|
58
|
+
for item in ordered_data:
|
59
|
+
dict_data = {
|
60
|
+
"type": item[0],
|
61
|
+
"collectionDate": item[1].strftime(date_format),
|
62
|
+
}
|
63
|
+
data["bins"].append(dict_data)
|
64
|
+
|
65
|
+
return data
|