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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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