uk_bin_collection 0.115.0__py3-none-any.whl → 0.117.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- uk_bin_collection/tests/input.json +54 -21
- uk_bin_collection/uk_bin_collection/councils/ArgyllandButeCouncil.py +67 -0
- uk_bin_collection/uk_bin_collection/councils/AshfieldDistrictCouncil.py +105 -0
- uk_bin_collection/uk_bin_collection/councils/BaberghDistrictCouncil.py +132 -0
- uk_bin_collection/uk_bin_collection/councils/CheshireWestAndChesterCouncil.py +95 -113
- uk_bin_collection/uk_bin_collection/councils/DerbyCityCouncil.py +55 -0
- uk_bin_collection/uk_bin_collection/councils/GraveshamBoroughCouncil.py +122 -0
- uk_bin_collection/uk_bin_collection/councils/MidSuffolkDistrictCouncil.py +132 -0
- uk_bin_collection/uk_bin_collection/councils/MiltonKeynesCityCouncil.py +60 -41
- uk_bin_collection/uk_bin_collection/councils/SouthStaffordshireDistrictCouncil.py +99 -0
- {uk_bin_collection-0.115.0.dist-info → uk_bin_collection-0.117.0.dist-info}/METADATA +1 -1
- {uk_bin_collection-0.115.0.dist-info → uk_bin_collection-0.117.0.dist-info}/RECORD +15 -8
- {uk_bin_collection-0.115.0.dist-info → uk_bin_collection-0.117.0.dist-info}/LICENSE +0 -0
- {uk_bin_collection-0.115.0.dist-info → uk_bin_collection-0.117.0.dist-info}/WHEEL +0 -0
- {uk_bin_collection-0.115.0.dist-info → uk_bin_collection-0.117.0.dist-info}/entry_points.txt +0 -0
@@ -32,6 +32,13 @@
|
|
32
32
|
"wiki_name": "Ards and North Down Council",
|
33
33
|
"wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
|
34
34
|
},
|
35
|
+
"ArgyllandButeCouncil": {
|
36
|
+
"uprn": "125061759",
|
37
|
+
"skip_get_url": true,
|
38
|
+
"url": "https://www.argyll-bute.gov.uk",
|
39
|
+
"wiki_name": "Argyll and Bute Council",
|
40
|
+
"wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search)."
|
41
|
+
},
|
35
42
|
"ArmaghBanbridgeCraigavonCouncil": {
|
36
43
|
"url": "https://www.armaghbanbridgecraigavon.gov.uk/",
|
37
44
|
"wiki_command_url_override": "https://www.armaghbanbridgecraigavon.gov.uk/",
|
@@ -48,13 +55,13 @@
|
|
48
55
|
"wiki_name": "Arun Council",
|
49
56
|
"wiki_note": "Pass the house name/number and postcode in their respective parameters, both wrapped in double quotes. This parser requires a Selenium webdriver."
|
50
57
|
},
|
51
|
-
"
|
52
|
-
"url": "https://
|
53
|
-
"
|
54
|
-
"
|
55
|
-
"
|
56
|
-
"wiki_name": "
|
57
|
-
"wiki_note": "
|
58
|
+
"AshfieldDistrictCouncil": {
|
59
|
+
"url": "https://www.ashfield.gov.uk",
|
60
|
+
"postcode": "NG16 6RH",
|
61
|
+
"house_number": "1",
|
62
|
+
"web_driver": "http://selenium:4444",
|
63
|
+
"wiki_name": "Ashfield District Council",
|
64
|
+
"wiki_note": "Pass the house name/number and postcode in their respective parameters, both wrapped in double quotes. This parser requires a Selenium webdriver"
|
58
65
|
},
|
59
66
|
"AshfordBoroughCouncil": {
|
60
67
|
"url": "https://ashford.gov.uk",
|
@@ -71,6 +78,13 @@
|
|
71
78
|
"wiki_name": "Aylesbury Vale Council (Buckinghamshire)",
|
72
79
|
"wiki_note": "To get the UPRN, please use [FindMyAddress](https://www.findmyaddress.co.uk/search). Returns all published collections in the past, present, future."
|
73
80
|
},
|
81
|
+
"BaberghDistrictCouncil": {
|
82
|
+
"skip_get_url": true,
|
83
|
+
"house_number": "Monday",
|
84
|
+
"url": "https://www.babergh.gov.uk",
|
85
|
+
"wiki_name": "Babergh District Council",
|
86
|
+
"wiki_note": "Use the House Number field to pass the DAY of the week for your collections. Monday/Tuesday/Wednesday/Thursday/Friday"
|
87
|
+
},
|
74
88
|
"BCPCouncil": {
|
75
89
|
"skip_get_url": true,
|
76
90
|
"uprn": "100040810214",
|
@@ -335,26 +349,19 @@
|
|
335
349
|
"wiki_note": "Both the UPRN and a one-line address are passed in the URL, which needs to be wrapped in double quotes. The one-line address is made up of the house number, street name, and postcode. Use the form [here](https://online.cheshireeast.gov.uk/mycollectionday/) to find them, then take the first line and postcode and replace all spaces with `%20`."
|
336
350
|
},
|
337
351
|
"CheshireWestAndChesterCouncil": {
|
338
|
-
"
|
339
|
-
"postcode": "CH3 9ER",
|
352
|
+
"uprn": "100012346655",
|
340
353
|
"skip_get_url": true,
|
341
|
-
"url": "https://
|
354
|
+
"url": "https://my.cheshirewestandchester.gov.uk",
|
342
355
|
"wiki_name": "Cheshire West and Chester Council",
|
343
|
-
"wiki_note": "Pass the
|
356
|
+
"wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search)."
|
344
357
|
},
|
345
358
|
"ChesterfieldBoroughCouncil": {
|
346
359
|
"uprn": "74008234",
|
347
360
|
"skip_get_url": true,
|
348
|
-
"url": "https://www.
|
361
|
+
"url": "https://www.chesterfield.gov.uk",
|
349
362
|
"wiki_name": "Chesterfield Borough Council",
|
350
363
|
"wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search)."
|
351
364
|
},
|
352
|
-
"ChesterfieldBoroughCouncil": {
|
353
|
-
"uprn": "74008234",
|
354
|
-
"skip_get_url": true,
|
355
|
-
"url": "https://www.cheshirewestandchester.gov.uk/residents/waste-and-recycling/your-bin-collection/collection-day",
|
356
|
-
"wiki_name": "Chesterfield Borough Council"
|
357
|
-
},
|
358
365
|
"ChichesterDistrictCouncil": {
|
359
366
|
"house_number": "7, Plaistow Road, Kirdford, Billingshurst, West Sussex",
|
360
367
|
"postcode": "RH14 0JT",
|
@@ -449,6 +456,12 @@
|
|
449
456
|
"wiki_name": "Dartford Borough Council",
|
450
457
|
"wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
|
451
458
|
},
|
459
|
+
"DerbyCityCouncil": {
|
460
|
+
"url": "https://www.derby.gov.uk",
|
461
|
+
"uprn": "10010684240",
|
462
|
+
"wiki_name": "Derby City Council",
|
463
|
+
"wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
|
464
|
+
},
|
452
465
|
"DerbyshireDalesDistrictCouncil": {
|
453
466
|
"postcode": "DE4 3AS",
|
454
467
|
"skip_get_url": true,
|
@@ -702,6 +715,13 @@
|
|
702
715
|
"wiki_name": "Gloucester City Council",
|
703
716
|
"wiki_note": "Pass the house number, postcode, and UPRN in their respective parameters. This parser requires a Selenium webdriver."
|
704
717
|
},
|
718
|
+
"GraveshamBoroughCouncil": {
|
719
|
+
"uprn": "100060927046",
|
720
|
+
"skip_get_url": true,
|
721
|
+
"url": "https://www.gravesham.gov.uk",
|
722
|
+
"wiki_name": "Gravesham Borough Council",
|
723
|
+
"wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search)."
|
724
|
+
},
|
705
725
|
"GuildfordCouncil": {
|
706
726
|
"house_number": "THE LODGE, PUTTENHAM HILL HOUSE, PUTTENHAM HILL, PUTTENHAM, GUILDFORD, GU3 1AH",
|
707
727
|
"postcode": "GU3 1AH",
|
@@ -982,6 +1002,13 @@
|
|
982
1002
|
"wiki_name": "Midlothian Council",
|
983
1003
|
"wiki_note": "Pass the house name/number wrapped in double quotes along with the postcode parameter."
|
984
1004
|
},
|
1005
|
+
"MidSuffolkDistrictCouncil": {
|
1006
|
+
"skip_get_url": true,
|
1007
|
+
"house_number": "Monday",
|
1008
|
+
"url": "https://www.midsuffolk.gov.uk",
|
1009
|
+
"wiki_name": "Mid Suffolk District Council",
|
1010
|
+
"wiki_note": "Use the House Number field to pass the DAY of the week for your collections. Monday/Tuesday/Wednesday/Thursday/Friday"
|
1011
|
+
},
|
985
1012
|
"MidSussexDistrictCouncil": {
|
986
1013
|
"house_number": "OAKLANDS, OAKLANDS ROAD RH16 1SS",
|
987
1014
|
"postcode": "RH16 1SS",
|
@@ -992,10 +1019,10 @@
|
|
992
1019
|
"wiki_note": "Pass the name of the street with the house number parameter, wrapped in double quotes. This parser requires a Selenium webdriver."
|
993
1020
|
},
|
994
1021
|
"MiltonKeynesCityCouncil": {
|
995
|
-
"uprn": "
|
996
|
-
"url": "https://
|
1022
|
+
"uprn": "25109551",
|
1023
|
+
"url": "https://mycouncil.milton-keynes.gov.uk/en/service/Waste_Collection_Round_Checker",
|
997
1024
|
"wiki_name": "Milton Keynes City Council",
|
998
|
-
"wiki_note": "
|
1025
|
+
"wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN."
|
999
1026
|
},
|
1000
1027
|
"MoleValleyDistrictCouncil": {
|
1001
1028
|
"postcode": "RH4 1SJ",
|
@@ -1421,6 +1448,12 @@
|
|
1421
1448
|
"wiki_name": "South Ribble Council",
|
1422
1449
|
"wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN."
|
1423
1450
|
},
|
1451
|
+
"SouthStaffordshireDistrictCouncil": {
|
1452
|
+
"uprn": "200004523954",
|
1453
|
+
"url": "https://www.sstaffs.gov.uk/where-i-live?uprn=200004523954",
|
1454
|
+
"wiki_name": "South Staffordshire District Council",
|
1455
|
+
"wiki_note": "The URL needs to be `https://www.sstaffs.gov.uk/where-i-live?uprn=<Your_UPRN>`. Replace `<Your_UPRN>` with your UPRN."
|
1456
|
+
},
|
1424
1457
|
"SouthTynesideCouncil": {
|
1425
1458
|
"house_number": "1",
|
1426
1459
|
"postcode": "NE33 3JW",
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import time
|
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
|
+
|
20
|
+
user_uprn = kwargs.get("uprn")
|
21
|
+
check_uprn(user_uprn)
|
22
|
+
user_uprn = user_uprn.zfill(12)
|
23
|
+
bindata = {"bins": []}
|
24
|
+
|
25
|
+
URI = "https://www.argyll-bute.gov.uk/rubbish-and-recycling/household-waste/bin-collection"
|
26
|
+
|
27
|
+
data = {"addressSelect": user_uprn}
|
28
|
+
|
29
|
+
s = requests.session()
|
30
|
+
r = s.post(URI, data=data)
|
31
|
+
r.raise_for_status()
|
32
|
+
|
33
|
+
soup = BeautifulSoup(r.content, features="html.parser")
|
34
|
+
soup.prettify()
|
35
|
+
|
36
|
+
# Find the table and extract the rows with bin schedule information
|
37
|
+
table = soup.find("table", class_="table table-bordered")
|
38
|
+
rows = table.find_all("tr")[1:] # Skip the header row
|
39
|
+
|
40
|
+
current_year = datetime.now().year
|
41
|
+
# Loop through each row and extract the bin type and collection date
|
42
|
+
for row in rows:
|
43
|
+
cells = row.find_all("td")
|
44
|
+
bin_type = cells[0].get_text(strip=True)
|
45
|
+
collection_date = cells[1].get_text(strip=True)
|
46
|
+
|
47
|
+
collection_date = datetime.strptime(
|
48
|
+
collection_date,
|
49
|
+
"%A %d %B",
|
50
|
+
)
|
51
|
+
|
52
|
+
if collection_date.month == 1:
|
53
|
+
collection_date = collection_date.replace(year=current_year + 1)
|
54
|
+
else:
|
55
|
+
collection_date = collection_date.replace(year=current_year)
|
56
|
+
|
57
|
+
dict_data = {
|
58
|
+
"type": bin_type,
|
59
|
+
"collectionDate": collection_date.strftime(date_format),
|
60
|
+
}
|
61
|
+
bindata["bins"].append(dict_data)
|
62
|
+
|
63
|
+
bindata["bins"].sort(
|
64
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
|
65
|
+
)
|
66
|
+
|
67
|
+
return bindata
|
@@ -0,0 +1,105 @@
|
|
1
|
+
import time
|
2
|
+
from datetime import datetime
|
3
|
+
|
4
|
+
from bs4 import BeautifulSoup
|
5
|
+
from selenium import webdriver
|
6
|
+
from selenium.webdriver.common.by import By
|
7
|
+
from selenium.webdriver.support import expected_conditions as EC
|
8
|
+
from selenium.webdriver.support.wait import WebDriverWait
|
9
|
+
|
10
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
11
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
12
|
+
|
13
|
+
|
14
|
+
class CouncilClass(AbstractGetBinDataClass):
|
15
|
+
"""
|
16
|
+
Concrete classes have to implement all abstract operations of the
|
17
|
+
base class. They can also override some operations with a default
|
18
|
+
implementation.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
22
|
+
# Get and check UPRN
|
23
|
+
user_postcode = kwargs.get("postcode")
|
24
|
+
user_paon = kwargs.get("paon")
|
25
|
+
check_paon(user_paon)
|
26
|
+
check_postcode(user_postcode)
|
27
|
+
web_driver = kwargs.get("web_driver")
|
28
|
+
headless = kwargs.get("headless")
|
29
|
+
bindata = {"bins": []}
|
30
|
+
|
31
|
+
API_URL = "https://portal.digital.ashfield.gov.uk/w/webpage/raise-case?service=bin_calendar"
|
32
|
+
|
33
|
+
# Create Selenium webdriver
|
34
|
+
driver = create_webdriver(web_driver, headless, None, __name__)
|
35
|
+
driver.get(API_URL)
|
36
|
+
|
37
|
+
title = WebDriverWait(driver, 10).until(
|
38
|
+
EC.presence_of_element_located((By.ID, "sub_page_title"))
|
39
|
+
)
|
40
|
+
|
41
|
+
# Wait for the postcode field to appear then populate it
|
42
|
+
WebDriverWait(driver, 10).until(
|
43
|
+
EC.presence_of_element_located(
|
44
|
+
(By.CSS_SELECTOR, "input.relation_path_type_ahead_search")
|
45
|
+
)
|
46
|
+
)
|
47
|
+
|
48
|
+
inputElement_postcode = WebDriverWait(driver, 10).until(
|
49
|
+
EC.presence_of_element_located(
|
50
|
+
(By.CSS_SELECTOR, "input.relation_path_type_ahead_search")
|
51
|
+
)
|
52
|
+
)
|
53
|
+
inputElement_postcode.clear()
|
54
|
+
inputElement_postcode.send_keys(user_postcode)
|
55
|
+
|
56
|
+
# Wait for the 'Select your property' dropdown to appear and select the first result
|
57
|
+
dropdown = WebDriverWait(driver, 10).until(
|
58
|
+
EC.element_to_be_clickable(
|
59
|
+
(
|
60
|
+
By.CLASS_NAME,
|
61
|
+
"result_list ",
|
62
|
+
)
|
63
|
+
)
|
64
|
+
)
|
65
|
+
|
66
|
+
address_element = (
|
67
|
+
WebDriverWait(driver, 10)
|
68
|
+
.until(
|
69
|
+
EC.element_to_be_clickable(
|
70
|
+
(By.XPATH, f"//li[starts-with(@aria-label, '{user_paon}')]")
|
71
|
+
)
|
72
|
+
)
|
73
|
+
.click()
|
74
|
+
)
|
75
|
+
|
76
|
+
search_button = WebDriverWait(driver, 10).until(
|
77
|
+
EC.element_to_be_clickable(
|
78
|
+
(By.XPATH, "//input[@type='submit' and @value='Search']")
|
79
|
+
)
|
80
|
+
)
|
81
|
+
search_button.click()
|
82
|
+
|
83
|
+
time.sleep(10)
|
84
|
+
|
85
|
+
soup = BeautifulSoup(driver.page_source, features="html.parser")
|
86
|
+
soup.prettify()
|
87
|
+
|
88
|
+
# Find the table by class name
|
89
|
+
table = soup.find("table", {"class": "table listing table-striped"})
|
90
|
+
|
91
|
+
# Iterate over each row in the tbody of the table
|
92
|
+
for row in table.find("tbody").find_all("tr"):
|
93
|
+
# Extract the service, day, and date for each row
|
94
|
+
service = row.find_all("td")[0].get_text(strip=True)
|
95
|
+
date = row.find_all("td")[2].get_text(strip=True)
|
96
|
+
|
97
|
+
dict_data = {
|
98
|
+
"type": service,
|
99
|
+
"collectionDate": datetime.strptime(date, "%a, %d %b %Y").strftime(
|
100
|
+
date_format
|
101
|
+
),
|
102
|
+
}
|
103
|
+
bindata["bins"].append(dict_data)
|
104
|
+
|
105
|
+
return bindata
|
@@ -0,0 +1,132 @@
|
|
1
|
+
import re
|
2
|
+
import time
|
3
|
+
|
4
|
+
import requests
|
5
|
+
from bs4 import BeautifulSoup
|
6
|
+
from selenium.webdriver.common.by import By
|
7
|
+
from selenium.webdriver.support import expected_conditions as EC
|
8
|
+
from selenium.webdriver.support.ui import Select
|
9
|
+
from selenium.webdriver.support.wait import WebDriverWait
|
10
|
+
|
11
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
12
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
13
|
+
|
14
|
+
|
15
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
16
|
+
class CouncilClass(AbstractGetBinDataClass):
|
17
|
+
"""
|
18
|
+
Concrete classes have to implement all abstract operations of the
|
19
|
+
base class. They can also override some operations with a default
|
20
|
+
implementation.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
24
|
+
|
25
|
+
collection_day = kwargs.get("paon")
|
26
|
+
bindata = {"bins": []}
|
27
|
+
|
28
|
+
days_of_week = [
|
29
|
+
"Monday",
|
30
|
+
"Tuesday",
|
31
|
+
"Wednesday",
|
32
|
+
"Thursday",
|
33
|
+
"Friday",
|
34
|
+
"Saturday",
|
35
|
+
"Sunday",
|
36
|
+
]
|
37
|
+
|
38
|
+
refusestartDate = datetime(2024, 11, 4)
|
39
|
+
recyclingstartDate = datetime(2024, 11, 11)
|
40
|
+
|
41
|
+
offset_days = days_of_week.index(collection_day)
|
42
|
+
|
43
|
+
refuse_dates = get_dates_every_x_days(refusestartDate, 14, 28)
|
44
|
+
recycling_dates = get_dates_every_x_days(recyclingstartDate, 14, 28)
|
45
|
+
|
46
|
+
bank_holidays = [
|
47
|
+
("25/12/2024", 2),
|
48
|
+
("26/12/2024", 2),
|
49
|
+
("27/12/2024", 3),
|
50
|
+
("30/12/2024", 1),
|
51
|
+
("31/12/2024", 2),
|
52
|
+
("01/01/2025", 2),
|
53
|
+
("02/01/2025", 2),
|
54
|
+
("03/01/2025", 3),
|
55
|
+
("06/01/2025", 1),
|
56
|
+
("07/01/2025", 1),
|
57
|
+
("08/01/2025", 1),
|
58
|
+
("09/01/2025", 1),
|
59
|
+
("10/01/2025", 1),
|
60
|
+
("18/04/2025", 1),
|
61
|
+
("21/04/2025", 1),
|
62
|
+
("22/04/2025", 1),
|
63
|
+
("23/04/2025", 1),
|
64
|
+
("24/04/2025", 1),
|
65
|
+
("25/04/2025", 1),
|
66
|
+
("05/05/2025", 1),
|
67
|
+
("06/05/2025", 1),
|
68
|
+
("07/05/2025", 1),
|
69
|
+
("08/05/2025", 1),
|
70
|
+
("09/05/2025", 1),
|
71
|
+
("26/05/2025", 1),
|
72
|
+
("27/05/2025", 1),
|
73
|
+
("28/05/2025", 1),
|
74
|
+
("29/05/2025", 1),
|
75
|
+
("30/05/2025", 1),
|
76
|
+
("25/08/2025", 1),
|
77
|
+
("26/08/2025", 1),
|
78
|
+
("27/08/2025", 1),
|
79
|
+
("28/08/2025", 1),
|
80
|
+
("29/08/2025", 1),
|
81
|
+
]
|
82
|
+
|
83
|
+
for refuseDate in refuse_dates:
|
84
|
+
|
85
|
+
collection_date = (
|
86
|
+
datetime.strptime(refuseDate, "%d/%m/%Y") + timedelta(days=offset_days)
|
87
|
+
).strftime("%d/%m/%Y")
|
88
|
+
|
89
|
+
holiday_offset = next(
|
90
|
+
(value for date, value in bank_holidays if date == collection_date), 0
|
91
|
+
)
|
92
|
+
|
93
|
+
if holiday_offset > 0:
|
94
|
+
collection_date = (
|
95
|
+
datetime.strptime(collection_date, "%d/%m/%Y")
|
96
|
+
+ timedelta(days=holiday_offset)
|
97
|
+
).strftime("%d/%m/%Y")
|
98
|
+
|
99
|
+
dict_data = {
|
100
|
+
"type": "Refuse Bin",
|
101
|
+
"collectionDate": collection_date,
|
102
|
+
}
|
103
|
+
bindata["bins"].append(dict_data)
|
104
|
+
|
105
|
+
for recyclingDate in recycling_dates:
|
106
|
+
|
107
|
+
collection_date = (
|
108
|
+
datetime.strptime(recyclingDate, "%d/%m/%Y")
|
109
|
+
+ timedelta(days=offset_days)
|
110
|
+
).strftime("%d/%m/%Y")
|
111
|
+
|
112
|
+
holiday_offset = next(
|
113
|
+
(value for date, value in bank_holidays if date == collection_date), 0
|
114
|
+
)
|
115
|
+
|
116
|
+
if holiday_offset > 0:
|
117
|
+
collection_date = (
|
118
|
+
datetime.strptime(collection_date, "%d/%m/%Y")
|
119
|
+
+ timedelta(days=holiday_offset)
|
120
|
+
).strftime("%d/%m/%Y")
|
121
|
+
|
122
|
+
dict_data = {
|
123
|
+
"type": "Recycling Bin",
|
124
|
+
"collectionDate": collection_date,
|
125
|
+
}
|
126
|
+
bindata["bins"].append(dict_data)
|
127
|
+
|
128
|
+
bindata["bins"].sort(
|
129
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
|
130
|
+
)
|
131
|
+
|
132
|
+
return bindata
|
@@ -1,123 +1,105 @@
|
|
1
|
-
import logging
|
2
1
|
import time
|
3
2
|
|
4
|
-
|
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
|
3
|
+
import requests
|
9
4
|
|
10
5
|
from uk_bin_collection.uk_bin_collection.common import *
|
11
6
|
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
12
7
|
|
13
|
-
# Set up logging
|
14
|
-
logging.basicConfig(
|
15
|
-
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
16
|
-
)
|
17
8
|
|
9
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
18
10
|
class CouncilClass(AbstractGetBinDataClass):
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
user_uprn = kwargs.get("uprn")
|
25
|
-
user_paon = kwargs.get("paon")
|
26
|
-
user_postcode = kwargs.get("postcode")
|
27
|
-
web_driver = kwargs.get("web_driver")
|
28
|
-
headless = kwargs.get("headless")
|
29
|
-
check_paon(user_paon)
|
30
|
-
check_postcode(user_postcode)
|
31
|
-
|
32
|
-
# Create Selenium webdriver
|
33
|
-
driver = create_webdriver(web_driver, headless, None, __name__)
|
34
|
-
if headless:
|
35
|
-
driver.set_window_size(1920, 1080)
|
36
|
-
|
37
|
-
driver.get(
|
38
|
-
"https://www.cheshirewestandchester.gov.uk/residents/waste-and-recycling/your-bin-collection/collection-day"
|
39
|
-
)
|
40
|
-
wait = WebDriverWait(driver, 60)
|
41
|
-
|
42
|
-
def click_element(by, value):
|
43
|
-
element = wait.until(EC.element_to_be_clickable((by, value)))
|
44
|
-
driver.execute_script("arguments[0].scrollIntoView();", element)
|
45
|
-
element.click()
|
46
|
-
|
47
|
-
logging.info("Accepting cookies")
|
48
|
-
click_element(By.ID, "ccc-close")
|
49
|
-
|
50
|
-
logging.info("Finding collection day")
|
51
|
-
click_element(By.LINK_TEXT, "Find your collection day")
|
52
|
-
|
53
|
-
logging.info("Switching to iframe")
|
54
|
-
iframe_presence = wait.until(
|
55
|
-
EC.presence_of_element_located((By.ID, "fillform-frame-1"))
|
56
|
-
)
|
57
|
-
driver.switch_to.frame(iframe_presence)
|
58
|
-
|
59
|
-
logging.info("Entering postcode")
|
60
|
-
input_element_postcode = wait.until(
|
61
|
-
EC.presence_of_element_located(
|
62
|
-
(By.XPATH, '//input[@id="postcode_search"]')
|
63
|
-
)
|
64
|
-
)
|
65
|
-
input_element_postcode.send_keys(user_postcode)
|
66
|
-
|
67
|
-
pcsearch_btn = wait.until(
|
68
|
-
EC.element_to_be_clickable((By.XPATH, "//input[@id='postcode_search']"))
|
69
|
-
)
|
70
|
-
click_element(By.XPATH, "//input[@id='postcode_search']")
|
71
|
-
|
72
|
-
logging.info("Selecting address")
|
73
|
-
dropdown = wait.until(EC.element_to_be_clickable((By.ID, "Choose_Address")))
|
74
|
-
dropdown_options = wait.until(
|
75
|
-
EC.presence_of_element_located((By.CLASS_NAME, "lookup-option"))
|
76
|
-
)
|
77
|
-
drop_down_values = Select(dropdown)
|
78
|
-
option_element = wait.until(
|
79
|
-
EC.presence_of_element_located(
|
80
|
-
(By.CSS_SELECTOR, f'option.lookup-option[value="{str(user_uprn)}"]')
|
81
|
-
)
|
82
|
-
)
|
83
|
-
driver.execute_script("arguments[0].scrollIntoView();", option_element)
|
84
|
-
drop_down_values.select_by_value(str(user_uprn))
|
85
|
-
|
86
|
-
logging.info("Waiting for bin schedule")
|
87
|
-
wait.until(
|
88
|
-
EC.presence_of_element_located(
|
89
|
-
(By.CLASS_NAME, "bin-schedule-content-bin-card")
|
90
|
-
)
|
91
|
-
)
|
92
|
-
|
93
|
-
logging.info("Extracting bin collection data")
|
94
|
-
soup = BeautifulSoup(driver.page_source, features="html.parser")
|
95
|
-
bin_cards = soup.find_all("div", {"class": "bin-schedule-content-bin-card"})
|
96
|
-
collections = []
|
97
|
-
|
98
|
-
for card in bin_cards:
|
99
|
-
bin_info = card.find("div", {"class": "bin-schedule-content-info"})
|
100
|
-
bin_name = bin_info.find_all("p")[0].text.strip() + " bin"
|
101
|
-
bin_date_str = bin_info.find_all("p")[1].text.split(":")[1].strip()
|
102
|
-
bin_date = datetime.strptime(bin_date_str, "%A, %B %d, %Y")
|
103
|
-
collections.append((bin_name, bin_date))
|
104
|
-
|
105
|
-
ordered_data = sorted(collections, key=lambda x: x[1])
|
106
|
-
|
107
|
-
for item in ordered_data:
|
108
|
-
dict_data = {
|
109
|
-
"type": item[0].capitalize(),
|
110
|
-
"collectionDate": item[1].strftime(date_format),
|
111
|
-
}
|
112
|
-
data["bins"].append(dict_data)
|
113
|
-
|
114
|
-
logging.info("Data extraction complete")
|
115
|
-
return data
|
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
|
+
"""
|
116
16
|
|
117
|
-
|
118
|
-
logging.error(f"An error occurred: {e}")
|
119
|
-
raise
|
17
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
120
18
|
|
121
|
-
|
122
|
-
|
123
|
-
|
19
|
+
user_uprn = kwargs.get("uprn")
|
20
|
+
check_uprn(user_uprn)
|
21
|
+
bindata = {"bins": []}
|
22
|
+
|
23
|
+
SESSION_URL = "https://my.cheshirewestandchester.gov.uk/authapi/isauthenticated?uri=https://my.cheshirewestandchester.gov.uk/fillform/?iframe_id=fillform-frame-1&db_id=&hostname=my.cheshirewestandchester.gov.uk&withCredentials=true"
|
24
|
+
|
25
|
+
API_URL = "https://my.cheshirewestandchester.gov.uk/apibroker/runLookup"
|
26
|
+
|
27
|
+
headers = {
|
28
|
+
"Content-Type": "application/json",
|
29
|
+
"Accept": "application/json",
|
30
|
+
"User-Agent": "Mozilla/5.0",
|
31
|
+
"X-Requested-With": "XMLHttpRequest",
|
32
|
+
"Referer": "https://mycouncil.milton-keynes.gov.uk/fillform/?iframe_id=fillform-frame-1&db_id=",
|
33
|
+
}
|
34
|
+
s = requests.session()
|
35
|
+
r = s.get(SESSION_URL)
|
36
|
+
r.raise_for_status()
|
37
|
+
session_data = r.json()
|
38
|
+
sid = session_data["auth-session"]
|
39
|
+
params = {
|
40
|
+
"id": "609b918c7dd6d",
|
41
|
+
"repeat_against": "",
|
42
|
+
"noRetry": "false",
|
43
|
+
"getOnlyTokens": "undefined",
|
44
|
+
"log_id": "",
|
45
|
+
"app_name": "AchieveForms",
|
46
|
+
# unix_timestamp
|
47
|
+
"_": str(int(time.time() * 1000)),
|
48
|
+
"sid": sid,
|
49
|
+
}
|
50
|
+
|
51
|
+
r = s.post(API_URL, headers=headers, params=params)
|
52
|
+
r.raise_for_status()
|
53
|
+
|
54
|
+
data = r.json()
|
55
|
+
rows_data = data["integration"]["transformed"]["rows_data"]["0"]
|
56
|
+
AuthenticateResponse = rows_data["AuthenticateResponse"]
|
57
|
+
|
58
|
+
params = {
|
59
|
+
"id": "6101d23110243",
|
60
|
+
"repeat_against": "",
|
61
|
+
"noRetry": "false",
|
62
|
+
"getOnlyTokens": "undefined",
|
63
|
+
"log_id": "",
|
64
|
+
"app_name": "AchieveForms",
|
65
|
+
# unix_timestamp
|
66
|
+
"_": str(int(time.time() * 1000)),
|
67
|
+
"sid": sid,
|
68
|
+
}
|
69
|
+
|
70
|
+
data = {
|
71
|
+
"formValues": {
|
72
|
+
"Section 1": {
|
73
|
+
"UPRN": {
|
74
|
+
"value": user_uprn,
|
75
|
+
},
|
76
|
+
"AuthenticateResponse": {
|
77
|
+
"value": AuthenticateResponse,
|
78
|
+
},
|
79
|
+
}
|
80
|
+
},
|
81
|
+
}
|
82
|
+
|
83
|
+
r = s.post(API_URL, json=data, headers=headers, params=params)
|
84
|
+
r.raise_for_status()
|
85
|
+
|
86
|
+
data = r.json()
|
87
|
+
rows_data = data["integration"]["transformed"]["rows_data"]
|
88
|
+
if not isinstance(rows_data, dict):
|
89
|
+
raise ValueError("Invalid data returned from API")
|
90
|
+
|
91
|
+
# Extract each service's relevant details for the bin schedule
|
92
|
+
for item in rows_data.values():
|
93
|
+
dict_data = {
|
94
|
+
"type": item["serviceType"],
|
95
|
+
"collectionDate": datetime.strptime(
|
96
|
+
item["collectionDateTime"], "%Y-%m-%dT%H:%M:%S"
|
97
|
+
).strftime(date_format),
|
98
|
+
}
|
99
|
+
bindata["bins"].append(dict_data)
|
100
|
+
|
101
|
+
bindata["bins"].sort(
|
102
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
|
103
|
+
)
|
104
|
+
|
105
|
+
return bindata
|
@@ -0,0 +1,55 @@
|
|
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://secure.derby.gov.uk/binday/Binday?search.PremisesId={user_uprn}"
|
23
|
+
|
24
|
+
# Make the GET request
|
25
|
+
session = requests.Session()
|
26
|
+
response = session.get(URI)
|
27
|
+
|
28
|
+
soup = BeautifulSoup(response.content, "html.parser")
|
29
|
+
|
30
|
+
# Find all divs with class "binresult" which contain the bin collection information
|
31
|
+
bin_results = soup.find_all("div", class_="binresult")
|
32
|
+
|
33
|
+
# Loop through each bin result to extract date and bin type
|
34
|
+
for result in bin_results:
|
35
|
+
# Find the collection date
|
36
|
+
date_text = result.find("p").strong.get_text(strip=True)
|
37
|
+
|
38
|
+
# Find the bin type by looking at the 'alt' attribute of the img tag
|
39
|
+
bin_type = result.find("img")["alt"]
|
40
|
+
|
41
|
+
if bin_type != "No bins":
|
42
|
+
dict_data = {
|
43
|
+
"type": bin_type,
|
44
|
+
"collectionDate": datetime.strptime(
|
45
|
+
date_text,
|
46
|
+
"%A, %d %B %Y:",
|
47
|
+
).strftime(date_format),
|
48
|
+
}
|
49
|
+
bindata["bins"].append(dict_data)
|
50
|
+
|
51
|
+
bindata["bins"].sort(
|
52
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
|
53
|
+
)
|
54
|
+
|
55
|
+
return bindata
|
@@ -0,0 +1,122 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
import requests
|
4
|
+
from dateutil.relativedelta import relativedelta
|
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
|
+
|
20
|
+
user_uprn = kwargs.get("uprn")
|
21
|
+
check_uprn(user_uprn)
|
22
|
+
bindata = {"bins": []}
|
23
|
+
|
24
|
+
SESSION_URL = "https://my.gravesham.gov.uk/authapi/isauthenticated?uri=https%253A%252F%252Fmy.gravesham.gov.uk%252Fen%252FAchieveForms%252F%253Fform_uri%253Dsandbox-publish%253A%252F%252FAF-Process-22218d5c-c6d6-492f-b627-c713771126be%252FAF-Stage-905e87c1-144b-4a72-8932-5518ddd3e618%252Fdefinition.json%2526redirectlink%253D%25252Fen%2526cancelRedirectLink%253D%25252Fen%2526consentMessage%253Dyes&hostname=my.gravesham.gov.uk&withCredentials=true"
|
25
|
+
|
26
|
+
API_URL = "https://my.gravesham.gov.uk/apibroker/runLookup"
|
27
|
+
|
28
|
+
headers = {
|
29
|
+
"Content-Type": "application/json",
|
30
|
+
"Accept": "application/json",
|
31
|
+
"User-Agent": "Mozilla/5.0",
|
32
|
+
"X-Requested-With": "XMLHttpRequest",
|
33
|
+
"Referer": "https://my.gravesham.gov.uk/fillform/?iframe_id=fillform-frame-1&db_id=",
|
34
|
+
}
|
35
|
+
s = requests.session()
|
36
|
+
r = s.get(SESSION_URL)
|
37
|
+
r.raise_for_status()
|
38
|
+
session_data = r.json()
|
39
|
+
sid = session_data["auth-session"]
|
40
|
+
params = {
|
41
|
+
"id": "5ee8854759297",
|
42
|
+
"repeat_against": "",
|
43
|
+
"noRetry": "false",
|
44
|
+
"getOnlyTokens": "undefined",
|
45
|
+
"log_id": "",
|
46
|
+
"app_name": "AF-Renderer::Self",
|
47
|
+
# unix_timestamp
|
48
|
+
"_": str(int(time.time() * 1000)),
|
49
|
+
"sid": sid,
|
50
|
+
}
|
51
|
+
r = s.post(API_URL, headers=headers, params=params)
|
52
|
+
r.raise_for_status()
|
53
|
+
|
54
|
+
data = r.json()
|
55
|
+
rows_data = data["integration"]["transformed"]["rows_data"]["0"]
|
56
|
+
tokenString = rows_data["tokenString"]
|
57
|
+
|
58
|
+
# Get the current date and time
|
59
|
+
current_datetime = datetime.now()
|
60
|
+
future_datetime = current_datetime + relativedelta(months=1)
|
61
|
+
|
62
|
+
# Format it using strftime
|
63
|
+
current_datetime = current_datetime.strftime("%Y-%m-%dT%H:%M:%S")
|
64
|
+
future_datetime = future_datetime.strftime("%Y-%m-%dT%H:%M:%S")
|
65
|
+
|
66
|
+
data = {
|
67
|
+
"formValues": {
|
68
|
+
"Check your bin day": {
|
69
|
+
"tokenString": {
|
70
|
+
"value": tokenString,
|
71
|
+
},
|
72
|
+
"UPRNForAPI": {
|
73
|
+
"value": user_uprn,
|
74
|
+
},
|
75
|
+
"formatDateToday": {
|
76
|
+
"value": current_datetime,
|
77
|
+
},
|
78
|
+
"formatDateTo": {
|
79
|
+
"value": future_datetime,
|
80
|
+
},
|
81
|
+
}
|
82
|
+
},
|
83
|
+
}
|
84
|
+
|
85
|
+
params = {
|
86
|
+
"id": "5c8f869376376",
|
87
|
+
"repeat_against": "",
|
88
|
+
"noRetry": "false",
|
89
|
+
"getOnlyTokens": "undefined",
|
90
|
+
"log_id": "",
|
91
|
+
"app_name": "AF-Renderer::Self",
|
92
|
+
# unix_timestamp
|
93
|
+
"_": str(int(time.time() * 1000)),
|
94
|
+
"sid": sid,
|
95
|
+
}
|
96
|
+
r = s.post(API_URL, json=data, headers=headers, params=params)
|
97
|
+
r.raise_for_status()
|
98
|
+
|
99
|
+
data = r.json()
|
100
|
+
|
101
|
+
rows_data = data["integration"]["transformed"]["rows_data"]
|
102
|
+
if not isinstance(rows_data, dict):
|
103
|
+
raise ValueError("Invalid data returned from API")
|
104
|
+
|
105
|
+
# Extract each service's relevant details for the bin schedule
|
106
|
+
for item in rows_data.values():
|
107
|
+
if item["Name"]:
|
108
|
+
Bin_Types = item["Name"].split("Empty Bin ")
|
109
|
+
for Bin_Type in Bin_Types:
|
110
|
+
if Bin_Type:
|
111
|
+
dict_data = {
|
112
|
+
"type": Bin_Type.strip(),
|
113
|
+
"collectionDate": datetime.strptime(
|
114
|
+
item["Date"], "%Y-%m-%dT%H:%M:%S"
|
115
|
+
).strftime(date_format),
|
116
|
+
}
|
117
|
+
bindata["bins"].append(dict_data)
|
118
|
+
|
119
|
+
bindata["bins"].sort(
|
120
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
|
121
|
+
)
|
122
|
+
return bindata
|
@@ -0,0 +1,132 @@
|
|
1
|
+
import re
|
2
|
+
import time
|
3
|
+
|
4
|
+
import requests
|
5
|
+
from bs4 import BeautifulSoup
|
6
|
+
from selenium.webdriver.common.by import By
|
7
|
+
from selenium.webdriver.support import expected_conditions as EC
|
8
|
+
from selenium.webdriver.support.ui import Select
|
9
|
+
from selenium.webdriver.support.wait import WebDriverWait
|
10
|
+
|
11
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
12
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
13
|
+
|
14
|
+
|
15
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
16
|
+
class CouncilClass(AbstractGetBinDataClass):
|
17
|
+
"""
|
18
|
+
Concrete classes have to implement all abstract operations of the
|
19
|
+
base class. They can also override some operations with a default
|
20
|
+
implementation.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
24
|
+
|
25
|
+
collection_day = kwargs.get("paon")
|
26
|
+
bindata = {"bins": []}
|
27
|
+
|
28
|
+
days_of_week = [
|
29
|
+
"Monday",
|
30
|
+
"Tuesday",
|
31
|
+
"Wednesday",
|
32
|
+
"Thursday",
|
33
|
+
"Friday",
|
34
|
+
"Saturday",
|
35
|
+
"Sunday",
|
36
|
+
]
|
37
|
+
|
38
|
+
refusestartDate = datetime(2024, 11, 11)
|
39
|
+
recyclingstartDate = datetime(2024, 11, 4)
|
40
|
+
|
41
|
+
offset_days = days_of_week.index(collection_day)
|
42
|
+
|
43
|
+
refuse_dates = get_dates_every_x_days(refusestartDate, 14, 28)
|
44
|
+
recycling_dates = get_dates_every_x_days(recyclingstartDate, 14, 28)
|
45
|
+
|
46
|
+
bank_holidays = [
|
47
|
+
("25/12/2024", 2),
|
48
|
+
("26/12/2024", 2),
|
49
|
+
("27/12/2024", 3),
|
50
|
+
("30/12/2024", 1),
|
51
|
+
("31/12/2024", 2),
|
52
|
+
("01/01/2025", 2),
|
53
|
+
("02/01/2025", 2),
|
54
|
+
("03/01/2025", 3),
|
55
|
+
("06/01/2025", 1),
|
56
|
+
("07/01/2025", 1),
|
57
|
+
("08/01/2025", 1),
|
58
|
+
("09/01/2025", 1),
|
59
|
+
("10/01/2025", 1),
|
60
|
+
("18/04/2025", 1),
|
61
|
+
("21/04/2025", 1),
|
62
|
+
("22/04/2025", 1),
|
63
|
+
("23/04/2025", 1),
|
64
|
+
("24/04/2025", 1),
|
65
|
+
("25/04/2025", 1),
|
66
|
+
("05/05/2025", 1),
|
67
|
+
("06/05/2025", 1),
|
68
|
+
("07/05/2025", 1),
|
69
|
+
("08/05/2025", 1),
|
70
|
+
("09/05/2025", 1),
|
71
|
+
("26/05/2025", 1),
|
72
|
+
("27/05/2025", 1),
|
73
|
+
("28/05/2025", 1),
|
74
|
+
("29/05/2025", 1),
|
75
|
+
("30/05/2025", 1),
|
76
|
+
("25/08/2025", 1),
|
77
|
+
("26/08/2025", 1),
|
78
|
+
("27/08/2025", 1),
|
79
|
+
("28/08/2025", 1),
|
80
|
+
("29/08/2025", 1),
|
81
|
+
]
|
82
|
+
|
83
|
+
for refuseDate in refuse_dates:
|
84
|
+
|
85
|
+
collection_date = (
|
86
|
+
datetime.strptime(refuseDate, "%d/%m/%Y") + timedelta(days=offset_days)
|
87
|
+
).strftime("%d/%m/%Y")
|
88
|
+
|
89
|
+
holiday_offset = next(
|
90
|
+
(value for date, value in bank_holidays if date == collection_date), 0
|
91
|
+
)
|
92
|
+
|
93
|
+
if holiday_offset > 0:
|
94
|
+
collection_date = (
|
95
|
+
datetime.strptime(collection_date, "%d/%m/%Y")
|
96
|
+
+ timedelta(days=holiday_offset)
|
97
|
+
).strftime("%d/%m/%Y")
|
98
|
+
|
99
|
+
dict_data = {
|
100
|
+
"type": "Refuse Bin",
|
101
|
+
"collectionDate": collection_date,
|
102
|
+
}
|
103
|
+
bindata["bins"].append(dict_data)
|
104
|
+
|
105
|
+
for recyclingDate in recycling_dates:
|
106
|
+
|
107
|
+
collection_date = (
|
108
|
+
datetime.strptime(recyclingDate, "%d/%m/%Y")
|
109
|
+
+ timedelta(days=offset_days)
|
110
|
+
).strftime("%d/%m/%Y")
|
111
|
+
|
112
|
+
holiday_offset = next(
|
113
|
+
(value for date, value in bank_holidays if date == collection_date), 0
|
114
|
+
)
|
115
|
+
|
116
|
+
if holiday_offset > 0:
|
117
|
+
collection_date = (
|
118
|
+
datetime.strptime(collection_date, "%d/%m/%Y")
|
119
|
+
+ timedelta(days=holiday_offset)
|
120
|
+
).strftime("%d/%m/%Y")
|
121
|
+
|
122
|
+
dict_data = {
|
123
|
+
"type": "Recycling Bin",
|
124
|
+
"collectionDate": collection_date,
|
125
|
+
}
|
126
|
+
bindata["bins"].append(dict_data)
|
127
|
+
|
128
|
+
bindata["bins"].sort(
|
129
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
|
130
|
+
)
|
131
|
+
|
132
|
+
return bindata
|
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
import time
|
2
|
+
|
3
|
+
import requests
|
4
|
+
|
2
5
|
from uk_bin_collection.uk_bin_collection.common import *
|
3
6
|
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
4
7
|
|
@@ -12,43 +15,59 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
12
15
|
"""
|
13
16
|
|
14
17
|
def parse_data(self, page: str, **kwargs) -> dict:
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
18
|
+
|
19
|
+
user_uprn = kwargs.get("uprn")
|
20
|
+
check_uprn(user_uprn)
|
21
|
+
bindata = {"bins": []}
|
22
|
+
|
23
|
+
SESSION_URL = "https://mycouncil.milton-keynes.gov.uk/authapi/isauthenticated?uri=https%253A%252F%252Fmycouncil.milton-keynes.gov.uk%252Fen%252Fservice%252FWaste_Collection_Round_Checker&hostname=mycouncil.milton-keynes.gov.uk&withCredentials=true"
|
24
|
+
|
25
|
+
API_URL = "https://mycouncil.milton-keynes.gov.uk/apibroker/runLookup"
|
26
|
+
|
27
|
+
data = {
|
28
|
+
"formValues": {"Section 1": {"uprnCore": {"value": user_uprn}}},
|
29
|
+
}
|
30
|
+
|
31
|
+
headers = {
|
32
|
+
"Content-Type": "application/json",
|
33
|
+
"Accept": "application/json",
|
34
|
+
"User-Agent": "Mozilla/5.0",
|
35
|
+
"X-Requested-With": "XMLHttpRequest",
|
36
|
+
"Referer": "https://mycouncil.milton-keynes.gov.uk/fillform/?iframe_id=fillform-frame-1&db_id=",
|
37
|
+
}
|
38
|
+
s = requests.session()
|
39
|
+
r = s.get(SESSION_URL)
|
40
|
+
r.raise_for_status()
|
41
|
+
session_data = r.json()
|
42
|
+
sid = session_data["auth-session"]
|
43
|
+
params = {
|
44
|
+
"id": "64d9feda3a507",
|
45
|
+
"repeat_against": "",
|
46
|
+
"noRetry": "false",
|
47
|
+
"getOnlyTokens": "undefined",
|
48
|
+
"log_id": "",
|
49
|
+
"app_name": "AF-Renderer::Self",
|
50
|
+
# unix_timestamp
|
51
|
+
"_": str(int(time.time() * 1000)),
|
52
|
+
"sid": sid,
|
53
|
+
}
|
54
|
+
|
55
|
+
r = s.post(API_URL, json=data, headers=headers, params=params)
|
56
|
+
r.raise_for_status()
|
57
|
+
|
58
|
+
data = r.json()
|
59
|
+
rows_data = data["integration"]["transformed"]["rows_data"]
|
60
|
+
if not isinstance(rows_data, dict):
|
61
|
+
raise ValueError("Invalid data returned from API")
|
62
|
+
|
63
|
+
# Extract each service's relevant details for the bin schedule
|
64
|
+
for item in rows_data.values():
|
65
|
+
dict_data = {
|
66
|
+
"type": item["AssetTypeName"],
|
67
|
+
"collectionDate": datetime.strptime(
|
68
|
+
item["NextInstance"], "%Y-%m-%d"
|
69
|
+
).strftime(date_format),
|
70
|
+
}
|
71
|
+
bindata["bins"].append(dict_data)
|
72
|
+
|
73
|
+
return bindata
|
@@ -0,0 +1,99 @@
|
|
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_date(self, date_str):
|
16
|
+
months = {
|
17
|
+
"January": "01",
|
18
|
+
"February": "02",
|
19
|
+
"March": "03",
|
20
|
+
"April": "04",
|
21
|
+
"May": "05",
|
22
|
+
"June": "06",
|
23
|
+
"July": "07",
|
24
|
+
"August": "08",
|
25
|
+
"September": "09",
|
26
|
+
"October": "10",
|
27
|
+
"November": "11",
|
28
|
+
"December": "12",
|
29
|
+
}
|
30
|
+
day, date, month_abbr, year = date_str.split()
|
31
|
+
month = months[month_abbr]
|
32
|
+
return f"{date}/{month}/{year}"
|
33
|
+
|
34
|
+
def add_bin_types_to_collection(
|
35
|
+
self, bin_data: {"bins": []}, collection_date: str, collectionType: str
|
36
|
+
):
|
37
|
+
if "Grey Bin" in collectionType:
|
38
|
+
bin_data["bins"].append(
|
39
|
+
{
|
40
|
+
"type": "Grey Bin",
|
41
|
+
"collectionDate": self.parse_date(collection_date),
|
42
|
+
}
|
43
|
+
)
|
44
|
+
if "Green Bin" in collectionType:
|
45
|
+
bin_data["bins"].append(
|
46
|
+
{
|
47
|
+
"type": "Green Bin",
|
48
|
+
"collectionDate": self.parse_date(collection_date),
|
49
|
+
}
|
50
|
+
)
|
51
|
+
|
52
|
+
if "Blue Bin" in collectionType:
|
53
|
+
bin_data["bins"].append(
|
54
|
+
{
|
55
|
+
"type": "Blue Bin",
|
56
|
+
"collectionDate": self.parse_date(collection_date),
|
57
|
+
}
|
58
|
+
)
|
59
|
+
|
60
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
61
|
+
# Make a BS4 object
|
62
|
+
soup = BeautifulSoup(page.text, features="html.parser")
|
63
|
+
soup.prettify()
|
64
|
+
|
65
|
+
# Initialize the bin data structure
|
66
|
+
bin_data = {"bins": []}
|
67
|
+
|
68
|
+
collectionDatesSection = soup.find("div", id="showCollectionDates")
|
69
|
+
|
70
|
+
# Find next date
|
71
|
+
collection_date = collectionDatesSection.find(
|
72
|
+
"p", class_="collection-date"
|
73
|
+
).getText()
|
74
|
+
|
75
|
+
# convert to date
|
76
|
+
collection_type = collectionDatesSection.find(
|
77
|
+
"p", class_="collection-type"
|
78
|
+
).getText()
|
79
|
+
|
80
|
+
self.add_bin_types_to_collection(bin_data, collection_date, collection_type)
|
81
|
+
|
82
|
+
# Find the table with collection dates
|
83
|
+
table = collectionDatesSection.find("table", class_="leisure-table")
|
84
|
+
|
85
|
+
# Extract the rows containing the bin collection information
|
86
|
+
rows = table.find_all("tr")
|
87
|
+
|
88
|
+
# Loop through the rows and extract bin data
|
89
|
+
for row in rows:
|
90
|
+
cells = row.find_all("td")
|
91
|
+
if len(cells) == 2:
|
92
|
+
collection_date = cells[1].get_text(strip=True)
|
93
|
+
collection_type = cells[0].get_text(strip=True)
|
94
|
+
|
95
|
+
self.add_bin_types_to_collection(
|
96
|
+
bin_data, collection_date, collection_type
|
97
|
+
)
|
98
|
+
|
99
|
+
return bin_data
|
@@ -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=
|
5
|
+
uk_bin_collection/tests/input.json,sha256=SYYyHZnd-yzGAz4ZLulu4DWjY4ykaCQ3zQgtkLdYVpg,101933
|
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
|
@@ -16,11 +16,14 @@ uk_bin_collection/uk_bin_collection/councils/AberdeenshireCouncil.py,sha256=aO1C
|
|
16
16
|
uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py,sha256=ppbrmm-MzB1wOulK--CU_0j4P-djNf3ozMhHnmQFqLo,1511
|
17
17
|
uk_bin_collection/uk_bin_collection/councils/AntrimAndNewtonabbeyCouncil.py,sha256=Hp5pteaC5RjL5ZqPZ564S9WQ6ZTKLMO6Dl_fxip2TUc,1653
|
18
18
|
uk_bin_collection/uk_bin_collection/councils/ArdsAndNorthDownCouncil.py,sha256=iMBldxNErgi-ok1o6xpqdNgMvR6qapaNqoTWDTqMeGo,3824
|
19
|
+
uk_bin_collection/uk_bin_collection/councils/ArgyllandButeCouncil.py,sha256=fJ0UvuSCbzFE9CPoxt1U9CJeFsbTKts_5GRBc3E9Eno,2201
|
19
20
|
uk_bin_collection/uk_bin_collection/councils/ArmaghBanbridgeCraigavonCouncil.py,sha256=o9NBbVCTdxKXnpYbP8-zxe1Gh8s57vwfV75Son_sAHE,2863
|
20
21
|
uk_bin_collection/uk_bin_collection/councils/ArunCouncil.py,sha256=yfhthv9nuogP19VOZ3TYQrq51qqjiCZcSel4sXhiKjs,4012
|
22
|
+
uk_bin_collection/uk_bin_collection/councils/AshfieldDistrictCouncil.py,sha256=2kZt9HGCVK-n0aq2VFWG6yiWihXjRf8MnksdQLMj4LU,3555
|
21
23
|
uk_bin_collection/uk_bin_collection/councils/AshfordBoroughCouncil.py,sha256=yC-8UMQHSbvze43PJ2_F4Z3cu7M7cynKTojipBJU7Ug,4307
|
22
24
|
uk_bin_collection/uk_bin_collection/councils/AylesburyValeCouncil.py,sha256=LouqjspEMt1TkOGqWHs2zkxwOETIy3n7p64uKIlAgUg,2401
|
23
25
|
uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py,sha256=W7QBx6Mgso8RYosuXsaYo3GGNAu-tiyBSmuYxr1JSOU,1707
|
26
|
+
uk_bin_collection/uk_bin_collection/councils/BaberghDistrictCouncil.py,sha256=T0Awn7afXhpyp3-R3IjZcowhcBgfXNlimRlODDAMMdQ,4094
|
24
27
|
uk_bin_collection/uk_bin_collection/councils/BarnetCouncil.py,sha256=Sd4-pbv0QZsR7soxvXYqsfdOUIqZqS6notyoZthG77s,9182
|
25
28
|
uk_bin_collection/uk_bin_collection/councils/BarnsleyMBCouncil.py,sha256=RKuH8HzGc3Q0WtLg-g_xVMn9hUYqdENgfcvvR4Bx5PI,4763
|
26
29
|
uk_bin_collection/uk_bin_collection/councils/BasildonCouncil.py,sha256=NymPmq5pud0PJ8ePcc2r1SKED4EHQ0EY2l71O-Metxc,3313
|
@@ -54,7 +57,7 @@ uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py,sha25
|
|
54
57
|
uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py,sha256=tXfzMetN6wxahuGGRp2mIyCCDSL4F2aG61HhUxw6COQ,2172
|
55
58
|
uk_bin_collection/uk_bin_collection/councils/ChelmsfordCityCouncil.py,sha256=EB88D0MNJwuDZ2GX1ENc5maGYx17mnHTCtNl6s-v11E,5090
|
56
59
|
uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py,sha256=73P5GF6Y7Ud1hNgyFypvT1d6eBWjzdBjr3h0Dsl6NWw,1679
|
57
|
-
uk_bin_collection/uk_bin_collection/councils/CheshireWestAndChesterCouncil.py,sha256=
|
60
|
+
uk_bin_collection/uk_bin_collection/councils/CheshireWestAndChesterCouncil.py,sha256=5mKZf22NgdyBY-SqV0c2q8b8IJobkoZrsfGEVUcxUyM,3544
|
58
61
|
uk_bin_collection/uk_bin_collection/councils/ChesterfieldBoroughCouncil.py,sha256=mZiM8Ugm_OP0JkC5pLaQmi4i79mAp4SNNrcIdsREjHw,7198
|
59
62
|
uk_bin_collection/uk_bin_collection/councils/ChichesterDistrictCouncil.py,sha256=HxrLcJves7ZsE8FbooymeecTUmScY4R7Oi71vwCePPo,4118
|
60
63
|
uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py,sha256=M7HjuUaFq8aSnOf_9m1QS4MmPPMmPhF3mLHSrfDPtV0,5194
|
@@ -67,6 +70,7 @@ uk_bin_collection/uk_bin_collection/councils/CrawleyBoroughCouncil.py,sha256=_BE
|
|
67
70
|
uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py,sha256=Vxh5ICoaXTAvx0nDOq_95XQ4He9sQKcLdI5keV2uxM4,11384
|
68
71
|
uk_bin_collection/uk_bin_collection/councils/DacorumBoroughCouncil.py,sha256=Tm_6pvBPj-6qStbe6-02LXaoCOlnnDvVXAAocGVvf_E,3970
|
69
72
|
uk_bin_collection/uk_bin_collection/councils/DartfordBoroughCouncil.py,sha256=SPirUUoweMwX5Txtsr0ocdcFtKxCQ9LhzTTJN20tM4w,1550
|
73
|
+
uk_bin_collection/uk_bin_collection/councils/DerbyCityCouncil.py,sha256=M8FGLhZn9wdRCq1W6z_yqJQqeba3EKyba3vhM22MzB4,1883
|
70
74
|
uk_bin_collection/uk_bin_collection/councils/DerbyshireDalesDistrictCouncil.py,sha256=MQC1-jXezXczrxTcvPQvkpGgyyAbzSKlX38WsmftHak,4007
|
71
75
|
uk_bin_collection/uk_bin_collection/councils/DoncasterCouncil.py,sha256=b7pxoToXu6dBBYXsXmlwfPXE8BjHxt0hjCOBNlNgvX8,3118
|
72
76
|
uk_bin_collection/uk_bin_collection/councils/DorsetCouncil.py,sha256=XUWH5BzkjPSFBLczo-Vo-Wly2JMoabm9WtI6_Mf-pO4,1523
|
@@ -98,6 +102,7 @@ uk_bin_collection/uk_bin_collection/councils/GatesheadCouncil.py,sha256=SRCgYhYs
|
|
98
102
|
uk_bin_collection/uk_bin_collection/councils/GedlingBoroughCouncil.py,sha256=XzfFMCwclh9zAJgsbaj4jywjdiH0wPaFicaVsLrN3ms,2297
|
99
103
|
uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py,sha256=9N5GXR32gXYBl3i44TITiZ7N73rgqXZmkyenI-kVRQI,2328
|
100
104
|
uk_bin_collection/uk_bin_collection/councils/GloucesterCityCouncil.py,sha256=8Wjvmdvg5blHVrREaEnhhWZaWhYVP4v_KdDVPLIUxaU,4889
|
105
|
+
uk_bin_collection/uk_bin_collection/councils/GraveshamBoroughCouncil.py,sha256=ueQ9xFiTxMUBTGV9VjtySHA1EFWliTM0AeNePBIG9ho,4568
|
101
106
|
uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py,sha256=9pVrmQhZcK2AD8gX8mNvP--L4L9KaY6L3B822VX6fec,5695
|
102
107
|
uk_bin_collection/uk_bin_collection/councils/HaltonBoroughCouncil.py,sha256=gq_CPqi6qM2oNiHhKKF1lZC86fyKL4lPhh_DN9pJZ04,5971
|
103
108
|
uk_bin_collection/uk_bin_collection/councils/HarboroughDistrictCouncil.py,sha256=uAbCgfrqkIkEKUyLVE8l72s5tzbfMFsw775i0nVRAyc,1934
|
@@ -133,9 +138,10 @@ uk_bin_collection/uk_bin_collection/councils/MansfieldDistrictCouncil.py,sha256=
|
|
133
138
|
uk_bin_collection/uk_bin_collection/councils/MertonCouncil.py,sha256=3Y2Un4xXo1sCcMsudynODSzocV_mMofWkX2JqONDb5o,1997
|
134
139
|
uk_bin_collection/uk_bin_collection/councils/MidAndEastAntrimBoroughCouncil.py,sha256=oOWwU5FSgGej2Mv7FQ66N-EzS5nZgmGsd0WnfLWUc1I,5238
|
135
140
|
uk_bin_collection/uk_bin_collection/councils/MidDevonCouncil.py,sha256=RjBZ7R3_Pax9p1d2DCygqryjV1RP4BYvqb-rT_KyOEg,3322
|
141
|
+
uk_bin_collection/uk_bin_collection/councils/MidSuffolkDistrictCouncil.py,sha256=nnN7S1mK7HU3NPW7KsmEVkcC7Gb5rE1mmW9FCUfukWk,4094
|
136
142
|
uk_bin_collection/uk_bin_collection/councils/MidSussexDistrictCouncil.py,sha256=AZgC9wmDLEjUOtIFvf0ehF5LHturXTH4DkE3ioPSVBA,6254
|
137
143
|
uk_bin_collection/uk_bin_collection/councils/MidlothianCouncil.py,sha256=mM5-itJDNhjsT5UEjSFfWppmfmPFSns4u_1QblewuFU,5605
|
138
|
-
uk_bin_collection/uk_bin_collection/councils/MiltonKeynesCityCouncil.py,sha256=
|
144
|
+
uk_bin_collection/uk_bin_collection/councils/MiltonKeynesCityCouncil.py,sha256=7e2pGBLCw24pNItHeI9jkxQ3rEOZ4WC4zVlbvKYGdXE,2600
|
139
145
|
uk_bin_collection/uk_bin_collection/councils/MoleValleyDistrictCouncil.py,sha256=xWR5S0gwQu9gXxjl788Wux1KaC0CT7ZFw0iXuRLZCEM,5599
|
140
146
|
uk_bin_collection/uk_bin_collection/councils/NeathPortTalbotCouncil.py,sha256=ychYR2nsyk2UIb8tjWaKrLUT4hxSsHN558l3RqZ0mjw,5635
|
141
147
|
uk_bin_collection/uk_bin_collection/councils/NewForestCouncil.py,sha256=ylTn9KmWITtaO9_Z8kJCN2w2ALfhrfGt3SeJ78lgw7M,5391
|
@@ -192,6 +198,7 @@ uk_bin_collection/uk_bin_collection/councils/SouthLanarkshireCouncil.py,sha256=f
|
|
192
198
|
uk_bin_collection/uk_bin_collection/councils/SouthNorfolkCouncil.py,sha256=C2qIZjjbl9JnuukX9OH2RbfP0hSdp3uX76APGY33qKs,4622
|
193
199
|
uk_bin_collection/uk_bin_collection/councils/SouthOxfordshireCouncil.py,sha256=zW4bN3hcqNoK_Y0-vPpuZs3K0LTPvApu6_v9K-D7WjE,3879
|
194
200
|
uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py,sha256=OdexbeiI5WsCfjlsnHjAce8oGF5fW-n7q2XOuxcpHzw,3604
|
201
|
+
uk_bin_collection/uk_bin_collection/councils/SouthStaffordshireDistrictCouncil.py,sha256=ACQMHWyamnj1ag3gNF-8Jhp-DKUok1GhFdnzH4nCzwU,3201
|
195
202
|
uk_bin_collection/uk_bin_collection/councils/SouthTynesideCouncil.py,sha256=dxXGrJfg_fn2IPTBgq6Duwy0WY8GYLafMuisaCjOnbs,3426
|
196
203
|
uk_bin_collection/uk_bin_collection/councils/SouthwarkCouncil.py,sha256=Z6JIbUt3yr4oG60n1At4AjPIGrs7Qzn_sDNY-TsS62E,4882
|
197
204
|
uk_bin_collection/uk_bin_collection/councils/StAlbansCityAndDistrictCouncil.py,sha256=mPZz6Za6kTSkrfHnj0OfwtnpRYR1dKvxbuFEKnWsiL8,1451
|
@@ -252,8 +259,8 @@ uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py,sha256=I2kBYMlsD4bId
|
|
252
259
|
uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py,sha256=EQWRhZ2pEejlvm0fPyOTsOHKvUZmPnxEYO_OWRGKTjs,1158
|
253
260
|
uk_bin_collection/uk_bin_collection/create_new_council.py,sha256=m-IhmWmeWQlFsTZC4OxuFvtw5ZtB8EAJHxJTH4O59lQ,1536
|
254
261
|
uk_bin_collection/uk_bin_collection/get_bin_data.py,sha256=YvmHfZqanwrJ8ToGch34x-L-7yPe31nB_x77_Mgl_vo,4545
|
255
|
-
uk_bin_collection-0.
|
256
|
-
uk_bin_collection-0.
|
257
|
-
uk_bin_collection-0.
|
258
|
-
uk_bin_collection-0.
|
259
|
-
uk_bin_collection-0.
|
262
|
+
uk_bin_collection-0.117.0.dist-info/LICENSE,sha256=vABBUOzcrgfaTKpzeo-si9YVEun6juDkndqA8RKdKGs,1071
|
263
|
+
uk_bin_collection-0.117.0.dist-info/METADATA,sha256=bK7wQJkZL4lfbMyqO3y-K75RtMP4EoojmrDqozltkC8,17574
|
264
|
+
uk_bin_collection-0.117.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
265
|
+
uk_bin_collection-0.117.0.dist-info/entry_points.txt,sha256=36WCSGMWSc916S3Hi1ZkazzDKHaJ6CD-4fCEFm5MIao,90
|
266
|
+
uk_bin_collection-0.117.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
{uk_bin_collection-0.115.0.dist-info → uk_bin_collection-0.117.0.dist-info}/entry_points.txt
RENAMED
File without changes
|