uk_bin_collection 0.119.0__py3-none-any.whl → 0.123.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. uk_bin_collection/tests/input.json +129 -10
  2. uk_bin_collection/tests/test_common_functions.py +26 -0
  3. uk_bin_collection/uk_bin_collection/common.py +30 -6
  4. uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py +0 -1
  5. uk_bin_collection/uk_bin_collection/councils/BaberghDistrictCouncil.py +3 -1
  6. uk_bin_collection/uk_bin_collection/councils/BlabyDistrictCouncil.py +8 -5
  7. uk_bin_collection/uk_bin_collection/councils/BlackburnCouncil.py +10 -2
  8. uk_bin_collection/uk_bin_collection/councils/CarmarthenshireCountyCouncil.py +3 -3
  9. uk_bin_collection/uk_bin_collection/councils/CheltenhamBoroughCouncil.py +102 -0
  10. uk_bin_collection/uk_bin_collection/councils/CotswoldDistrictCouncil.py +3 -3
  11. uk_bin_collection/uk_bin_collection/councils/CumberlandAllerdaleCouncil.py +93 -0
  12. uk_bin_collection/uk_bin_collection/councils/EastAyrshireCouncil.py +11 -8
  13. uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py +14 -0
  14. uk_bin_collection/uk_bin_collection/councils/FolkstoneandHytheDistrictCouncil.py +81 -0
  15. uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py +17 -0
  16. uk_bin_collection/uk_bin_collection/councils/HackneyCouncil.py +85 -0
  17. uk_bin_collection/uk_bin_collection/councils/HartlepoolBoroughCouncil.py +83 -0
  18. uk_bin_collection/uk_bin_collection/councils/HighPeakCouncil.py +10 -5
  19. uk_bin_collection/uk_bin_collection/councils/HinckleyandBosworthBoroughCouncil.py +71 -0
  20. uk_bin_collection/uk_bin_collection/councils/KingsLynnandWestNorfolkBC.py +59 -0
  21. uk_bin_collection/uk_bin_collection/councils/LondonBoroughHavering.py +75 -0
  22. uk_bin_collection/uk_bin_collection/councils/LondonBoroughLewisham.py +140 -0
  23. uk_bin_collection/uk_bin_collection/councils/MidSuffolkDistrictCouncil.py +3 -1
  24. uk_bin_collection/uk_bin_collection/councils/MonmouthshireCountyCouncil.py +70 -0
  25. uk_bin_collection/uk_bin_collection/councils/MorayCouncil.py +65 -0
  26. uk_bin_collection/uk_bin_collection/councils/NewcastleUnderLymeCouncil.py +66 -0
  27. uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py +93 -0
  28. uk_bin_collection/uk_bin_collection/councils/RoyalBoroughofGreenwich.py +113 -0
  29. uk_bin_collection/uk_bin_collection/councils/SandwellBoroughCouncil.py +87 -0
  30. uk_bin_collection/uk_bin_collection/councils/ThurrockCouncil.py +93 -0
  31. uk_bin_collection/uk_bin_collection/councils/WarwickDistrictCouncil.py +29 -10
  32. uk_bin_collection/uk_bin_collection/councils/WestNorthamptonshireCouncil.py +12 -10
  33. uk_bin_collection/uk_bin_collection/councils/WyreForestDistrictCouncil.py +65 -0
  34. {uk_bin_collection-0.119.0.dist-info → uk_bin_collection-0.123.1.dist-info}/METADATA +1 -1
  35. {uk_bin_collection-0.119.0.dist-info → uk_bin_collection-0.123.1.dist-info}/RECORD +38 -21
  36. {uk_bin_collection-0.119.0.dist-info → uk_bin_collection-0.123.1.dist-info}/LICENSE +0 -0
  37. {uk_bin_collection-0.119.0.dist-info → uk_bin_collection-0.123.1.dist-info}/WHEEL +0 -0
  38. {uk_bin_collection-0.119.0.dist-info → uk_bin_collection-0.123.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,93 @@
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_postcode = kwargs.get("postcode")
19
+ user_paon = kwargs.get("paon")
20
+ check_postcode(user_postcode)
21
+ check_paon(user_paon)
22
+ bindata = {"bins": []}
23
+
24
+ URI = "https://abc-wrp.whitespacews.com/"
25
+
26
+ session = requests.Session()
27
+
28
+ # get link from first page as has some kind of unique hash
29
+ r = session.get(
30
+ URI,
31
+ )
32
+ r.raise_for_status()
33
+ soup = BeautifulSoup(r.text, features="html.parser")
34
+
35
+ alink = soup.find("a", text="View My Collections")
36
+
37
+ if alink is None:
38
+ raise Exception("Initial page did not load correctly")
39
+
40
+ # greplace 'seq' query string to skip next step
41
+ nextpageurl = alink["href"].replace("seq=1", "seq=2")
42
+
43
+ data = {
44
+ "address_name_number": user_paon,
45
+ "address_postcode": user_postcode,
46
+ }
47
+
48
+ # get list of addresses
49
+ r = session.post(nextpageurl, data)
50
+ r.raise_for_status()
51
+
52
+ soup = BeautifulSoup(r.text, features="html.parser")
53
+
54
+ # get first address (if you don't enter enough argument values this won't find the right address)
55
+ alink = soup.find("div", id="property_list").find("a")
56
+
57
+ if alink is None:
58
+ raise Exception("Address not found")
59
+
60
+ nextpageurl = URI + alink["href"]
61
+
62
+ # get collection page
63
+ r = session.get(
64
+ nextpageurl,
65
+ )
66
+ r.raise_for_status()
67
+ soup = BeautifulSoup(r.text, features="html.parser")
68
+
69
+ if soup.find("span", id="waste-hint"):
70
+ raise Exception("No scheduled services at this address")
71
+
72
+ u1s = soup.find("section", id="scheduled-collections").find_all("u1")
73
+
74
+ for u1 in u1s:
75
+ lis = u1.find_all("li", recursive=False)
76
+
77
+ date = lis[1].text.replace("\n", "")
78
+ bin_type = lis[2].text.replace("\n", "")
79
+
80
+ dict_data = {
81
+ "type": bin_type,
82
+ "collectionDate": datetime.strptime(
83
+ date,
84
+ "%d/%m/%Y",
85
+ ).strftime(date_format),
86
+ }
87
+ bindata["bins"].append(dict_data)
88
+
89
+ bindata["bins"].sort(
90
+ key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
91
+ )
92
+
93
+ return bindata
@@ -30,14 +30,17 @@ class CouncilClass(AbstractGetBinDataClass):
30
30
  # Find each <time> element in the schedule
31
31
  for entry in soup.find_all("time"):
32
32
 
33
- dict_data = {
34
- "type": entry.find(class_="ScheduleItem").text.strip(),
35
- "collectionDate": datetime.strptime(
36
- entry["datetime"],
37
- "%Y-%m-%d",
38
- ).strftime("%d/%m/%Y"),
39
- }
40
- bindata["bins"].append(dict_data)
33
+ ScheduleItems = entry.find_all(class_="ScheduleItem")
34
+
35
+ for ScheduleItem in ScheduleItems:
36
+ dict_data = {
37
+ "type": ScheduleItem.text.strip(),
38
+ "collectionDate": datetime.strptime(
39
+ entry["datetime"],
40
+ "%Y-%m-%d",
41
+ ).strftime("%d/%m/%Y"),
42
+ }
43
+ bindata["bins"].append(dict_data)
41
44
 
42
45
  bindata["bins"].sort(
43
46
  key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
@@ -1,4 +1,5 @@
1
1
  from bs4 import BeautifulSoup
2
+
2
3
  from uk_bin_collection.uk_bin_collection.common import *
3
4
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
4
5
 
@@ -45,4 +46,17 @@ class CouncilClass(AbstractGetBinDataClass):
45
46
  }
46
47
  data["bins"].append(dict_data)
47
48
 
49
+ if len(page_text) > 5:
50
+ garden_day = datetime.strptime(
51
+ remove_ordinal_indicator_from_date_string(
52
+ page_text[6].find_next("strong").text
53
+ ),
54
+ "%d %B %Y",
55
+ ).strftime(date_format)
56
+ dict_data = {
57
+ "type": "Garden",
58
+ "collectionDate": garden_day,
59
+ }
60
+ data["bins"].append(dict_data)
61
+
48
62
  return data
@@ -0,0 +1,81 @@
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://service.folkestone-hythe.gov.uk/webapp/myarea/index.php?uprn={user_uprn}"
23
+
24
+ # Make the GET request
25
+ response = requests.get(URI)
26
+
27
+ soup = BeautifulSoup(response.content, "html.parser")
28
+
29
+ soup = soup.find("div", {"id": "bincollections"})
30
+
31
+ # Find the Recycling and Non-Recyclables sections
32
+ bin_schedule = {}
33
+
34
+ # Extract the recycling schedule
35
+ recycling_section = soup.find("span", text=lambda x: x and "Recycling" in x)
36
+ if recycling_section:
37
+ bin_types = recycling_section.text.replace("Recycling: ", "").split(" / ")
38
+ recycling_dates = recycling_section.find_next("ul").find_all("li")
39
+ bin_schedule["Recycling"] = [date.text.strip() for date in recycling_dates]
40
+ for date in recycling_dates:
41
+ for bin_type in bin_types:
42
+ dict_data = {
43
+ "type": bin_type.strip(),
44
+ "collectionDate": datetime.strptime(
45
+ remove_ordinal_indicator_from_date_string(
46
+ date.text.strip()
47
+ ),
48
+ "%A %d %B %Y",
49
+ ).strftime("%d/%m/%Y"),
50
+ }
51
+ bindata["bins"].append(dict_data)
52
+
53
+ # Extract the non-recyclables schedule
54
+ non_recyclables_section = soup.find(
55
+ "span", text=lambda x: x and "Non-Recyclables" in x
56
+ )
57
+ if non_recyclables_section:
58
+ bin_types = non_recyclables_section.text.replace(
59
+ "Non-Recyclables: ", ""
60
+ ).split(" / ")
61
+ non_recyclables_dates = non_recyclables_section.find_next("ul").find_all(
62
+ "li"
63
+ )
64
+ for date in non_recyclables_dates:
65
+ for bin_type in bin_types:
66
+ dict_data = {
67
+ "type": bin_type.strip(),
68
+ "collectionDate": datetime.strptime(
69
+ remove_ordinal_indicator_from_date_string(
70
+ date.text.strip()
71
+ ),
72
+ "%A %d %B %Y",
73
+ ).strftime("%d/%m/%Y"),
74
+ }
75
+ bindata["bins"].append(dict_data)
76
+
77
+ bindata["bins"].sort(
78
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
79
+ )
80
+
81
+ return bindata
@@ -27,6 +27,23 @@ class CouncilClass(AbstractGetBinDataClass):
27
27
  "../Images/Bins/ashBin.gif": "Ash bin",
28
28
  }
29
29
 
30
+ fieldset = soup.find("fieldset")
31
+ ps = fieldset.find_all("p")
32
+ for p in ps:
33
+ collection = p.text.strip().replace("Your next ", "").split(".")[0]
34
+ bin_type = collection.split(" day is")[0]
35
+ collection_date = datetime.strptime(
36
+ remove_ordinal_indicator_from_date_string(collection).split("day is ")[
37
+ 1
38
+ ],
39
+ "%A %d %B %Y",
40
+ )
41
+ dict_data = {
42
+ "type": bin_type,
43
+ "collectionDate": collection_date.strftime(date_format),
44
+ }
45
+ data["bins"].append(dict_data)
46
+
30
47
  # Find the page body with all the calendars
31
48
  body = soup.find("div", {"id": "Application_ctl00"})
32
49
  calendars = body.find_all_next("table", {"title": "Calendar"})
@@ -0,0 +1,85 @@
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_paon = kwargs.get("paon")
20
+ user_postcode = kwargs.get("postcode")
21
+ check_postcode(user_postcode)
22
+ check_paon(user_paon)
23
+ bindata = {"bins": []}
24
+
25
+ URI = "https://waste-api-hackney-live.ieg4.net/f806d91c-e133-43a6-ba9a-c0ae4f4cccf6/property/opensearch"
26
+
27
+ data = {
28
+ "Postcode": user_postcode,
29
+ }
30
+ headers = {"Content-Type": "application/json"}
31
+
32
+ # Make the GET request
33
+ response = requests.post(URI, json=data, headers=headers)
34
+
35
+ addresses = response.json()
36
+
37
+ for address in addresses["addressSummaries"]:
38
+ summary = address["summary"]
39
+ if user_paon in summary:
40
+ systemId = address["systemId"]
41
+ if systemId:
42
+ URI = f"https://waste-api-hackney-live.ieg4.net/f806d91c-e133-43a6-ba9a-c0ae4f4cccf6/alloywastepages/getproperty/{systemId}"
43
+
44
+ response = requests.get(URI)
45
+
46
+ address = response.json()
47
+
48
+ binIDs = address["providerSpecificFields"][
49
+ "attributes_wasteContainersAssignableWasteContainers"
50
+ ]
51
+ for binID in binIDs.split(","):
52
+ URI = f"https://waste-api-hackney-live.ieg4.net/f806d91c-e133-43a6-ba9a-c0ae4f4cccf6/alloywastepages/getbin/{binID}"
53
+ response = requests.get(URI)
54
+ getBin = response.json()
55
+
56
+ bin_type = getBin["subTitle"]
57
+
58
+ URI = f"https://waste-api-hackney-live.ieg4.net/f806d91c-e133-43a6-ba9a-c0ae4f4cccf6/alloywastepages/getcollection/{binID}"
59
+ response = requests.get(URI)
60
+ getcollection = response.json()
61
+
62
+ collectionID = getcollection["scheduleCodeWorkflowID"]
63
+
64
+ URI = f"https://waste-api-hackney-live.ieg4.net/f806d91c-e133-43a6-ba9a-c0ae4f4cccf6/alloywastepages/getworkflow/{collectionID}"
65
+ response = requests.get(URI)
66
+ collection_dates = response.json()
67
+
68
+ dates = collection_dates["trigger"]["dates"]
69
+
70
+ for date in dates:
71
+ parsed_datetime = datetime.strptime(
72
+ date, "%Y-%m-%dT%H:%M:%SZ"
73
+ ).strftime(date_format)
74
+
75
+ dict_data = {
76
+ "type": bin_type.strip(),
77
+ "collectionDate": parsed_datetime,
78
+ }
79
+ bindata["bins"].append(dict_data)
80
+
81
+ bindata["bins"].sort(
82
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
83
+ )
84
+
85
+ return bindata
@@ -0,0 +1,83 @@
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
+ bindata = {"bins": []}
23
+
24
+ SESSION_URL = "https://online.hartlepool.gov.uk/authapi/isauthenticated?uri=https%253A%252F%252Fonline.hartlepool.gov.uk%252Fservice%252FRefuse_and_recycling___check_bin_day&hostname=online.hartlepool.gov.uk&withCredentials=true"
25
+
26
+ API_URL = "https://online.hartlepool.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://online.hartlepool.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": "5ec67e019ffdd",
42
+ "repeat_against": "",
43
+ "noRetry": "true",
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
+
52
+ data = {
53
+ "formValues": {
54
+ "Section 1": {
55
+ "collectionLocationUPRN": {
56
+ "value": user_uprn,
57
+ },
58
+ },
59
+ },
60
+ }
61
+
62
+ r = s.post(API_URL, json=data, headers=headers, params=params)
63
+ r.raise_for_status()
64
+
65
+ data = r.json()
66
+ rows_data = data["integration"]["transformed"]["rows_data"]["0"]
67
+ if not isinstance(rows_data, dict):
68
+ raise ValueError("Invalid data returned from API")
69
+
70
+ soup = BeautifulSoup(rows_data["HTMLCollectionDatesText"], "html.parser")
71
+
72
+ # Find all div elements containing the bin schedule
73
+ for div in soup.find_all("div"):
74
+ # Extract bin type and date from the span tag
75
+ text = div.find("span").text.strip()
76
+ bin_type, date = text.split(" ", 1)
77
+ dict_data = {
78
+ "type": bin_type,
79
+ "collectionDate": date,
80
+ }
81
+ bindata["bins"].append(dict_data)
82
+
83
+ return bindata
@@ -83,11 +83,16 @@ class CouncilClass(AbstractGetBinDataClass):
83
83
  )
84
84
 
85
85
  # Select address from dropdown and wait
86
- inputElement_ad = Select(
87
- driver.find_element(By.ID, "FINDBINDAYSHIGHPEAK_ADDRESSSELECT_ADDRESS")
88
- )
89
-
90
- inputElement_ad.select_by_visible_text(user_paon)
86
+ WebDriverWait(driver, 10).until(
87
+ EC.element_to_be_clickable(
88
+ (
89
+ By.XPATH,
90
+ "//select[@id='FINDBINDAYSHIGHPEAK_ADDRESSSELECT_ADDRESS']//option[contains(., '"
91
+ + user_paon
92
+ + "')]",
93
+ )
94
+ )
95
+ ).click()
91
96
 
92
97
  WebDriverWait(driver, 10).until(
93
98
  EC.presence_of_element_located(
@@ -0,0 +1,71 @@
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.hinckley-bosworth.gov.uk/set-location?id={user_uprn}&redirect=refuse&rememberloc="
23
+
24
+ # Make the GET request
25
+ response = requests.get(URI)
26
+
27
+ # Parse the HTML
28
+ soup = BeautifulSoup(response.content, "html.parser")
29
+
30
+ # Find all the bin collection date containers
31
+ bin_schedule = []
32
+ collection_divs = soup.find_all(
33
+ "div", class_=["first_date_bins", "last_date_bins"]
34
+ )
35
+
36
+ for div in collection_divs:
37
+ # Extract the date
38
+ date = div.find("h3", class_="collectiondate").text.strip().replace(":", "")
39
+
40
+ # Extract bin types
41
+ bins = [img["alt"] for img in div.find_all("img", class_="collection")]
42
+
43
+ # Append to the schedule
44
+ bin_schedule.append({"date": date, "bins": bins})
45
+
46
+ current_year = datetime.now().year
47
+ current_month = datetime.now().month
48
+
49
+ # Print the schedule
50
+ for entry in bin_schedule:
51
+ bin_types = entry["bins"]
52
+ date = datetime.strptime(entry["date"], "%d %B")
53
+
54
+ if (current_month > 9) and (date.month < 4):
55
+ date = date.replace(year=(current_year + 1))
56
+ else:
57
+ date = date.replace(year=current_year)
58
+
59
+ for bin_type in bin_types:
60
+
61
+ dict_data = {
62
+ "type": bin_type,
63
+ "collectionDate": date.strftime("%d/%m/%Y"),
64
+ }
65
+ bindata["bins"].append(dict_data)
66
+
67
+ bindata["bins"].sort(
68
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
69
+ )
70
+
71
+ return bindata
@@ -0,0 +1,59 @@
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
+ user_uprn = user_uprn.zfill(12)
21
+ bindata = {"bins": []}
22
+
23
+ URI = "https://www.west-norfolk.gov.uk/info/20174/bins_and_recycling_collection_dates"
24
+
25
+ headers = {"Cookie": f"bcklwn_uprn={user_uprn}"}
26
+
27
+ # Make the GET request
28
+ response = requests.get(URI, headers=headers)
29
+
30
+ soup = BeautifulSoup(response.content, features="html.parser")
31
+ soup.prettify()
32
+
33
+ # Find all bin_date_container divs
34
+ bin_date_containers = soup.find_all("div", class_="bin_date_container")
35
+
36
+ # Loop through each bin_date_container
37
+ for container in bin_date_containers:
38
+ # Extract the collection date
39
+ date = (
40
+ container.find("h3", class_="collectiondate").text.strip().rstrip(":")
41
+ )
42
+
43
+ # Extract the bin type from the alt attribute of the img tag
44
+ bin_type = container.find("img")["alt"]
45
+
46
+ dict_data = {
47
+ "type": bin_type,
48
+ "collectionDate": datetime.strptime(
49
+ date,
50
+ "%A %d %B %Y",
51
+ ).strftime("%d/%m/%Y"),
52
+ }
53
+ bindata["bins"].append(dict_data)
54
+
55
+ bindata["bins"].sort(
56
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
57
+ )
58
+
59
+ return bindata
@@ -0,0 +1,75 @@
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
+ bindata = {"bins": []}
23
+
24
+ URI = "https://lbhapiprod.azure-api.net"
25
+ endpoint = f"{URI}/whitespace/GetCollectionByUprnAndDate"
26
+ subscription_key = "2ea6a75f9ea34bb58d299a0c9f84e72e"
27
+
28
+ # Get today's date in 'YYYY-MM-DD' format
29
+ collection_date = datetime.now().strftime("%Y-%m-%d")
30
+
31
+ # Define the request headers
32
+ headers = {
33
+ "Content-Type": "application/json",
34
+ "Ocp-Apim-Subscription-Key": subscription_key,
35
+ }
36
+
37
+ # Define the request body
38
+ data = {
39
+ "getCollectionByUprnAndDate": {
40
+ "getCollectionByUprnAndDateInput": {
41
+ "uprn": user_uprn,
42
+ "nextCollectionFromDate": collection_date,
43
+ }
44
+ }
45
+ }
46
+ # Make the POST request
47
+ response = requests.post(endpoint, headers=headers, data=json.dumps(data))
48
+ response.raise_for_status() # Raise an exception for HTTP errors
49
+
50
+ # Parse the JSON response
51
+ response_data = response.json()
52
+
53
+ collections = (
54
+ response_data.get("getCollectionByUprnAndDateResponse", {})
55
+ .get("getCollectionByUprnAndDateResult", {})
56
+ .get("Collections", [])
57
+ )
58
+
59
+ for collection in collections:
60
+ bin_type = collection["service"]
61
+ collection_date = collection["date"]
62
+
63
+ dict_data = {
64
+ "type": bin_type,
65
+ "collectionDate": datetime.strptime(
66
+ collection_date,
67
+ "%d/%m/%Y %H:%M:%S",
68
+ ).strftime(date_format),
69
+ }
70
+ bindata["bins"].append(dict_data)
71
+ bindata["bins"].sort(
72
+ key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
73
+ )
74
+
75
+ return bindata