uk_bin_collection 0.110.0__py3-none-any.whl → 0.112.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,7 +4,6 @@ import pytest
4
4
  from uk_bin_collection.collect_data import UKBinCollectionApp, import_council_module
5
5
 
6
6
 
7
-
8
7
  # Test UKBinCollectionApp setup_arg_parser
9
8
  def test_setup_arg_parser():
10
9
  app = UKBinCollectionApp()
@@ -430,8 +430,10 @@ def test_string_with_whitespace_and_numbers():
430
430
  )
431
431
  def test_get_next_day_of_week(today_str, day_name, expected):
432
432
  mock_today = datetime.strptime(today_str, "%Y-%m-%d")
433
- with patch('uk_bin_collection.common.datetime') as mock_datetime: # replace 'your_module' with the actual module name
433
+ with patch(
434
+ "uk_bin_collection.common.datetime"
435
+ ) as mock_datetime: # replace 'your_module' with the actual module name
434
436
  mock_datetime.now.return_value = mock_today
435
437
  mock_datetime.side_effect = lambda *args, **kw: datetime(*args, **kw)
436
438
  result = get_next_day_of_week(day_name, date_format="%m/%d/%Y")
437
- assert result == expected
439
+ assert result == expected
@@ -2,26 +2,37 @@ import pytest
2
2
 
3
3
  # Test the command-line options
4
4
 
5
+
5
6
  def test_headless_mode(pytestconfig):
6
7
  # Simulate pytest command-line option
7
8
  headless_mode_value = pytestconfig.getoption("--headless")
8
9
  assert headless_mode_value == "True" # This should match the default value
9
10
 
11
+
10
12
  def test_local_browser(pytestconfig):
11
13
  local_browser_value = pytestconfig.getoption("--local_browser")
12
14
  assert local_browser_value == "False" # This should match the default value
13
15
 
16
+
14
17
  def test_selenium_url(pytestconfig):
15
18
  selenium_url_value = pytestconfig.getoption("--selenium_url")
16
- assert selenium_url_value == "http://localhost:4444" # This should match the default value
19
+ assert (
20
+ selenium_url_value == "http://localhost:4444"
21
+ ) # This should match the default value
22
+
17
23
 
18
24
  # Test the fixtures
19
25
 
26
+
20
27
  def test_headless_mode_fixture(headless_mode):
21
28
  assert headless_mode == "True" # This should match the default value
22
29
 
30
+
23
31
  def test_local_browser_fixture(local_browser):
24
32
  assert local_browser == "False" # This should match the default value
25
33
 
34
+
26
35
  def test_selenium_url_fixture(selenium_url):
27
- assert selenium_url == "http://localhost:4444" # This should match the default value
36
+ assert (
37
+ selenium_url == "http://localhost:4444"
38
+ ) # This should match the default value
@@ -105,8 +105,10 @@ def test_output_json():
105
105
  assert type(output) == str
106
106
  assert output == '{\n "bin": ""\n}'
107
107
 
108
+
108
109
  class ConcreteGetBinDataClass(agbdc):
109
110
  """Concrete implementation of the abstract class to test abstract methods."""
111
+
110
112
  def parse_data(self, page: str, **kwargs) -> dict:
111
113
  return {"mock_key": "mock_value"}
112
114
 
@@ -114,33 +116,46 @@ class ConcreteGetBinDataClass(agbdc):
114
116
  # You can implement the method or delegate it to the abstract class's method
115
117
  super().update_dev_mode_data(council_module_str, this_url, **kwargs)
116
118
 
119
+
117
120
  @pytest.fixture
118
121
  def concrete_class_instance():
119
122
  return ConcreteGetBinDataClass()
120
123
 
124
+
121
125
  def test_get_and_parse_data_no_skip_get_url(concrete_class_instance):
122
126
  mock_page = "mocked page content"
123
127
  mock_parsed_data = {"mock_key": "mock_value"}
124
128
 
125
- with mock.patch.object(concrete_class_instance, 'get_data', return_value=mock_page) as mock_get_data, \
126
- mock.patch.object(concrete_class_instance, 'parse_data', return_value=mock_parsed_data) as mock_parse_data:
127
-
129
+ with mock.patch.object(
130
+ concrete_class_instance, "get_data", return_value=mock_page
131
+ ) as mock_get_data, mock.patch.object(
132
+ concrete_class_instance, "parse_data", return_value=mock_parsed_data
133
+ ) as mock_parse_data:
134
+
128
135
  result = concrete_class_instance.get_and_parse_data("http://example.com")
129
136
 
130
137
  mock_get_data.assert_called_once_with("http://example.com")
131
138
  mock_parse_data.assert_called_once_with(mock_page, url="http://example.com")
132
139
  assert result == mock_parsed_data
133
140
 
141
+
134
142
  def test_get_and_parse_data_skip_get_url(concrete_class_instance):
135
143
  mock_parsed_data = {"mock_key": "mock_value"}
136
144
 
137
- with mock.patch.object(concrete_class_instance, 'parse_data', return_value=mock_parsed_data) as mock_parse_data:
138
-
139
- result = concrete_class_instance.get_and_parse_data("http://example.com", skip_get_url=True)
145
+ with mock.patch.object(
146
+ concrete_class_instance, "parse_data", return_value=mock_parsed_data
147
+ ) as mock_parse_data:
148
+
149
+ result = concrete_class_instance.get_and_parse_data(
150
+ "http://example.com", skip_get_url=True
151
+ )
140
152
 
141
- mock_parse_data.assert_called_once_with("", url="http://example.com", skip_get_url=True)
153
+ mock_parse_data.assert_called_once_with(
154
+ "", url="http://example.com", skip_get_url=True
155
+ )
142
156
  assert result == mock_parsed_data
143
-
157
+
158
+
144
159
  @pytest.fixture
145
160
  def setup_test_update_dev_mode_data():
146
161
  """Fixture to set up and tear down the environment for test_update_dev_mode_data"""
@@ -148,7 +163,7 @@ def setup_test_update_dev_mode_data():
148
163
  test_dir = tempfile.TemporaryDirectory()
149
164
 
150
165
  # Patch os.getcwd() to return the temporary directory
151
- cwd_patch = patch('os.getcwd', return_value=test_dir.name)
166
+ cwd_patch = patch("os.getcwd", return_value=test_dir.name)
152
167
  mock_getcwd = cwd_patch.start()
153
168
 
154
169
  # Ensure the nested directory structure exists
@@ -171,15 +186,15 @@ def test_update_dev_mode_data(setup_test_update_dev_mode_data):
171
186
  obj = ConcreteGetBinDataClass()
172
187
 
173
188
  # Define input arguments for the method
174
- council_module_str = 'test_council_module'
175
- this_url = 'https://example.com'
189
+ council_module_str = "test_council_module"
190
+ this_url = "https://example.com"
176
191
  kwargs = {
177
- 'postcode': '12345',
178
- 'paon': '1A',
179
- 'uprn': '100012345',
180
- 'usrn': '200012345',
181
- 'web_driver': 'mocked_web_driver',
182
- 'skip_get_url': True,
192
+ "postcode": "12345",
193
+ "paon": "1A",
194
+ "uprn": "100012345",
195
+ "usrn": "200012345",
196
+ "web_driver": "mocked_web_driver",
197
+ "skip_get_url": True,
183
198
  }
184
199
 
185
200
  # Call the method being tested on the instance
@@ -190,8 +205,8 @@ def test_update_dev_mode_data(setup_test_update_dev_mode_data):
190
205
  assert os.path.exists(input_file_path)
191
206
 
192
207
  # Read the contents of the file and make necessary assertions
193
- with open(input_file_path, 'r') as f:
208
+ with open(input_file_path, "r") as f:
194
209
  file_content = f.read()
195
210
 
196
211
  # Example assertion - check if certain values exist in the file content (based on your actual file format)
197
- assert '100012345' in file_content # Checking UPRN as an example
212
+ assert "100012345" in file_content # Checking UPRN as an example
@@ -106,6 +106,7 @@ def get_date_with_ordinal(date_number: int) -> str:
106
106
  else {1: "st", 2: "nd", 3: "rd"}.get(date_number % 10, "th")
107
107
  )
108
108
 
109
+
109
110
  def has_numbers(inputString: str) -> bool:
110
111
  """
111
112
 
@@ -27,13 +27,14 @@ def parse_bin_text(bin_type_str: str, bin_date_str: str) -> List[Dict[str, str]]
27
27
  bin_date = datetime.strptime(bin_date_str, "%A, %B %d, %Y")
28
28
 
29
29
  for bin_type in bin_type_str.split(", "):
30
- bins.append({
31
- "type": bin_type.strip() + " bin",
32
- "collectionDate": bin_date.strftime(date_format)
33
- })
30
+ bins.append(
31
+ {
32
+ "type": bin_type.strip() + " bin",
33
+ "collectionDate": bin_date.strftime(date_format),
34
+ }
35
+ )
34
36
 
35
37
  return bins
36
-
37
38
 
38
39
  class CouncilClass(AbstractGetBinDataClass):
39
40
  """
@@ -1,7 +1,10 @@
1
1
  import requests
2
2
  import json
3
3
  from datetime import datetime
4
- from uk_bin_collection.uk_bin_collection.common import check_uprn, date_format as DATE_FORMAT
4
+ from uk_bin_collection.uk_bin_collection.common import (
5
+ check_uprn,
6
+ date_format as DATE_FORMAT,
7
+ )
5
8
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
6
9
 
7
10
 
@@ -11,15 +14,15 @@ class CouncilClass(AbstractGetBinDataClass):
11
14
  """
12
15
 
13
16
  def parse_data(self, page: str, **kwargs) -> dict:
14
- url_base = "https://basildonportal.azurewebsites.net/api/getPropertyRefuseInformation"
17
+ url_base = (
18
+ "https://basildonportal.azurewebsites.net/api/getPropertyRefuseInformation"
19
+ )
15
20
 
16
21
  uprn = kwargs.get("uprn")
17
22
  # Check the UPRN is valid
18
23
  check_uprn(uprn)
19
24
 
20
- payload = {
21
- "uprn": uprn
22
- }
25
+ payload = {"uprn": uprn}
23
26
 
24
27
  headers = {"Content-Type": "application/json"}
25
28
 
@@ -36,7 +39,9 @@ class CouncilClass(AbstractGetBinDataClass):
36
39
  bins.append(
37
40
  {
38
41
  "type": service_name,
39
- "collectionDate": collection_data.get("current_collection_date"),
42
+ "collectionDate": collection_data.get(
43
+ "current_collection_date"
44
+ ),
40
45
  }
41
46
  )
42
47
 
@@ -15,6 +15,7 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
15
15
 
16
16
  _LOGGER = logging.getLogger(__name__)
17
17
 
18
+
18
19
  class CouncilClass(AbstractGetBinDataClass):
19
20
  """
20
21
  Implementation for Chesterfield Borough Council waste collection data retrieval.
@@ -56,7 +57,9 @@ class CouncilClass(AbstractGetBinDataClass):
56
57
  session.get(API_URLS["session"], headers=HEADERS, verify=False)
57
58
 
58
59
  # Step 2: Get fwuid
59
- fwuid_response = session.get(API_URLS["fwuid"], headers=HEADERS, verify=False)
60
+ fwuid_response = session.get(
61
+ API_URLS["fwuid"], headers=HEADERS, verify=False
62
+ )
60
63
  fwuid_data = fwuid_response.json()
61
64
  fwuid = fwuid_data.get("auraConfig", {}).get("context", {}).get("fwuid")
62
65
 
@@ -66,50 +69,58 @@ class CouncilClass(AbstractGetBinDataClass):
66
69
 
67
70
  # Step 3: Prepare payload for UPRN search
68
71
  payload = {
69
- "message": json.dumps({
70
- "actions": [{
71
- "id": "4;a",
72
- "descriptor": "aura://ApexActionController/ACTION$execute",
73
- "callingDescriptor": "UNKNOWN",
74
- "params": {
75
- "namespace": "",
76
- "classname": "CBC_VE_CollectionDays",
77
- "method": "getServicesByUPRN",
78
- "params": {
79
- "propertyUprn": user_uprn,
80
- "executedFrom": "Main Website"
81
- },
82
- "cacheable": False,
83
- "isContinuation": False
84
- }
85
- }]
86
- }),
87
- "aura.context": json.dumps({
88
- "mode": "PROD",
89
- "fwuid": fwuid,
90
- "app": "c:cbc_VE_CollectionDaysLO",
91
- "loaded": {
92
- "APPLICATION@markup://c:cbc_VE_CollectionDaysLO": "pqeNg7kPWCbx1pO8sIjdLA"
93
- },
94
- "dn": [],
95
- "globals": {},
96
- "uad": True
97
- }),
72
+ "message": json.dumps(
73
+ {
74
+ "actions": [
75
+ {
76
+ "id": "4;a",
77
+ "descriptor": "aura://ApexActionController/ACTION$execute",
78
+ "callingDescriptor": "UNKNOWN",
79
+ "params": {
80
+ "namespace": "",
81
+ "classname": "CBC_VE_CollectionDays",
82
+ "method": "getServicesByUPRN",
83
+ "params": {
84
+ "propertyUprn": user_uprn,
85
+ "executedFrom": "Main Website",
86
+ },
87
+ "cacheable": False,
88
+ "isContinuation": False,
89
+ },
90
+ }
91
+ ]
92
+ }
93
+ ),
94
+ "aura.context": json.dumps(
95
+ {
96
+ "mode": "PROD",
97
+ "fwuid": fwuid,
98
+ "app": "c:cbc_VE_CollectionDaysLO",
99
+ "loaded": {
100
+ "APPLICATION@markup://c:cbc_VE_CollectionDaysLO": "pqeNg7kPWCbx1pO8sIjdLA"
101
+ },
102
+ "dn": [],
103
+ "globals": {},
104
+ "uad": True,
105
+ }
106
+ ),
98
107
  "aura.pageURI": "/bins-and-recycling/bin-collections/check-bin-collections.aspx",
99
108
  "aura.token": "null",
100
109
  }
101
110
 
102
111
  # Step 4: Make POST request to fetch collection data
103
112
  search_response = session.post(
104
- API_URLS["search"],
105
- data=payload,
106
- headers=HEADERS,
107
- verify=False
113
+ API_URLS["search"], data=payload, headers=HEADERS, verify=False
108
114
  )
109
115
  search_data = search_response.json()
110
116
 
111
117
  # Step 5: Extract service units
112
- service_units = search_data.get("actions", [])[0].get("returnValue", {}).get("returnValue", {}).get("serviceUnits", [])
118
+ service_units = (
119
+ search_data.get("actions", [])[0]
120
+ .get("returnValue", {})
121
+ .get("returnValue", {})
122
+ .get("serviceUnits", [])
123
+ )
113
124
 
114
125
  if not service_units:
115
126
  _LOGGER.warning("No service units found for the given UPRN.")
@@ -141,7 +152,9 @@ class CouncilClass(AbstractGetBinDataClass):
141
152
 
142
153
  # Extract the next scheduled date
143
154
  try:
144
- dt_zulu = item["serviceTasks"][0]["serviceTaskSchedules"][0]["nextInstance"]["currentScheduledDate"]
155
+ dt_zulu = item["serviceTasks"][0]["serviceTaskSchedules"][0][
156
+ "nextInstance"
157
+ ]["currentScheduledDate"]
145
158
  dt_utc = datetime.strptime(dt_zulu, "%Y-%m-%dT%H:%M:%S.%f%z")
146
159
  dt_local = dt_utc.astimezone(None)
147
160
  collection_date = dt_local.date()
@@ -38,7 +38,7 @@ class CouncilClass(AbstractGetBinDataClass):
38
38
  for icon in icons:
39
39
  cal_item = icon.find_parent().find_parent()
40
40
  bin_date = datetime.strptime(
41
- cal_item["title"],
41
+ cal_item["title"].replace("today is ", ""),
42
42
  "%A, %d %B %Y",
43
43
  )
44
44
 
@@ -0,0 +1,75 @@
1
+ from time import sleep
2
+
3
+ import requests
4
+ from bs4 import BeautifulSoup
5
+
6
+ from uk_bin_collection.uk_bin_collection.common import *
7
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
8
+
9
+
10
+ # import the wonderful Beautiful Soup and the URL grabber
11
+ class CouncilClass(AbstractGetBinDataClass):
12
+ """
13
+ Concrete classes have to implement all abstract operations of the
14
+ base class. They can also override some operations with a default
15
+ implementation.
16
+ """
17
+
18
+ def parse_data(self, page: str, **kwargs) -> dict:
19
+
20
+ user_uprn = kwargs.get("uprn")
21
+ # check_uprn(user_uprn)
22
+ bindata = {"bins": []}
23
+
24
+ URI = f"https://waste-services.sutton.gov.uk/waste/{user_uprn}"
25
+
26
+ s = requests.Session()
27
+
28
+ r = s.get(URI)
29
+ while "Loading your bin days..." in r.text:
30
+ sleep(2)
31
+ r = s.get(URI)
32
+ r.raise_for_status()
33
+
34
+ soup = BeautifulSoup(r.content, "html.parser")
35
+
36
+ current_year = datetime.now().year
37
+ next_year = current_year + 1
38
+
39
+ services = soup.find_all("h3", class_="govuk-heading-m waste-service-name")
40
+
41
+ for service in services:
42
+ bin_type = service.get_text(
43
+ strip=True
44
+ ) # Bin type name (e.g., 'Food waste', 'Mixed recycling')
45
+ if bin_type == "Bulky waste":
46
+ continue
47
+ service_details = service.find_next("div", class_="govuk-grid-row")
48
+
49
+ next_collection = (
50
+ service_details.find("dt", string="Next collection")
51
+ .find_next_sibling("dd")
52
+ .get_text(strip=True)
53
+ )
54
+
55
+ next_collection = datetime.strptime(
56
+ remove_ordinal_indicator_from_date_string(next_collection),
57
+ "%A, %d %B",
58
+ )
59
+
60
+ if next_collection.month == 1:
61
+ next_collection = next_collection.replace(year=next_year)
62
+ else:
63
+ next_collection = next_collection.replace(year=current_year)
64
+
65
+ dict_data = {
66
+ "type": bin_type,
67
+ "collectionDate": next_collection.strftime("%d/%m/%Y"),
68
+ }
69
+ bindata["bins"].append(dict_data)
70
+
71
+ bindata["bins"].sort(
72
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
73
+ )
74
+
75
+ return bindata
@@ -0,0 +1,93 @@
1
+ import time
2
+
3
+ import requests
4
+
5
+ from uk_bin_collection.uk_bin_collection.common import *
6
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
7
+
8
+
9
+ # import the wonderful Beautiful Soup and the URL grabber
10
+ class CouncilClass(AbstractGetBinDataClass):
11
+ """
12
+ Concrete classes have to implement all abstract operations of the
13
+ base class. They can also override some operations with a default
14
+ implementation.
15
+ """
16
+
17
+ def parse_data(self, page: str, **kwargs) -> dict:
18
+
19
+ user_uprn = kwargs.get("uprn")
20
+ check_uprn(user_uprn)
21
+ bindata = {"bins": []}
22
+
23
+ SESSION_URL = "https://my.middevon.gov.uk/authapi/isauthenticated?uri=https%253A%252F%252Fmy.middevon.gov.uk%252Fen%252FAchieveForms%252F%253Fform_uri%253Dsandbox-publish%253A%252F%252FAF-Process-2289dd06-9a12-4202-ba09-857fe756f6bd%252FAF-Stage-eb382015-001c-415d-beda-84f796dbb167%252Fdefinition.json%2526redirectlink%253D%25252Fen%2526cancelRedirectLink%253D%25252Fen%2526consentMessage%253Dyes&hostname=my.middevon.gov.uk&withCredentials=true"
24
+
25
+ API_URL = "https://my.middevon.gov.uk/apibroker/runLookup"
26
+
27
+ payload = {
28
+ "formValues": {
29
+ "Your Address": {
30
+ "listAddress": {"value": user_uprn},
31
+ },
32
+ },
33
+ }
34
+
35
+ headers = {
36
+ "Content-Type": "application/json",
37
+ "Accept": "application/json",
38
+ "User-Agent": "Mozilla/5.0",
39
+ "X-Requested-With": "XMLHttpRequest",
40
+ "Referer": "https://my.middevon.gov.uk/fillform/?iframe_id=fillform-frame-1&db_id=",
41
+ }
42
+
43
+ ids = [
44
+ "6423144f50ec0",
45
+ "641c7ae9b4c96",
46
+ "645e13a01dba1",
47
+ "642315aacb919",
48
+ "64231699483cf",
49
+ "642421bab7478",
50
+ "6424229605d13",
51
+ "645e14020c9cc",
52
+ ]
53
+
54
+ rows_data = []
55
+
56
+ for id in ids:
57
+ s = requests.session()
58
+ r = s.get(SESSION_URL)
59
+ r.raise_for_status()
60
+ session_data = r.json()
61
+ sid = session_data["auth-session"]
62
+
63
+ params = {
64
+ "id": id,
65
+ "repeat_against": "",
66
+ "noRetry": "false",
67
+ "getOnlyTokens": "undefined",
68
+ "log_id": "",
69
+ "app_name": "AF-Renderer::Self",
70
+ # unix_timestamp
71
+ "_": str(int(time.time() * 1000)),
72
+ "sid": sid,
73
+ }
74
+ r = s.post(API_URL, json=payload, headers=headers, params=params)
75
+ r.raise_for_status()
76
+ data = r.json()
77
+ rows_data = data["integration"]["transformed"]["rows_data"]
78
+ if isinstance(rows_data, dict):
79
+ date = datetime.strptime(rows_data["0"]["display"], "%d-%b-%y")
80
+ bin_types = (rows_data["0"]["CollectionItems"]).split(" and ")
81
+
82
+ for bin_type in bin_types:
83
+ dict_data = {
84
+ "type": bin_type,
85
+ "collectionDate": date.strftime(date_format),
86
+ }
87
+ bindata["bins"].append(dict_data)
88
+
89
+ bindata["bins"].sort(
90
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
91
+ )
92
+
93
+ return bindata
@@ -0,0 +1,63 @@
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
+ user_postcode = kwargs.get("postcode")
20
+ check_uprn(user_uprn)
21
+ check_postcode(user_postcode)
22
+ bindata = {"bins": []}
23
+
24
+ session_uri = "https://www.oxford.gov.uk/mybinday"
25
+ URI = "https://www.oxford.gov.uk/xfp/form/142"
26
+
27
+ session = requests.Session()
28
+ token_response = session.get(session_uri)
29
+ soup = BeautifulSoup(token_response.text, "html.parser")
30
+ token = soup.find("input", {"name": "__token"}).attrs["value"]
31
+
32
+ form_data = {
33
+ "__token": token,
34
+ "page": "12",
35
+ "locale": "en_GB",
36
+ "q6ad4e3bf432c83230a0347a6eea6c805c672efeb_0_0": user_postcode,
37
+ "q6ad4e3bf432c83230a0347a6eea6c805c672efeb_1_0": user_uprn,
38
+ "next": "Next",
39
+ }
40
+
41
+ collection_response = session.post(URI, data=form_data)
42
+
43
+ collection_soup = BeautifulSoup(collection_response.text, "html.parser")
44
+ for paragraph in collection_soup.find("div", class_="editor").find_all("p"):
45
+ matches = re.match(r"^(\w+) Next Collection: (.*)", paragraph.text)
46
+ if matches:
47
+ collection_type, date_string = matches.groups()
48
+ try:
49
+ date = datetime.strptime(date_string, "%A %d %B %Y").date()
50
+ except ValueError:
51
+ date = datetime.strptime(date_string, "%A %d %b %Y").date()
52
+
53
+ dict_data = {
54
+ "type": collection_type,
55
+ "collectionDate": date.strftime("%d/%m/%Y"),
56
+ }
57
+ bindata["bins"].append(dict_data)
58
+
59
+ bindata["bins"].sort(
60
+ key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
61
+ )
62
+
63
+ return bindata
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uk_bin_collection
3
- Version: 0.110.0
3
+ Version: 0.112.0
4
4
  Summary: Python Lib to collect UK Bin Data
5
5
  Author: Robert Bradley
6
6
  Author-email: robbrad182@gmail.com