uk_bin_collection 0.126.1__py3-none-any.whl → 0.127.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of uk_bin_collection might be problematic. Click here for more details.

Files changed (19) hide show
  1. uk_bin_collection/tests/input.json +48 -13
  2. uk_bin_collection/uk_bin_collection/councils/BrentCouncil.py +115 -0
  3. uk_bin_collection/uk_bin_collection/councils/CornwallCouncil.py +9 -4
  4. uk_bin_collection/uk_bin_collection/councils/CoventryCityCouncil.py +1 -1
  5. uk_bin_collection/uk_bin_collection/councils/CumberlandCouncil.py +96 -0
  6. uk_bin_collection/uk_bin_collection/councils/DenbighshireCouncil.py +66 -0
  7. uk_bin_collection/uk_bin_collection/councils/DundeeCityCouncil.py +44 -0
  8. uk_bin_collection/uk_bin_collection/councils/GwyneddCouncil.py +54 -0
  9. uk_bin_collection/uk_bin_collection/councils/KirkleesCouncil.py +63 -130
  10. uk_bin_collection/uk_bin_collection/councils/LondonBoroughSutton.py +1 -1
  11. uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +4 -2
  12. uk_bin_collection/uk_bin_collection/councils/OadbyAndWigstonBoroughCouncil.py +65 -0
  13. uk_bin_collection/uk_bin_collection/councils/WalthamForest.py +2 -2
  14. uk_bin_collection/uk_bin_collection/councils/WestDunbartonshireCouncil.py +66 -0
  15. {uk_bin_collection-0.126.1.dist-info → uk_bin_collection-0.127.0.dist-info}/METADATA +1 -1
  16. {uk_bin_collection-0.126.1.dist-info → uk_bin_collection-0.127.0.dist-info}/RECORD +19 -12
  17. {uk_bin_collection-0.126.1.dist-info → uk_bin_collection-0.127.0.dist-info}/LICENSE +0 -0
  18. {uk_bin_collection-0.126.1.dist-info → uk_bin_collection-0.127.0.dist-info}/WHEEL +0 -0
  19. {uk_bin_collection-0.126.1.dist-info → uk_bin_collection-0.127.0.dist-info}/entry_points.txt +0 -0
@@ -244,6 +244,13 @@
244
244
  "wiki_name": "Breckland Council",
245
245
  "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
246
246
  },
247
+ "BrentCouncil": {
248
+ "house_number": "25",
249
+ "postcode": "HA3 0QU",
250
+ "url": "https://recyclingservices.brent.gov.uk/waste",
251
+ "wiki_name": "Brent Council",
252
+ "wiki_note": "Pass the house number and postcode in their respective parameters."
253
+ },
247
254
  "BrightonandHoveCityCouncil": {
248
255
  "house_number": "44 Carden Avenue, Brighton, BN1 8NE",
249
256
  "postcode": "BN1 8NE",
@@ -440,7 +447,7 @@
440
447
  "uprn": "100110734613",
441
448
  "url": "https://www.copeland.gov.uk",
442
449
  "wiki_name": "Copeland Borough Council",
443
- "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
450
+ "wiki_note": "*****This has now been replaced by Cumberland Council****"
444
451
  },
445
452
  "CornwallCouncil": {
446
453
  "skip_get_url": true,
@@ -449,12 +456,6 @@
449
456
  "wiki_name": "Cornwall Council",
450
457
  "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
451
458
  },
452
- "CoventryCityCouncil": {
453
- "url": "https://www.coventry.gov.uk/directory-record/56384/abberton-way-",
454
- "wiki_command_url_override": "https://www.coventry.gov.uk/directory_record/XXXXXX/XXXXXX",
455
- "wiki_name": "Coventry City Council",
456
- "wiki_note": "Follow the instructions [here](https://www.coventry.gov.uk/bin-collection-calendar) until you get the page that shows the weekly collections for your address, then copy the URL and replace the URL in the command."
457
- },
458
459
  "CotswoldDistrictCouncil": {
459
460
  "house_number": "19",
460
461
  "postcode": "GL56 0GB",
@@ -465,7 +466,7 @@
465
466
  "wiki_note": "Pass the full address in the house number and postcode in"
466
467
  },
467
468
  "CoventryCityCouncil": {
468
- "url": "https://www.coventry.gov.uk/directory-record/56384/abberton-way-",
469
+ "url": "https://www.coventry.gov.uk/directory-record/62310/abberton-way-",
469
470
  "wiki_command_url_override": "https://www.coventry.gov.uk/directory_record/XXXXXX/XXXXXX",
470
471
  "wiki_name": "Coventry City Council",
471
472
  "wiki_note": "Follow the instructions [here](https://www.coventry.gov.uk/bin-collection-calendar) until you get the page that shows the weekly collections for your address then copy the URL and replace the URL in the command."
@@ -486,6 +487,12 @@
486
487
  "wiki_name": "Croydon Council",
487
488
  "wiki_note": "Pass the house number and postcode in their respective parameters."
488
489
  },
490
+ "CumberlandCouncil": {
491
+ "uprn": "100110734613",
492
+ "url": "https://waste.cumberland.gov.uk",
493
+ "wiki_name": "Cumberland Borough Council",
494
+ "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
495
+ },
489
496
  "CumberlandAllerdaleCouncil": {
490
497
  "house_number": "2",
491
498
  "postcode": "CA13 0DE",
@@ -508,6 +515,12 @@
508
515
  "wiki_name": "Dartford Borough Council",
509
516
  "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
510
517
  },
518
+ "DenbighshireCouncil": {
519
+ "url": "https://www.denbighshire.gov.uk/",
520
+ "uprn": "200004299351",
521
+ "wiki_name": "Denbighshire Council",
522
+ "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
523
+ },
511
524
  "DerbyCityCouncil": {
512
525
  "url": "https://www.derby.gov.uk",
513
526
  "uprn": "10010684240",
@@ -550,6 +563,12 @@
550
563
  "wiki_name": "Dudley Council",
551
564
  "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
552
565
  },
566
+ "DundeeCityCouncil": {
567
+ "url": "https://www.dundeecity.gov.uk/",
568
+ "uprn": "9059043390",
569
+ "wiki_name": "Dundee City Council",
570
+ "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
571
+ },
553
572
  "DurhamCouncil": {
554
573
  "skip_get_url": true,
555
574
  "uprn": "200003218818",
@@ -805,6 +824,12 @@
805
824
  "wiki_name": "Guildford Council",
806
825
  "wiki_note": "If the bin day is 'today' then the collectionDate will only show today's date if before 7 AM; else the date will be in 'previousCollectionDate'. To get the UPRN, you will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search)."
807
826
  },
827
+ "GwyneddCouncil": {
828
+ "url": "https://diogel.gwynedd.llyw.cymru",
829
+ "uprn": "10070350463",
830
+ "wiki_name": "Gwynedd Council",
831
+ "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
832
+ },
808
833
  "HackneyCouncil": {
809
834
  "house_number": "101",
810
835
  "postcode": "N16 9AS",
@@ -936,13 +961,11 @@
936
961
  "wiki_note": "Follow the instructions [here](https://waste-services.kingston.gov.uk/waste) until the \"Your bin days\" page, then copy the URL and replace the URL in the command."
937
962
  },
938
963
  "KirkleesCouncil": {
939
- "house_number": "24",
940
- "postcode": "HD7 5DX",
964
+ "uprn": "83002937",
941
965
  "skip_get_url": true,
942
966
  "url": "https://www.kirklees.gov.uk/beta/your-property-bins-recycling/your-bins",
943
- "web_driver": "http://selenium:4444",
944
967
  "wiki_name": "Kirklees Council",
945
- "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver."
968
+ "wiki_note": "Provide your UPRN. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search)."
946
969
  },
947
970
  "KnowsleyMBCouncil": {
948
971
  "house_number": "22",
@@ -1325,7 +1348,7 @@
1325
1348
  "house_number": "22",
1326
1349
  "postcode": "NE46 1UQ",
1327
1350
  "skip_get_url": true,
1328
- "url": "https://www.northumberland.gov.uk/Waste/Bins/Bin-Calendars.aspx",
1351
+ "url": "https://www.northumberland.gov.uk/Waste/Household-waste/Household-bin-collections/Bin-Calendars.aspx",
1329
1352
  "web_driver": "http://selenium:4444",
1330
1353
  "wiki_name": "Northumberland Council",
1331
1354
  "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver."
@@ -1344,6 +1367,12 @@
1344
1367
  "house_number": "Newdigate Road",
1345
1368
  "wiki_note": "Pass the name of the street ONLY in the house number parameter, wrapped in double quotes. Street name must match exactly as it appears on the council's website."
1346
1369
  },
1370
+ "OadbyAndWigstonBoroughCouncil": {
1371
+ "url": "https://my.oadby-wigston.gov.uk",
1372
+ "uprn": "10010149102",
1373
+ "wiki_name": "Oadby & Wigston Borough Council",
1374
+ "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
1375
+ },
1347
1376
  "OldhamCouncil": {
1348
1377
  "url": "https://portal.oldham.gov.uk/bincollectiondates/details?uprn=422000033556",
1349
1378
  "wiki_name": "Oldham Council",
@@ -1985,6 +2014,12 @@
1985
2014
  "wiki_name": "West Berkshire Council",
1986
2015
  "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter."
1987
2016
  },
2017
+ "WestDunbartonshireCouncil": {
2018
+ "url": "https://www.west-dunbarton.gov.uk/",
2019
+ "uprn": "129001383",
2020
+ "wiki_name": "West Dunbartonshire Council",
2021
+ "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
2022
+ },
1988
2023
  "WestLancashireBoroughCouncil": {
1989
2024
  "url": "https://www.westlancs.gov.uk",
1990
2025
  "uprn": "10012343339",
@@ -0,0 +1,115 @@
1
+ from time import sleep
2
+
3
+ import requests
4
+ from bs4 import BeautifulSoup
5
+
6
+ from uk_bin_collection.uk_bin_collection.common import *
7
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
8
+
9
+
10
+ # 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
+ data = {"bins": []}
20
+ user_postcode = kwargs.get("postcode")
21
+ user_paon = kwargs.get("paon")
22
+ check_postcode(user_postcode)
23
+ check_paon(user_paon)
24
+
25
+ URI = "https://recyclingservices.brent.gov.uk/waste"
26
+
27
+ payload = {"postcode": user_postcode}
28
+
29
+ s = requests.Session()
30
+
31
+ # Make the POST request
32
+ response = s.post(URI, data=payload)
33
+
34
+ # Make a BS4 object
35
+ soup = BeautifulSoup(response.content, features="html.parser")
36
+
37
+ address_list = soup.find_all("option")
38
+
39
+ current_year = datetime.now().year
40
+ next_year = current_year + 1
41
+
42
+ for address in address_list:
43
+ if user_paon in (address.text):
44
+ address_id = address.get("value")
45
+ URI = f"https://recyclingservices.brent.gov.uk/waste/{address_id}"
46
+
47
+ counter = 0
48
+ r = s.get(URI)
49
+ while "Loading your bin days..." in r.text:
50
+ counter = counter + 1
51
+ if counter == 20:
52
+ return data
53
+ sleep(2)
54
+ r = s.get(URI)
55
+
56
+ r.raise_for_status()
57
+
58
+ soup = BeautifulSoup(r.content, features="html.parser")
59
+
60
+ wastecollections = soup.find("div", {"class": "waste__collections"})
61
+
62
+ # Find all waste service sections
63
+ waste_services = wastecollections.find_all(
64
+ "h3", class_="govuk-heading-m waste-service-name"
65
+ )
66
+
67
+ for service in waste_services:
68
+ # Get the collection type (e.g., Rubbish, Recycling)
69
+ collection_type = (service.get_text(strip=True)).split("\n")[0]
70
+
71
+ # Find the sibling container holding details
72
+ service_details = service.find_next(
73
+ "dl", class_="govuk-summary-list"
74
+ )
75
+
76
+ if service_details:
77
+
78
+ # Extract next collection date
79
+ next_collection_row = service_details.find(
80
+ "dt", string="Next collection"
81
+ )
82
+ next_collection = (
83
+ next_collection_row.find_next_sibling("dd").get_text(
84
+ strip=True
85
+ )
86
+ if next_collection_row
87
+ else "Unknown"
88
+ )
89
+
90
+ # Parse dates into standard dd/mm/yyyy format
91
+ next_collection_date = datetime.strptime(
92
+ remove_ordinal_indicator_from_date_string(next_collection),
93
+ "%A, %d %B",
94
+ )
95
+
96
+ if (datetime.now().month == 12) and (
97
+ next_collection.month == 1
98
+ ):
99
+ next_collection_date = next_collection_date.replace(
100
+ year=next_year
101
+ )
102
+ else:
103
+ next_collection_date = next_collection_date.replace(
104
+ year=current_year
105
+ )
106
+
107
+ dict_data = {
108
+ "type": collection_type.strip(),
109
+ "collectionDate": next_collection_date.strftime(
110
+ date_format
111
+ ),
112
+ }
113
+ data["bins"].append(dict_data)
114
+
115
+ return data
@@ -1,7 +1,8 @@
1
1
  from bs4 import BeautifulSoup
2
+ from dateutil.relativedelta import relativedelta
3
+
2
4
  from uk_bin_collection.uk_bin_collection.common import *
3
5
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
4
- from dateutil.relativedelta import relativedelta
5
6
 
6
7
 
7
8
  # import the wonderful Beautiful Soup and the URL grabber
@@ -52,9 +53,13 @@ class CouncilClass(AbstractGetBinDataClass):
52
53
 
53
54
  for item in soup.find_all("div", class_="collection text-center service"):
54
55
  bin_type = item.contents[1].text + " bin"
55
- collection_date = datetime.strptime(item.contents[5].text, "%d %b").replace(
56
- year=curr_date.year
57
- )
56
+ try:
57
+ collection_date = datetime.strptime(
58
+ item.contents[5].text, "%d %b"
59
+ ).replace(year=curr_date.year)
60
+ except:
61
+ continue
62
+
58
63
  if curr_date.month == 12 and collection_date.month == 1:
59
64
  collection_date = collection_date + relativedelta(years=1)
60
65
  collections.append((bin_type, collection_date))
@@ -20,7 +20,7 @@ class CouncilClass(AbstractGetBinDataClass):
20
20
  curr_date = datetime.today()
21
21
 
22
22
  soup = BeautifulSoup(page.content, features="html.parser")
23
- button = soup.find("a", text="Find out which bin will be collected when.")
23
+ button = soup.find("a", text="Find out which bin will be collected when and sign up for a free email reminder.")
24
24
 
25
25
  if button["href"]:
26
26
  URI = button["href"]
@@ -0,0 +1,96 @@
1
+ import requests
2
+ from bs4 import BeautifulSoup
3
+
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
+
18
+ user_uprn = kwargs.get("uprn")
19
+ check_uprn(user_uprn)
20
+ bindata = {"bins": []}
21
+
22
+ URI = "https://waste.cumberland.gov.uk/renderform?t=25&k=E43CEB1FB59F859833EF2D52B16F3F4EBE1CAB6A"
23
+
24
+ s = requests.Session()
25
+
26
+ # Make the GET request
27
+ response = s.get(URI)
28
+
29
+ # Make a BS4 object
30
+ soup = BeautifulSoup(response.content, features="html.parser")
31
+
32
+ # print(soup)
33
+
34
+ token = (soup.find("input", {"name": "__RequestVerificationToken"})).get(
35
+ "value"
36
+ )
37
+
38
+ formguid = (soup.find("input", {"name": "FormGuid"})).get("value")
39
+
40
+ # print(token)
41
+ # print(formguid)
42
+
43
+ headers = {
44
+ "Content-Type": "application/x-www-form-urlencoded",
45
+ "Origin": "https://waste.cumberland.gov.uk",
46
+ "Referer": "https://waste.cumberland.gov.uk/renderform?t=25&k=E43CEB1FB59F859833EF2D52B16F3F4EBE1CAB6A",
47
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 OPR/98.0.0.0",
48
+ "X-Requested-With": "XMLHttpRequest",
49
+ }
50
+
51
+ payload = {
52
+ "__RequestVerificationToken": token,
53
+ "FormGuid": formguid,
54
+ "ObjectTemplateID": "25",
55
+ "Trigger": "submit",
56
+ "CurrentSectionID": "33",
57
+ "TriggerCtl": "",
58
+ "FF265": f"U{user_uprn}",
59
+ "FF265lbltxt": "Please select your address",
60
+ }
61
+
62
+ # print(payload)
63
+
64
+ response = s.post(
65
+ "https://waste.cumberland.gov.uk/renderform/Form",
66
+ headers=headers,
67
+ data=payload,
68
+ )
69
+
70
+ soup = BeautifulSoup(response.content, features="html.parser")
71
+ for row in soup.find_all("div", class_="resirow"):
72
+ # Extract the type of collection (e.g., Recycling, Refuse)
73
+ collection_type_div = row.find("div", class_="col")
74
+ collection_type = (
75
+ collection_type_div.get("class")[1]
76
+ if collection_type_div
77
+ else "Unknown"
78
+ )
79
+
80
+ # Extract the collection date
81
+ date_div = row.find("div", style="width:360px;")
82
+ collection_date = date_div.text.strip() if date_div else "Unknown"
83
+
84
+ dict_data = {
85
+ "type": collection_type,
86
+ "collectionDate": datetime.strptime(
87
+ collection_date, "%A %d %B %Y"
88
+ ).strftime(date_format),
89
+ }
90
+ bindata["bins"].append(dict_data)
91
+
92
+ bindata["bins"].sort(
93
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
94
+ )
95
+
96
+ return bindata
@@ -0,0 +1,66 @@
1
+ import time
2
+
3
+ import requests
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
+
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
+
19
+ user_uprn = kwargs.get("uprn")
20
+ check_uprn(user_uprn)
21
+ bindata = {"bins": []}
22
+
23
+ URI = "https://refusecalendarapi.denbighshire.gov.uk/Csrf/token"
24
+
25
+ token = requests.get(URI)
26
+
27
+ token_data = token.json()
28
+
29
+ URI = f"https://refusecalendarapi.denbighshire.gov.uk/Calendar/{user_uprn}"
30
+
31
+ headers = {
32
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
33
+ "content-type": "application/json",
34
+ "dnt": "1",
35
+ "host": "refusecalendarapi.denbighshire.gov.uk",
36
+ "referer": "https://refusecalendarapi.denbighshire.gov.uk/",
37
+ "x-csrf-token": token_data["token"],
38
+ }
39
+
40
+ # Make the GET request
41
+ response = requests.get(URI, headers=headers)
42
+
43
+ # Parse the JSON response
44
+ json_data = response.json()
45
+
46
+ bin_types = {
47
+ "refuseDate": "Refuse",
48
+ "recyclingDate": "Recycling",
49
+ "gardenDate": "Garden Waste",
50
+ "ahpDate": "AHP (Assisted Household Pickup)",
51
+ "tradeDate": "Trade Waste",
52
+ "tradeRefuseDate": "Trade Refuse",
53
+ "tradeRecyclingDate": "Trade Recycling",
54
+ }
55
+
56
+ bindata["bins"] = [
57
+ {"type": label, "collectionDate": json_data[key]}
58
+ for key, label in bin_types.items()
59
+ if json_data.get(key)
60
+ ]
61
+
62
+ bindata["bins"].sort(
63
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
64
+ )
65
+
66
+ return bindata
@@ -0,0 +1,44 @@
1
+ import time
2
+
3
+ import requests
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
+
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
+
19
+ user_uprn = kwargs.get("uprn")
20
+ check_uprn(user_uprn)
21
+ bindata = {"bins": []}
22
+
23
+ URI = f"https://www.dundee-mybins.co.uk/get_calendar.php?rn={user_uprn}"
24
+
25
+ # Make the GET request
26
+ response = requests.get(URI)
27
+
28
+ # Parse the JSON response
29
+ bin_collection = response.json()
30
+
31
+ for item in bin_collection:
32
+ dict_data = {
33
+ "type": item["title"],
34
+ "collectionDate": datetime.strptime(item["start"], "%Y-%m-%d").strftime(
35
+ date_format
36
+ ),
37
+ }
38
+ bindata["bins"].append(dict_data)
39
+
40
+ bindata["bins"].sort(
41
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
42
+ )
43
+
44
+ return bindata
@@ -0,0 +1,54 @@
1
+ import requests
2
+ from bs4 import BeautifulSoup, Tag
3
+
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
+
18
+ user_uprn = kwargs.get("uprn")
19
+ check_uprn(user_uprn)
20
+ bindata = {"bins": []}
21
+
22
+ URI = f"https://diogel.gwynedd.llyw.cymru/Daearyddol/en/LleDwinByw/Index/{user_uprn}"
23
+
24
+ # Make the GET request
25
+ response = requests.get(URI)
26
+
27
+ soup = BeautifulSoup(response.text, "html.parser")
28
+ collections_headline = soup.find("h6", text="Next collection dates:")
29
+ if not isinstance(collections_headline, Tag):
30
+ raise Exception("Could not find collections")
31
+ collections = collections_headline.find_next("ul").find_all("li")
32
+
33
+ for collection in collections:
34
+ if not isinstance(collection, Tag):
35
+ continue
36
+ for p in collection.find_all("p"):
37
+ p.extract()
38
+
39
+ bin_type, date_str = collection.text.strip().split(":")[:2]
40
+ bin_type, date_str = bin_type.strip(), date_str.strip()
41
+
42
+ dict_data = {
43
+ "type": bin_type,
44
+ "collectionDate": datetime.strptime(date_str, "%A %d/%m/%Y").strftime(
45
+ date_format
46
+ ),
47
+ }
48
+ bindata["bins"].append(dict_data)
49
+
50
+ bindata["bins"].sort(
51
+ key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
52
+ )
53
+
54
+ return bindata
@@ -1,36 +1,12 @@
1
1
  import time
2
- from datetime import datetime
3
- from typing import Optional
4
2
 
5
- from bs4 import BeautifulSoup
6
- from selenium.common import TimeoutException
7
- from selenium.webdriver.common.by import By
8
- from selenium.webdriver.common.keys import Keys
9
- from selenium.webdriver.remote.webdriver import WebDriver
10
- from selenium.webdriver.support import expected_conditions as EC
11
- from selenium.webdriver.support.wait import WebDriverWait
12
- from webdriver_manager.drivers.chrome import ChromeDriver
3
+ import requests
13
4
 
14
- from selenium import webdriver
15
-
16
- from uk_bin_collection.uk_bin_collection.common import create_webdriver
17
- from uk_bin_collection.uk_bin_collection.common import date_format
5
+ from uk_bin_collection.uk_bin_collection.common import *
18
6
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
19
7
 
20
8
 
21
- def wait_for_element(driver, element_type, element: str, timeout: int = 5):
22
- element_present = EC.presence_of_element_located((element_type, element))
23
- wait_for_element_conditions(driver, element_present, timeout=timeout)
24
-
25
-
26
- def wait_for_element_conditions(driver, conditions, timeout: int = 5):
27
- try:
28
- WebDriverWait(driver, timeout).until(conditions)
29
- except TimeoutException:
30
- print("Timed out waiting for page to load")
31
- raise
32
-
33
-
9
+ # import the wonderful Beautiful Soup and the URL grabber
34
10
  class CouncilClass(AbstractGetBinDataClass):
35
11
  """
36
12
  Concrete classes have to implement all abstract operations of the
@@ -38,106 +14,63 @@ class CouncilClass(AbstractGetBinDataClass):
38
14
  implementation.
39
15
  """
40
16
 
41
- def __init__(self):
42
- self._driver: Optional[WebDriver] = None
43
-
44
- def parse_data(self, *args, **kwargs) -> dict:
45
- try:
46
- return self._parse_data(*args, **kwargs)
47
- finally:
48
- if self._driver:
49
- self._driver.quit()
50
-
51
- def _parse_data(self, page: str, **kwargs) -> dict:
52
- """
53
- Process:
54
-
55
- - Use a house number and postcode that is known to be domestic and resolves to a
56
- single unique address. When the address search form is submitted with
57
- those details, a session is created
58
-
59
- - Now a session exists, navigate to the calendar URL, specifying the UPRN
60
-
61
- - Extract info from the 'alt' attribute of the images on that page
62
- """
63
- data = {"bins": []}
64
- collections = []
65
-
66
- user_paon = kwargs["paon"]
67
- user_postcode = kwargs["postcode"]
68
-
69
- self._driver = driver = webdriver.Chrome()
70
- # self._driver = driver = create_webdriver(
71
- # web_driver=kwargs["web_driver"],
72
- # headless=kwargs.get("headless", True),
73
- # session_name=__name__,
74
- # )
75
- driver.implicitly_wait(1)
76
-
77
- driver.get(
78
- "https://my.kirklees.gov.uk/service/Bins_and_recycling___Manage_your_bins"
79
- )
80
-
81
- time.sleep(5)
82
-
83
- # Switch to iframe
84
- iframe = driver.find_element(By.CSS_SELECTOR, "#fillform-frame-1")
85
- driver.switch_to.frame(iframe)
86
-
87
- wait_for_element(
88
- driver, By.ID, "mandatory_Postcode", timeout=10
89
- )
90
-
91
- postcode_input = driver.find_element(
92
- By.ID, "Postcode"
93
- )
94
- postcode_input.send_keys(user_postcode)
95
-
96
- wait_for_element(driver, By.ID, "List")
97
- time.sleep(2)
98
-
99
- WebDriverWait(driver, 10).until(
100
- EC.element_to_be_clickable(
101
- (
102
- By.XPATH,
103
- "//select[@name='List']//option[contains(., '"
104
- + user_paon
105
- + "')]",
106
- )
107
- )
108
- ).click()
109
-
110
- time.sleep(10)
111
-
112
- # For whatever reason, the page sometimes automatically goes to the next step
113
- next_button = driver.find_element(By.XPATH, '/html/body/div/div/section/form/div/nav/div[2]/button')
114
- if next_button.is_displayed():
115
- next_button.click()
116
-
117
-
118
- time.sleep(5)
119
-
120
- soup = BeautifulSoup(self._driver.page_source, features="html.parser")
121
- soup.prettify()
122
-
123
- radio_button_text = soup.find_all("label", {"class": "radio-label"})
124
- for label in radio_button_text:
125
- parsed_text = label.text.split("x ")
126
- row = parsed_text[1].lower().split("collection date: ")
127
- bin_type = row[0].split("(")[0].strip()
128
- date_text = row[1].strip().replace(")", "")
129
- if date_text == "today":
130
- bin_date = datetime.now()
131
- else:
132
- bin_date = datetime.strptime(date_text, "%A %d %B %Y")
133
- collections.append((bin_type, bin_date))
134
-
135
- ordered_data = sorted(collections, key=lambda x: x[1])
136
- for item in ordered_data:
137
- dict_data = {
138
- "type": item[0].replace("standard ", "").capitalize(),
139
- "collectionDate": item[1].strftime(date_format),
140
- }
141
- data["bins"].append(dict_data)
142
-
143
- return data
17
+ def parse_data(self, page: str, **kwargs) -> dict:
18
+
19
+ user_uprn = kwargs.get("uprn")
20
+ check_uprn(user_uprn)
21
+ bindata = {"bins": []}
22
+
23
+ SESSION_URL = "https://my.kirklees.gov.uk/authapi/isauthenticated?uri=https%253A%252F%252Fmy.kirklees.gov.uk%252Fservice%252FBins_and_recycling___Manage_your_bins&hostname=my.kirklees.gov.uk&withCredentials=true"
24
+
25
+ API_URL = "https://my.kirklees.gov.uk/apibroker/runLookup"
26
+
27
+ data = {
28
+ "formValues": {"Search": {"validatedUPRN": {"value": user_uprn}}},
29
+ }
30
+ headers = {
31
+ "Content-Type": "application/json",
32
+ "Accept": "application/json",
33
+ "User-Agent": "Mozilla/5.0",
34
+ "X-Requested-With": "XMLHttpRequest",
35
+ "Referer": "https://my.kirklees.gov.uk/fillform/?iframe_id=fillform-frame-1&db_id=",
36
+ }
37
+ s = requests.session()
38
+ r = s.get(SESSION_URL)
39
+ r.raise_for_status()
40
+ session_data = r.json()
41
+ sid = session_data["auth-session"]
42
+ params = {
43
+ "id": "65e08e60b299d",
44
+ "repeat_against": "",
45
+ "noRetry": "false",
46
+ "getOnlyTokens": "undefined",
47
+ "log_id": "",
48
+ "app_name": "AF-Renderer::Self",
49
+ # unix_timestamp
50
+ "_": str(int(time.time() * 1000)),
51
+ "sid": sid,
52
+ }
53
+
54
+ r = s.post(API_URL, json=data, headers=headers, params=params)
55
+ r.raise_for_status()
56
+
57
+ data = r.json()
58
+ rows_data = data["integration"]["transformed"]["rows_data"]
59
+ if not isinstance(rows_data, dict):
60
+ raise ValueError("Invalid data returned from API")
61
+
62
+ for bin_id, bin_info in rows_data.items():
63
+ label = bin_info.get("label", "Unknown")
64
+ next_collection_date = bin_info.get("NextCollectionDate", "Unknown")
65
+ # Convert the date string into a readable format
66
+ try:
67
+ formatted_date = datetime.strptime(
68
+ next_collection_date, "%Y-%m-%dT%H:%M:%S"
69
+ ).strftime(date_format)
70
+ except ValueError:
71
+ formatted_date = "Unknown"
72
+
73
+ dict_data = {"type": label, "collectionDate": formatted_date}
74
+ bindata["bins"].append(dict_data)
75
+
76
+ return bindata
@@ -61,7 +61,7 @@ class CouncilClass(AbstractGetBinDataClass):
61
61
  "%A, %d %B",
62
62
  )
63
63
 
64
- if next_collection.month == 1:
64
+ if (datetime.now().month == 12) and (next_collection.month == 1):
65
65
  next_collection = next_collection.replace(year=next_year)
66
66
  else:
67
67
  next_collection = next_collection.replace(year=current_year)
@@ -27,7 +27,7 @@ class CouncilClass(AbstractGetBinDataClass):
27
27
  def parse_data(self, page: str, **kwargs) -> dict:
28
28
  driver = None
29
29
  try:
30
- page = "https://www.northumberland.gov.uk/Waste/Bins/Bin-Calendars.aspx"
30
+ page = "https://www.northumberland.gov.uk/Waste/Household-waste/Household-bin-collections/Bin-Calendars.aspx"
31
31
 
32
32
  data = {"bins": []}
33
33
 
@@ -74,7 +74,7 @@ class CouncilClass(AbstractGetBinDataClass):
74
74
  "span",
75
75
  id="p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_spanRouteSummary",
76
76
  )
77
- .string.replace("Routes found: ", "")
77
+ .text.replace("Routes found: ", "")
78
78
  .split(","),
79
79
  )
80
80
  )
@@ -82,6 +82,8 @@ class CouncilClass(AbstractGetBinDataClass):
82
82
  # Get the background colour for each of them...
83
83
  bins_by_colours = dict()
84
84
  for bin in bins_collected:
85
+ if "(but no dates found)" in bin:
86
+ continue
85
87
  style_str = soup.find("span", string=bin)["style"]
86
88
  bin_colour = self.extract_styles(style_str)["background-color"].upper()
87
89
  bins_by_colours[bin_colour] = bin
@@ -0,0 +1,65 @@
1
+ import requests
2
+ from bs4 import BeautifulSoup
3
+
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
+
18
+ user_uprn = kwargs.get("uprn")
19
+ check_uprn(user_uprn)
20
+ bindata = {"bins": []}
21
+
22
+ URI = f"https://my.oadby-wigston.gov.uk/location?put=ow{user_uprn}&rememberme=0&redirect=%2F"
23
+
24
+ # Make the GET request
25
+ response = requests.get(URI)
26
+
27
+ soup = BeautifulSoup(response.text, features="html.parser")
28
+ soup.prettify()
29
+
30
+ # Find the collection list
31
+ collection_list = soup.find("ul", class_="refuse")
32
+
33
+ current_year = datetime.now().year
34
+ next_year = current_year + 1
35
+
36
+ # Loop through each collection item
37
+ for li in collection_list.find_all("li"):
38
+ date_text = li.find("strong", class_="date").text.strip()
39
+ bin_type = li.find("a").text # Get the class for bin type
40
+
41
+ # Parse the date
42
+ if date_text == "Today":
43
+ collection_date = datetime.now()
44
+ else:
45
+ try:
46
+ collection_date = datetime.strptime(date_text, "%A %d %b")
47
+ except:
48
+ continue
49
+
50
+ if (datetime.now().month == 12) and (collection_date.month == 1):
51
+ collection_date = collection_date.replace(year=next_year)
52
+ else:
53
+ collection_date = collection_date.replace(year=current_year)
54
+
55
+ dict_data = {
56
+ "type": bin_type,
57
+ "collectionDate": collection_date.strftime(date_format),
58
+ }
59
+ bindata["bins"].append(dict_data)
60
+
61
+ bindata["bins"].sort(
62
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
63
+ )
64
+
65
+ return bindata
@@ -3,10 +3,10 @@ from datetime import datetime
3
3
 
4
4
  from bs4 import BeautifulSoup
5
5
  from selenium.webdriver.common.by import By
6
+ from selenium.webdriver.common.keys import Keys
6
7
  from selenium.webdriver.support import expected_conditions as EC
7
8
  from selenium.webdriver.support.ui import Select
8
9
  from selenium.webdriver.support.wait import WebDriverWait
9
- from selenium.webdriver.common.keys import Keys
10
10
 
11
11
  from uk_bin_collection.uk_bin_collection.common import *
12
12
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
@@ -73,7 +73,7 @@ class CouncilClass(AbstractGetBinDataClass):
73
73
  find_ac_button.send_keys(Keys.RETURN)
74
74
  h4_element = wait.until(
75
75
  EC.presence_of_element_located(
76
- (By.XPATH, "//h4[contains(text(), 'Your Collections')]")
76
+ (By.XPATH, "//h4[contains(text(), 'Next Collections')]")
77
77
  )
78
78
  )
79
79
 
@@ -0,0 +1,66 @@
1
+ import requests
2
+ from bs4 import BeautifulSoup
3
+
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
+
18
+ user_uprn = kwargs.get("uprn")
19
+ check_uprn(user_uprn)
20
+ bindata = {"bins": []}
21
+
22
+ URI = f"https://www.west-dunbarton.gov.uk/recycling-and-waste/bin-collection-day/?uprn={user_uprn}"
23
+
24
+ # Make the GET request
25
+ response = requests.get(URI)
26
+
27
+ soup = BeautifulSoup(response.content, "html.parser")
28
+
29
+ # For each next-date class get the text within the date-string class
30
+ schedule_details = soup.findAll("div", {"class": "round-info"})
31
+
32
+ for item in schedule_details:
33
+ schedule_date = item.find("span", {"class": "date-string"}).text.strip()
34
+ schedule_type = item.find("div", {"class": "round-name"}).text.strip()
35
+ # Format is 22 March 2023 - convert to date
36
+ collection_date = datetime.strptime(schedule_date, "%d %B %Y").date()
37
+
38
+ # If the type contains "Blue bin or bag" or "Blue" then set the type to "BLUE"
39
+ if "bag" in schedule_type.lower() or "blue" in schedule_type.lower():
40
+ dict_data = {
41
+ "type": "Blue",
42
+ "collectionDate": collection_date.strftime(date_format),
43
+ }
44
+ bindata["bins"].append(dict_data)
45
+
46
+ # If the type contains "caddy" or "brown" then set the type to "BROWN"
47
+ if "caddy" in schedule_type.lower() or "brown" in schedule_type.lower():
48
+ dict_data = {
49
+ "type": "Brown",
50
+ "collectionDate": collection_date.strftime(date_format),
51
+ }
52
+ bindata["bins"].append(dict_data)
53
+
54
+ # If the type contains "Non-Recyclable" then set the type to "BLACK", compare in lowecase
55
+ if "non-recyclable" in schedule_type.lower():
56
+ dict_data = {
57
+ "type": "Black",
58
+ "collectionDate": collection_date.strftime(date_format),
59
+ }
60
+ bindata["bins"].append(dict_data)
61
+
62
+ bindata["bins"].sort(
63
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
64
+ )
65
+
66
+ return bindata
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uk_bin_collection
3
- Version: 0.126.1
3
+ Version: 0.127.0
4
4
  Summary: Python Lib to collect UK Bin Data
5
5
  Author: Robert Bradley
6
6
  Author-email: robbrad182@gmail.com
@@ -2,7 +2,7 @@ uk_bin_collection/README.rst,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
2
2
  uk_bin_collection/tests/council_feature_input_parity.py,sha256=DO6Mk4ImYgM5ZCZ-cutwz5RoYYWZRLYx2tr6zIs_9Rc,3843
3
3
  uk_bin_collection/tests/features/environment.py,sha256=VQZjJdJI_kZn08M0j5cUgvKT4k3iTw8icJge1DGOkoA,127
4
4
  uk_bin_collection/tests/features/validate_council_outputs.feature,sha256=SJK-Vc737hrf03tssxxbeg_JIvAH-ddB8f6gU1LTbuQ,251
5
- uk_bin_collection/tests/input.json,sha256=sx_MqqcCdD9-P6RKcn9Ekxi4EjRtFnr-vg5j3rts-lo,112972
5
+ uk_bin_collection/tests/input.json,sha256=rvu_PBl9QXNOcrHuTaHr9a9U40sQtncdI4BgTniJxP8,114370
6
6
  uk_bin_collection/tests/output.schema,sha256=ZwKQBwYyTDEM4G2hJwfLUVM-5v1vKRvRK9W9SS1sd18,1086
7
7
  uk_bin_collection/tests/step_defs/step_helpers/file_handler.py,sha256=Ygzi4V0S1MIHqbdstUlIqtRIwnynvhu4UtpweJ6-5N8,1474
8
8
  uk_bin_collection/tests/step_defs/test_validate_council.py,sha256=VZ0a81sioJULD7syAYHjvK_-nT_Rd36tUyzPetSA0gk,3475
@@ -43,6 +43,7 @@ uk_bin_collection/uk_bin_collection/councils/BracknellForestCouncil.py,sha256=Ll
43
43
  uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py,sha256=BEWS2c62cOsf26jqn1AkNUvVmc5AlUADYLaQuPn9RY4,5456
44
44
  uk_bin_collection/uk_bin_collection/councils/BraintreeDistrictCouncil.py,sha256=2vYHilpI8mSwC2Ykdr1gxYAN3excDWqF6AwtGbkwbTw,2441
45
45
  uk_bin_collection/uk_bin_collection/councils/BrecklandCouncil.py,sha256=PX6A_pDvaN109aSNWmEhm88GFKfkClIkmbwGURWvsks,1744
46
+ uk_bin_collection/uk_bin_collection/councils/BrentCouncil.py,sha256=ucwokxvASYi_KiOYSOVdaGfC1kfUbII0r6Zl2NE1hnU,4208
46
47
  uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py,sha256=k6qt4cds-Ejd97Z-__pw2BYvGVbFdc9SUfF73PPrTNA,5823
47
48
  uk_bin_collection/uk_bin_collection/councils/BristolCityCouncil.py,sha256=kJmmDJz_kQ45DHmG7ocrUpNJonEn0kuXYEDQyZaf9ks,5576
48
49
  uk_bin_collection/uk_bin_collection/councils/BromleyBoroughCouncil.py,sha256=dii85JLmYU1uMidCEsWVo3stTcq_QqyC65DxG8u1UmE,4302
@@ -69,20 +70,23 @@ uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py,sha256=M7HjuUaFq8
69
70
  uk_bin_collection/uk_bin_collection/councils/ColchesterCityCouncil.py,sha256=Mny-q2rQkWe2Tj1gINwEM1L4AkqQl1EDMAaKY0-deD4,3968
70
71
  uk_bin_collection/uk_bin_collection/councils/ConwyCountyBorough.py,sha256=el75qv2QyfWZBU09tJLvD8vLQZ9pCg73u1NBFs6ybo8,1034
71
72
  uk_bin_collection/uk_bin_collection/councils/CopelandBoroughCouncil.py,sha256=JDtcJFUY47a3Ws4_7pnCYlngw_3p_A-m5MitPLdrv3w,3983
72
- uk_bin_collection/uk_bin_collection/councils/CornwallCouncil.py,sha256=WZiz50svwyZgO8QKUCLy7hfFuy2HmAx5h-TG3yAweRA,2836
73
+ uk_bin_collection/uk_bin_collection/councils/CornwallCouncil.py,sha256=6zHe_Qu-R9hTB52QRk215rYfO3OocbJ_D62kVWnrve8,2912
73
74
  uk_bin_collection/uk_bin_collection/councils/CotswoldDistrictCouncil.py,sha256=Y2yvduT6XTy54bDYX17-dltjd7L36cc7GDjlx0xWskA,4896
74
- uk_bin_collection/uk_bin_collection/councils/CoventryCityCouncil.py,sha256=kfAvA2e4MlO0W9YT70U_mW9gxVPrmr0BOGzV99Tw2Bg,2012
75
+ uk_bin_collection/uk_bin_collection/councils/CoventryCityCouncil.py,sha256=QejW440Wp1mpEyRZIy1A0hYPejg_-D0bfHtpC2-VoaI,2050
75
76
  uk_bin_collection/uk_bin_collection/councils/CrawleyBoroughCouncil.py,sha256=Oaj5INA3zNjtzBRsfLvRTIxZzcd4E4bJfVF1ULWlrL4,4322
76
77
  uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py,sha256=Vxh5ICoaXTAvx0nDOq_95XQ4He9sQKcLdI5keV2uxM4,11384
77
78
  uk_bin_collection/uk_bin_collection/councils/CumberlandAllerdaleCouncil.py,sha256=bPOmkyzNnHrOtUprbouHdOsgpu7WilUADcaccWTCPFI,2839
79
+ uk_bin_collection/uk_bin_collection/councils/CumberlandCouncil.py,sha256=hM0SUXW4k_queqVH0hEx-dmuBHQs0Ds1LEjlsJ8EiCY,3231
78
80
  uk_bin_collection/uk_bin_collection/councils/DacorumBoroughCouncil.py,sha256=Tm_6pvBPj-6qStbe6-02LXaoCOlnnDvVXAAocGVvf_E,3970
79
81
  uk_bin_collection/uk_bin_collection/councils/DartfordBoroughCouncil.py,sha256=SPirUUoweMwX5Txtsr0ocdcFtKxCQ9LhzTTJN20tM4w,1550
82
+ uk_bin_collection/uk_bin_collection/councils/DenbighshireCouncil.py,sha256=FtG0LMTY21RBQiBpeX4FihdMCEZ1knpARYyMfyCn9ng,2103
80
83
  uk_bin_collection/uk_bin_collection/councils/DerbyCityCouncil.py,sha256=M8FGLhZn9wdRCq1W6z_yqJQqeba3EKyba3vhM22MzB4,1883
81
84
  uk_bin_collection/uk_bin_collection/councils/DerbyshireDalesDistrictCouncil.py,sha256=MQC1-jXezXczrxTcvPQvkpGgyyAbzSKlX38WsmftHak,4007
82
85
  uk_bin_collection/uk_bin_collection/councils/DoncasterCouncil.py,sha256=b7pxoToXu6dBBYXsXmlwfPXE8BjHxt0hjCOBNlNgvX8,3118
83
86
  uk_bin_collection/uk_bin_collection/councils/DorsetCouncil.py,sha256=XUWH5BzkjPSFBLczo-Vo-Wly2JMoabm9WtI6_Mf-pO4,1523
84
87
  uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py,sha256=3Zgap6kaVpDXtRfBKEL1Ms6eb0iFIipYKNtOq3Hrdd4,1891
85
88
  uk_bin_collection/uk_bin_collection/councils/DudleyCouncil.py,sha256=tDGfBWD5AVEzHt-XowD4qmmDcdlOLKC0oON8jkARRAI,3134
89
+ uk_bin_collection/uk_bin_collection/councils/DundeeCityCouncil.py,sha256=CzzPZ3FJW36ojW3wHIwEYXhMP1LlTKBJQEpnrLr5Lds,1285
86
90
  uk_bin_collection/uk_bin_collection/councils/DurhamCouncil.py,sha256=6O8bNsQVYQbrCYQE9Rp0c_rtkcXuxR3s9J6jn4MK4_s,1695
87
91
  uk_bin_collection/uk_bin_collection/councils/EalingCouncil.py,sha256=UhNXGi-_6NYZu50988VEvOzmAVunxOoyJ6mz0OEaUz4,1321
88
92
  uk_bin_collection/uk_bin_collection/councils/EastAyrshireCouncil.py,sha256=i3AcWkeAnk7rD59nOm0QCSH7AggqjUAdwsXuSIC8ZJE,1614
@@ -114,6 +118,7 @@ uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py,sha256=-QKWla
114
118
  uk_bin_collection/uk_bin_collection/councils/GloucesterCityCouncil.py,sha256=8Wjvmdvg5blHVrREaEnhhWZaWhYVP4v_KdDVPLIUxaU,4889
115
119
  uk_bin_collection/uk_bin_collection/councils/GraveshamBoroughCouncil.py,sha256=ueQ9xFiTxMUBTGV9VjtySHA1EFWliTM0AeNePBIG9ho,4568
116
120
  uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py,sha256=9pVrmQhZcK2AD8gX8mNvP--L4L9KaY6L3B822VX6fec,5695
121
+ uk_bin_collection/uk_bin_collection/councils/GwyneddCouncil.py,sha256=eK2KkY1NbIxVtBruQYSNPA0J7fuzMik5it02dFbKYV0,1855
117
122
  uk_bin_collection/uk_bin_collection/councils/HackneyCouncil.py,sha256=fJZTHCk-uXC61V_7uxiU_MhIupfeObjaUYAT0v0onLo,3100
118
123
  uk_bin_collection/uk_bin_collection/councils/HaltonBoroughCouncil.py,sha256=gq_CPqi6qM2oNiHhKKF1lZC86fyKL4lPhh_DN9pJZ04,5971
119
124
  uk_bin_collection/uk_bin_collection/councils/HarboroughDistrictCouncil.py,sha256=uAbCgfrqkIkEKUyLVE8l72s5tzbfMFsw775i0nVRAyc,1934
@@ -131,7 +136,7 @@ uk_bin_collection/uk_bin_collection/councils/HuntingdonDistrictCouncil.py,sha256
131
136
  uk_bin_collection/uk_bin_collection/councils/IslingtonCouncil.py,sha256=xavzL6ZIU9DG8Xro3vN0CEnYmNU31OGnOvnq78wgpQc,1258
132
137
  uk_bin_collection/uk_bin_collection/councils/KingsLynnandWestNorfolkBC.py,sha256=Shj18R-7NW4ivqJJFVJOLmf-EeN6hXP2Of30oI-SeAQ,1932
133
138
  uk_bin_collection/uk_bin_collection/councils/KingstonUponThamesCouncil.py,sha256=iZ7njIxccCGBhUUWWd9Azh7cxUAKaofebCm3lo-TuxA,3543
134
- uk_bin_collection/uk_bin_collection/councils/KirkleesCouncil.py,sha256=RnxwjkTfdMGZEdBwkUUxTVLAIEOKGnaHDkqWEUbk1Xk,4802
139
+ uk_bin_collection/uk_bin_collection/councils/KirkleesCouncil.py,sha256=WPM7koIqK5Wz-iT9Mds6AptihGZtl4KZhkVTcT9cx_c,2762
135
140
  uk_bin_collection/uk_bin_collection/councils/KnowsleyMBCouncil.py,sha256=VdlWDESoHfr_X0r8-UMaLMUQhKZOa2BnpVPkX-1u3EQ,5605
136
141
  uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py,sha256=FmHT6oyD4BwWuhxA80PHnGA7HPrLuyjP_54Cg8hT6k4,2537
137
142
  uk_bin_collection/uk_bin_collection/councils/LeedsCityCouncil.py,sha256=iSZApZ9oSfSatQ6dAxmykSfti91jGuY6n2BwEkVMOiU,5144
@@ -146,7 +151,7 @@ uk_bin_collection/uk_bin_collection/councils/LondonBoroughHounslow.py,sha256=UOe
146
151
  uk_bin_collection/uk_bin_collection/councils/LondonBoroughLambeth.py,sha256=r9D5lHe5kIRStCd5lRIax16yhb4KTFzzfYEFv1bacWw,2009
147
152
  uk_bin_collection/uk_bin_collection/councils/LondonBoroughLewisham.py,sha256=d8rlJDTbY3nj-Zjg6iwvwfe-X13Gq86DGGW6QkQAUW0,5310
148
153
  uk_bin_collection/uk_bin_collection/councils/LondonBoroughRedbridge.py,sha256=A_6Sis5hsF53Th04KeadHRasGbpAm6aoaWJ6X8eC4Y8,6604
149
- uk_bin_collection/uk_bin_collection/councils/LondonBoroughSutton.py,sha256=GAE51GfLk6lBWBiHzon7Egvlcds63bL_nK2jBlAMzs8,2530
154
+ uk_bin_collection/uk_bin_collection/councils/LondonBoroughSutton.py,sha256=T0gCjpBQ9k3uNNIiJGphNiSXeh2n6bohflg9RwbL3NA,2565
150
155
  uk_bin_collection/uk_bin_collection/councils/LutonBoroughCouncil.py,sha256=vScUi_R8FnBddii2_zLlZBLxuh85mKmCm8nKW3zxky0,2758
151
156
  uk_bin_collection/uk_bin_collection/councils/MaldonDistrictCouncil.py,sha256=PMVt2XFggttPmbWyrBrHJ-W6R_6-0ux1BkY1kj1IKzg,1997
152
157
  uk_bin_collection/uk_bin_collection/councils/MalvernHillsDC.py,sha256=iQG0EkX2npBicvsGKQRYyBGSBvKVUbKvUvvwrC9xV1A,2100
@@ -182,10 +187,11 @@ uk_bin_collection/uk_bin_collection/councils/NorthSomersetCouncil.py,sha256=EbFV
182
187
  uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py,sha256=MHfcSrfWBIiWwVnhOqUrOkr0Ahguu_oU4pHrv_vSa-Y,5929
183
188
  uk_bin_collection/uk_bin_collection/councils/NorthWestLeicestershire.py,sha256=gJj0dyQc5QUefqusKGk2LLXfWbG5tlEXUOh8KAPh3RI,4584
184
189
  uk_bin_collection/uk_bin_collection/councils/NorthYorkshire.py,sha256=2wTrr3VrZDp9-YtDPmWd649gXeWH4hbm2-Hw8Vau5Xs,1933
185
- uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py,sha256=KEFsxEvQ159fkuFo-fza67YCnnCZ5ElwE80zTrqDEWI,4990
190
+ uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py,sha256=CF9__v8xOUJ9Ukic9Yg9WnVUojV3Cv6_dHAHJzL6V3o,5106
186
191
  uk_bin_collection/uk_bin_collection/councils/NorwichCityCouncil.py,sha256=6w5-xKiyxTlJLaCkwUnAWjd5jnohonnNRr0sTlfACvk,2702
187
192
  uk_bin_collection/uk_bin_collection/councils/NottinghamCityCouncil.py,sha256=panTCjnsBOQ98-TBO9xVZk_jcT_gjMhx3Gg5oWxBRLo,1254
188
193
  uk_bin_collection/uk_bin_collection/councils/NuneatonBedworthBoroughCouncil.py,sha256=KqjQczn214ctkTsm-zimlDSDtgvcLQ23fte3kHiCEsg,19187
194
+ uk_bin_collection/uk_bin_collection/councils/OadbyAndWigstonBoroughCouncil.py,sha256=Kgy5HA0xZ9hR4_cAydPfOfskhGUB4j93AQF2-9Fj-Cg,2179
189
195
  uk_bin_collection/uk_bin_collection/councils/OldhamCouncil.py,sha256=9dlesCxNoVXlmQaqZj7QFh00smnJbm1Gnjkr_Uvzurs,1771
190
196
  uk_bin_collection/uk_bin_collection/councils/OxfordCityCouncil.py,sha256=d_bY0cXRDH4kSoWGGCTNN61MNErapSOf2WSTYDJr2r8,2318
191
197
  uk_bin_collection/uk_bin_collection/councils/PerthAndKinrossCouncil.py,sha256=Kos5GzN2co3Ij3tSHOXB9S71Yt78RROCfVRtnh7M1VU,3657
@@ -259,7 +265,7 @@ uk_bin_collection/uk_bin_collection/councils/ValeofGlamorganCouncil.py,sha256=Ph
259
265
  uk_bin_collection/uk_bin_collection/councils/ValeofWhiteHorseCouncil.py,sha256=KBKGHcWAdPC_8-CfKnLOdP7Ww6RIvlxLIJGqBsq_77g,4208
260
266
  uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py,sha256=vRfIU0Uloi1bgXqjOCpdb-EQ4oY-aismcANZRwOIFkc,4914
261
267
  uk_bin_collection/uk_bin_collection/councils/WalsallCouncil.py,sha256=wv-M3zZj0E6EIPoVB9AF_NCg_8XGM9uhqGa-F5yzoc4,2277
262
- uk_bin_collection/uk_bin_collection/councils/WalthamForest.py,sha256=P7MMw0EhpRmDbbnHb25tY5_yvYuZUFwJ1br4TOv24sY,4997
268
+ uk_bin_collection/uk_bin_collection/councils/WalthamForest.py,sha256=_0ucZrZx-x49wxV7DGZsj4zcKv25HT-o2PkPabw8r68,4997
263
269
  uk_bin_collection/uk_bin_collection/councils/WandsworthCouncil.py,sha256=aQoJpC7YkhKewQyZlsv5m4__2IPoKRsOrPxjNH5xRNo,2594
264
270
  uk_bin_collection/uk_bin_collection/councils/WarringtonBoroughCouncil.py,sha256=AB9mrV1v4pKKhfsBS8MpjO8XXBifqojSk53J9Q74Guk,1583
265
271
  uk_bin_collection/uk_bin_collection/councils/WarwickDistrictCouncil.py,sha256=DAL_f0BcIxFj7Ngkms5Q80kgSGp9y5zB35wRPudayz8,1644
@@ -268,6 +274,7 @@ uk_bin_collection/uk_bin_collection/councils/WaverleyBoroughCouncil.py,sha256=tp
268
274
  uk_bin_collection/uk_bin_collection/councils/WealdenDistrictCouncil.py,sha256=SvSSaLkx7iJjzypAwKkaJwegXkSsIQtUOS2V605kz1A,3368
269
275
  uk_bin_collection/uk_bin_collection/councils/WelhatCouncil.py,sha256=ikUft37dYNJghfe-_6Fskiq1JihqpLmLNj38QkKSUUA,2316
270
276
  uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py,sha256=pTNiyR4WoGdRnYPdMhv0T4ArVm5Blz82QBY1pSGh6eI,5513
277
+ uk_bin_collection/uk_bin_collection/councils/WestDunbartonshireCouncil.py,sha256=I73A8ehWqwowYqanBD8mAMDJH9R0cJnX3RFykrSvf_Y,2648
271
278
  uk_bin_collection/uk_bin_collection/councils/WestLancashireBoroughCouncil.py,sha256=iohI2NZSjsEnCpSGsCPZFC2EQZL5pyzcb9Ng_H0tMUE,4718
272
279
  uk_bin_collection/uk_bin_collection/councils/WestLindseyDistrictCouncil.py,sha256=ip7HWKl2ysHHwGbn4nZMQWfMSwq9eh-5DWO_Hjb7It0,4757
273
280
  uk_bin_collection/uk_bin_collection/councils/WestLothianCouncil.py,sha256=dq0jimtARvRkZiGbVFrXXZgY-BODtz3uYZ5UKn0bf64,4114
@@ -291,8 +298,8 @@ uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py,sha256=I2kBYMlsD4bId
291
298
  uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py,sha256=EQWRhZ2pEejlvm0fPyOTsOHKvUZmPnxEYO_OWRGKTjs,1158
292
299
  uk_bin_collection/uk_bin_collection/create_new_council.py,sha256=m-IhmWmeWQlFsTZC4OxuFvtw5ZtB8EAJHxJTH4O59lQ,1536
293
300
  uk_bin_collection/uk_bin_collection/get_bin_data.py,sha256=YvmHfZqanwrJ8ToGch34x-L-7yPe31nB_x77_Mgl_vo,4545
294
- uk_bin_collection-0.126.1.dist-info/LICENSE,sha256=vABBUOzcrgfaTKpzeo-si9YVEun6juDkndqA8RKdKGs,1071
295
- uk_bin_collection-0.126.1.dist-info/METADATA,sha256=fN8jdX3P00t50rKftM6CEXIrAps_JOUb9Mrb89TL9Rk,17745
296
- uk_bin_collection-0.126.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
297
- uk_bin_collection-0.126.1.dist-info/entry_points.txt,sha256=36WCSGMWSc916S3Hi1ZkazzDKHaJ6CD-4fCEFm5MIao,90
298
- uk_bin_collection-0.126.1.dist-info/RECORD,,
301
+ uk_bin_collection-0.127.0.dist-info/LICENSE,sha256=vABBUOzcrgfaTKpzeo-si9YVEun6juDkndqA8RKdKGs,1071
302
+ uk_bin_collection-0.127.0.dist-info/METADATA,sha256=cEmdgh6m1R0NjncpNXCC69NwwpGJANfqoSc1mAvW7S4,17745
303
+ uk_bin_collection-0.127.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
304
+ uk_bin_collection-0.127.0.dist-info/entry_points.txt,sha256=36WCSGMWSc916S3Hi1ZkazzDKHaJ6CD-4fCEFm5MIao,90
305
+ uk_bin_collection-0.127.0.dist-info/RECORD,,