uk_bin_collection 0.79.1__py3-none-any.whl → 0.81.0__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.
- uk_bin_collection/tests/input.json +47 -1
 - uk_bin_collection/uk_bin_collection/common.py +23 -1
 - uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py +2 -1
 - uk_bin_collection/uk_bin_collection/councils/BirminghamCityCouncil.py +27 -13
 - uk_bin_collection/uk_bin_collection/councils/BracknellForestCouncil.py +4 -4
 - uk_bin_collection/uk_bin_collection/councils/BristolCityCouncil.py +2 -1
 - uk_bin_collection/uk_bin_collection/councils/BromleyBoroughCouncil.py +6 -15
 - uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py +8 -6
 - uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py +5 -4
 - uk_bin_collection/uk_bin_collection/councils/DartfordBoroughCouncil.py +7 -9
 - uk_bin_collection/uk_bin_collection/councils/EastCambridgeshireCouncil.py +2 -1
 - uk_bin_collection/uk_bin_collection/councils/HaringeyCouncil.py +13 -5
 - uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py +6 -4
 - uk_bin_collection/uk_bin_collection/councils/LisburnCastlereaghCityCouncil.py +1 -0
 - uk_bin_collection/uk_bin_collection/councils/ManchesterCityCouncil.py +2 -1
 - uk_bin_collection/uk_bin_collection/councils/NewForestCouncil.py +146 -0
 - uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py +1 -0
 - uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py +2 -1
 - uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py +1 -0
 - uk_bin_collection/uk_bin_collection/councils/SouthCambridgeshireCouncil.py +1 -0
 - uk_bin_collection/uk_bin_collection/councils/StHelensBC.py +2 -1
 - uk_bin_collection/uk_bin_collection/councils/StaffordBoroughCouncil.py +69 -0
 - uk_bin_collection/uk_bin_collection/councils/SwanseaCouncil.py +70 -0
 - uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py +1 -0
 - uk_bin_collection/uk_bin_collection/councils/ThreeRiversDistrictCouncil.py +140 -0
 - uk_bin_collection/uk_bin_collection/councils/UttlesfordDistrictCouncil.py +117 -0
 - uk_bin_collection/uk_bin_collection/councils/WaverleyBoroughCouncil.py +2 -1
 - uk_bin_collection/uk_bin_collection/councils/WiganBoroughCouncil.py +2 -1
 - uk_bin_collection/uk_bin_collection/councils/WindsorAndMaidenheadCouncil.py +2 -1
 - uk_bin_collection/uk_bin_collection/councils/WokingBoroughCouncil.py +4 -2
 - uk_bin_collection/uk_bin_collection/councils/WychavonDistrictCouncil.py +156 -0
 - {uk_bin_collection-0.79.1.dist-info → uk_bin_collection-0.81.0.dist-info}/METADATA +1 -1
 - {uk_bin_collection-0.79.1.dist-info → uk_bin_collection-0.81.0.dist-info}/RECORD +36 -30
 - {uk_bin_collection-0.79.1.dist-info → uk_bin_collection-0.81.0.dist-info}/LICENSE +0 -0
 - {uk_bin_collection-0.79.1.dist-info → uk_bin_collection-0.81.0.dist-info}/WHEEL +0 -0
 - {uk_bin_collection-0.79.1.dist-info → uk_bin_collection-0.81.0.dist-info}/entry_points.txt +0 -0
 
| 
         @@ -263,9 +263,9 @@ 
     | 
|
| 
       263 
263 
     | 
    
         
             
                    "wiki_name": "Croydon Council"
         
     | 
| 
       264 
264 
     | 
    
         
             
                },
         
     | 
| 
       265 
265 
     | 
    
         
             
                "DartfordBoroughCouncil": {
         
     | 
| 
      
 266 
     | 
    
         
            +
                    "uprn": "010094157511",
         
     | 
| 
       266 
267 
     | 
    
         
             
                    "url": "https://windmz.dartford.gov.uk/ufs/WS_CHECK_COLLECTIONS.eb?UPRN=010094157511",
         
     | 
| 
       267 
268 
     | 
    
         
             
                    "wiki_name": "DartfordBoroughCouncil",
         
     | 
| 
       268 
     | 
    
         
            -
                    "uprn": "010094157511",
         
     | 
| 
       269 
269 
     | 
    
         
             
                    "wiki_note": "Use https://uprn.uk/ to find your UPRN "
         
     | 
| 
       270 
270 
     | 
    
         
             
                },
         
     | 
| 
       271 
271 
     | 
    
         
             
                "DerbyshireDalesDistrictCouncil": {
         
     | 
| 
         @@ -602,6 +602,14 @@ 
     | 
|
| 
       602 
602 
     | 
    
         
             
                    "web_driver": "http://selenium:4444",
         
     | 
| 
       603 
603 
     | 
    
         
             
                    "wiki_name": "Neath Port Talbot Council"
         
     | 
| 
       604 
604 
     | 
    
         
             
                },
         
     | 
| 
      
 605 
     | 
    
         
            +
                "NewForestCouncil": {
         
     | 
| 
      
 606 
     | 
    
         
            +
                    "postcode": "SO41 0GJ",
         
     | 
| 
      
 607 
     | 
    
         
            +
                    "skip_get_url": true,
         
     | 
| 
      
 608 
     | 
    
         
            +
                    "uprn": "100060482345",
         
     | 
| 
      
 609 
     | 
    
         
            +
                    "url": "https://forms.newforest.gov.uk/id/FIND_MY_COLLECTION",
         
     | 
| 
      
 610 
     | 
    
         
            +
                    "web_driver": "http://selenium:4444",
         
     | 
| 
      
 611 
     | 
    
         
            +
                    "wiki_name": "New Forest Council"
         
     | 
| 
      
 612 
     | 
    
         
            +
                },
         
     | 
| 
       605 
613 
     | 
    
         
             
                "NewarkAndSherwoodDC": {
         
     | 
| 
       606 
614 
     | 
    
         
             
                    "url": "http://app.newark-sherwooddc.gov.uk/bincollection/calendar?pid=200004258529",
         
     | 
| 
       607 
615 
     | 
    
         
             
                    "wiki_command_url_override": "http://app.newark-sherwooddc.gov.uk/bincollection/calendar?pid=XXXXXXXX",
         
     | 
| 
         @@ -909,6 +917,12 @@ 
     | 
|
| 
       909 
917 
     | 
    
         
             
                    "url": "https://www.sthelens.gov.uk/",
         
     | 
| 
       910 
918 
     | 
    
         
             
                    "wiki_name": "St Helens Borough Council"
         
     | 
| 
       911 
919 
     | 
    
         
             
                },
         
     | 
| 
      
 920 
     | 
    
         
            +
                "StaffordBoroughCouncil": {
         
     | 
| 
      
 921 
     | 
    
         
            +
                    "uprn": "100032203010",
         
     | 
| 
      
 922 
     | 
    
         
            +
                    "url": "https://www.staffordbc.gov.uk/address/100032203010",
         
     | 
| 
      
 923 
     | 
    
         
            +
                    "wiki_name": "StaffordBoroughCouncil",
         
     | 
| 
      
 924 
     | 
    
         
            +
                    "wiki_note": "The URL needs to be https://www.staffordbc.gov.uk/address/<Your_UPRN>"
         
     | 
| 
      
 925 
     | 
    
         
            +
                },
         
     | 
| 
       912 
926 
     | 
    
         
             
                "StaffordshireMoorlandsDistrictCouncil": {
         
     | 
| 
       913 
927 
     | 
    
         
             
                    "postcode": "ST8 6HN",
         
     | 
| 
       914 
928 
     | 
    
         
             
                    "skip_get_url": true,
         
     | 
| 
         @@ -959,6 +973,13 @@ 
     | 
|
| 
       959 
973 
     | 
    
         
             
                    "url": "https://swale.gov.uk/bins-littering-and-the-environment/bins/collection-days",
         
     | 
| 
       960 
974 
     | 
    
         
             
                    "wiki_name": "Swale Borough Council"
         
     | 
| 
       961 
975 
     | 
    
         
             
                },
         
     | 
| 
      
 976 
     | 
    
         
            +
                "SwanseaCouncil": {
         
     | 
| 
      
 977 
     | 
    
         
            +
                    "postcode": "SA43PQ",
         
     | 
| 
      
 978 
     | 
    
         
            +
                    "skip_get_url": true,
         
     | 
| 
      
 979 
     | 
    
         
            +
                    "uprn": "100100324821",
         
     | 
| 
      
 980 
     | 
    
         
            +
                    "url": "https://www1.swansea.gov.uk/recyclingsearch/",
         
     | 
| 
      
 981 
     | 
    
         
            +
                    "wiki_name": "SwanseaCouncil"
         
     | 
| 
      
 982 
     | 
    
         
            +
                },
         
     | 
| 
       962 
983 
     | 
    
         
             
                "TamesideMBCouncil": {
         
     | 
| 
       963 
984 
     | 
    
         
             
                    "skip_get_url": true,
         
     | 
| 
       964 
985 
     | 
    
         
             
                    "uprn": "100012835362",
         
     | 
| 
         @@ -992,6 +1013,14 @@ 
     | 
|
| 
       992 
1013 
     | 
    
         
             
                    "url": "https://testvalley.gov.uk/wasteandrecycling/when-are-my-bins-collected",
         
     | 
| 
       993 
1014 
     | 
    
         
             
                    "wiki_name": "Test Valley Borough Council"
         
     | 
| 
       994 
1015 
     | 
    
         
             
                },
         
     | 
| 
      
 1016 
     | 
    
         
            +
                "ThreeRiversDistrictCouncil": {
         
     | 
| 
      
 1017 
     | 
    
         
            +
                    "postcode": "WD3 7AZ",
         
     | 
| 
      
 1018 
     | 
    
         
            +
                    "skip_get_url": true,
         
     | 
| 
      
 1019 
     | 
    
         
            +
                    "uprn": "100080913662",
         
     | 
| 
      
 1020 
     | 
    
         
            +
                    "url": "https://my.threerivers.gov.uk/en/AchieveForms/?mode=fill&consentMessage=yes&form_uri=sandbox-publish://AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b/AF-Stage-01ee28aa-1584-442c-8d1f-119b6e27114a/definition.json&process=1&process_uri=sandbox-processes://AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b&process_id=AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b&noLoginPrompt=1",
         
     | 
| 
      
 1021 
     | 
    
         
            +
                    "web_driver": "http://selenium:4444",
         
     | 
| 
      
 1022 
     | 
    
         
            +
                    "wiki_name": "Three Rivers District Council"
         
     | 
| 
      
 1023 
     | 
    
         
            +
                },
         
     | 
| 
       995 
1024 
     | 
    
         
             
                "TonbridgeAndMallingBC": {
         
     | 
| 
       996 
1025 
     | 
    
         
             
                    "postcode": "ME19 4JS",
         
     | 
| 
       997 
1026 
     | 
    
         
             
                    "skip_get_url": true,
         
     | 
| 
         @@ -1011,6 +1040,15 @@ 
     | 
|
| 
       1011 
1040 
     | 
    
         
             
                    "url": "https://collections-torridge.azurewebsites.net/WebService2.asmx",
         
     | 
| 
       1012 
1041 
     | 
    
         
             
                    "wiki_name": "Torridge District Council"
         
     | 
| 
       1013 
1042 
     | 
    
         
             
                },
         
     | 
| 
      
 1043 
     | 
    
         
            +
                "UttlesfordDistrictCouncil": {
         
     | 
| 
      
 1044 
     | 
    
         
            +
                    "house_number": "72, Birchanger Lane",
         
     | 
| 
      
 1045 
     | 
    
         
            +
                    "postcode": "CM23 5QF",
         
     | 
| 
      
 1046 
     | 
    
         
            +
                    "skip_get_url": true,
         
     | 
| 
      
 1047 
     | 
    
         
            +
                    "uprn": "100090643434",
         
     | 
| 
      
 1048 
     | 
    
         
            +
                    "url": "https://bins.uttlesford.gov.uk/",
         
     | 
| 
      
 1049 
     | 
    
         
            +
                    "web_driver": "http://selenium:4444",
         
     | 
| 
      
 1050 
     | 
    
         
            +
                    "wiki_name": "UttlesfordDistrictCouncil"
         
     | 
| 
      
 1051 
     | 
    
         
            +
                },
         
     | 
| 
       1014 
1052 
     | 
    
         
             
                "ValeofGlamorganCouncil": {
         
     | 
| 
       1015 
1053 
     | 
    
         
             
                    "skip_get_url": true,
         
     | 
| 
       1016 
1054 
     | 
    
         
             
                    "uprn": "64029020",
         
     | 
| 
         @@ -1137,6 +1175,14 @@ 
     | 
|
| 
       1137 
1175 
     | 
    
         
             
                    "wiki_name": "Woking Borough Council/Joint Waste Solutions",
         
     | 
| 
       1138 
1176 
     | 
    
         
             
                    "wiki_note": "Works with all collection areas that use Joint Waste Solutions. Just use the correct URL."
         
     | 
| 
       1139 
1177 
     | 
    
         
             
                },
         
     | 
| 
      
 1178 
     | 
    
         
            +
                "WychavonDistrictCouncil": {
         
     | 
| 
      
 1179 
     | 
    
         
            +
                    "url": "https://selfservice.wychavon.gov.uk/wdcroundlookup/wdc_search.jsp",
         
     | 
| 
      
 1180 
     | 
    
         
            +
                    "wiki_name": "WychavonDistrictCouncil",
         
     | 
| 
      
 1181 
     | 
    
         
            +
                    "web_driver": "http://selenium:4444",
         
     | 
| 
      
 1182 
     | 
    
         
            +
                    "skip_get_url": true,
         
     | 
| 
      
 1183 
     | 
    
         
            +
                    "postcode": "WR3 7RU", 
         
     | 
| 
      
 1184 
     | 
    
         
            +
                    "uprn": "100120716273"
         
     | 
| 
      
 1185 
     | 
    
         
            +
                },
         
     | 
| 
       1140 
1186 
     | 
    
         
             
                "WyreCouncil": {
         
     | 
| 
       1141 
1187 
     | 
    
         
             
                    "postcode": "FY6 8HG",
         
     | 
| 
       1142 
1188 
     | 
    
         
             
                    "skip_get_url": true,
         
     | 
| 
         @@ -5,7 +5,7 @@ import os 
     | 
|
| 
       5 
5 
     | 
    
         
             
            import pandas as pd
         
     | 
| 
       6 
6 
     | 
    
         
             
            import re
         
     | 
| 
       7 
7 
     | 
    
         
             
            import requests
         
     | 
| 
       8 
     | 
    
         
            -
            from datetime import datetime
         
     | 
| 
      
 8 
     | 
    
         
            +
            from datetime import datetime, timedelta
         
     | 
| 
       9 
9 
     | 
    
         
             
            from dateutil.parser import parse
         
     | 
| 
       10 
10 
     | 
    
         
             
            from enum import Enum
         
     | 
| 
       11 
11 
     | 
    
         
             
            from selenium import webdriver
         
     | 
| 
         @@ -242,6 +242,28 @@ def save_data(file_path, data): 
     | 
|
| 
       242 
242 
     | 
    
         
             
                    json.dump(data, file, sort_keys=True, indent=4)
         
     | 
| 
       243 
243 
     | 
    
         | 
| 
       244 
244 
     | 
    
         | 
| 
      
 245 
     | 
    
         
            +
            def get_next_day_of_week(day_name):
         
     | 
| 
      
 246 
     | 
    
         
            +
                days_of_week = [
         
     | 
| 
      
 247 
     | 
    
         
            +
                    "Monday",
         
     | 
| 
      
 248 
     | 
    
         
            +
                    "Tuesday",
         
     | 
| 
      
 249 
     | 
    
         
            +
                    "Wednesday",
         
     | 
| 
      
 250 
     | 
    
         
            +
                    "Thursday",
         
     | 
| 
      
 251 
     | 
    
         
            +
                    "Friday",
         
     | 
| 
      
 252 
     | 
    
         
            +
                    "Saturday",
         
     | 
| 
      
 253 
     | 
    
         
            +
                    "Sunday",
         
     | 
| 
      
 254 
     | 
    
         
            +
                ]
         
     | 
| 
      
 255 
     | 
    
         
            +
                today = datetime.now()
         
     | 
| 
      
 256 
     | 
    
         
            +
                today_idx = today.weekday()  # Monday is 0 and Sunday is 6
         
     | 
| 
      
 257 
     | 
    
         
            +
                target_idx = days_of_week.index(day_name)
         
     | 
| 
      
 258 
     | 
    
         
            +
             
     | 
| 
      
 259 
     | 
    
         
            +
                days_until_target = (target_idx - today_idx) % 7
         
     | 
| 
      
 260 
     | 
    
         
            +
                if days_until_target == 0:
         
     | 
| 
      
 261 
     | 
    
         
            +
                    days_until_target = 7  # Ensure it's the next instance of the day, not today if today is that day
         
     | 
| 
      
 262 
     | 
    
         
            +
             
     | 
| 
      
 263 
     | 
    
         
            +
                next_day = today + timedelta(days=days_until_target)
         
     | 
| 
      
 264 
     | 
    
         
            +
                return next_day.strftime(date_format)
         
     | 
| 
      
 265 
     | 
    
         
            +
             
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
       245 
267 
     | 
    
         
             
            def contains_date(string, fuzzy=False) -> bool:
         
     | 
| 
       246 
268 
     | 
    
         
             
                """
         
     | 
| 
       247 
269 
     | 
    
         
             
                Return whether the string can be interpreted as a date.
         
     | 
| 
         @@ -2,6 +2,7 @@ from datetime import datetime 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            import requests
         
     | 
| 
       4 
4 
     | 
    
         
             
            from bs4 import BeautifulSoup
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
       5 
6 
     | 
    
         
             
            from uk_bin_collection.uk_bin_collection.common import *
         
     | 
| 
       6 
7 
     | 
    
         
             
            from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
         
     | 
| 
       7 
8 
     | 
    
         | 
| 
         @@ -22,7 +23,7 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       22 
23 
     | 
    
         | 
| 
       23 
24 
     | 
    
         
             
                    # Start a new session to walk through the form
         
     | 
| 
       24 
25 
     | 
    
         
             
                    requests.packages.urllib3.disable_warnings()
         
     | 
| 
       25 
     | 
    
         
            -
                    s = requests. 
     | 
| 
      
 26 
     | 
    
         
            +
                    s = requests.Session()
         
     | 
| 
       26 
27 
     | 
    
         | 
| 
       27 
28 
     | 
    
         
             
                    headers = {
         
     | 
| 
       28 
29 
     | 
    
         
             
                        "Origin": "https://www.centralbedfordshire.gov.uk",
         
     | 
| 
         @@ -1,6 +1,14 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            from  
     | 
| 
      
 1 
     | 
    
         
            +
            from typing import Dict, List, Any, Optional
         
     | 
| 
       2 
2 
     | 
    
         
             
            from bs4 import BeautifulSoup
         
     | 
| 
       3 
     | 
    
         
            -
            from  
     | 
| 
      
 3 
     | 
    
         
            +
            from dateutil.relativedelta import relativedelta
         
     | 
| 
      
 4 
     | 
    
         
            +
            import requests
         
     | 
| 
      
 5 
     | 
    
         
            +
            import logging
         
     | 
| 
      
 6 
     | 
    
         
            +
            import re
         
     | 
| 
      
 7 
     | 
    
         
            +
            from datetime import datetime
         
     | 
| 
      
 8 
     | 
    
         
            +
            from uk_bin_collection.common import *
         
     | 
| 
      
 9 
     | 
    
         
            +
            from dateutil.parser import parse
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            from uk_bin_collection.uk_bin_collection.common import check_uprn, check_postcode
         
     | 
| 
       4 
12 
     | 
    
         
             
            from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
         
     | 
| 
       5 
13 
     | 
    
         | 
| 
       6 
14 
     | 
    
         | 
| 
         @@ -23,7 +31,7 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       23 
31 
     | 
    
         
             
                implementation.
         
     | 
| 
       24 
32 
     | 
    
         
             
                """
         
     | 
| 
       25 
33 
     | 
    
         | 
| 
       26 
     | 
    
         
            -
                def get_data(self, url) -> str:
         
     | 
| 
      
 34 
     | 
    
         
            +
                def get_data(self, url: str) -> str:
         
     | 
| 
       27 
35 
     | 
    
         
             
                    """This method makes the request to the council
         
     | 
| 
       28 
36 
     | 
    
         | 
| 
       29 
37 
     | 
    
         
             
                    Keyword arguments:
         
     | 
| 
         @@ -44,21 +52,27 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       44 
52 
     | 
    
         
             
                        full_page = session.get(url)
         
     | 
| 
       45 
53 
     | 
    
         
             
                        return full_page
         
     | 
| 
       46 
54 
     | 
    
         
             
                    except requests.exceptions.HTTPError as errh:
         
     | 
| 
       47 
     | 
    
         
            -
                         
     | 
| 
      
 55 
     | 
    
         
            +
                        logging.error(f"Http Error: {errh}")
         
     | 
| 
       48 
56 
     | 
    
         
             
                        raise
         
     | 
| 
       49 
57 
     | 
    
         
             
                    except requests.exceptions.ConnectionError as errc:
         
     | 
| 
       50 
     | 
    
         
            -
                         
     | 
| 
      
 58 
     | 
    
         
            +
                        logging.error(f"Error Connecting: {errc}")
         
     | 
| 
       51 
59 
     | 
    
         
             
                        raise
         
     | 
| 
       52 
60 
     | 
    
         
             
                    except requests.exceptions.Timeout as errt:
         
     | 
| 
       53 
     | 
    
         
            -
                         
     | 
| 
      
 61 
     | 
    
         
            +
                        logging.error(f"Timeout Error: {errt}")
         
     | 
| 
       54 
62 
     | 
    
         
             
                        raise
         
     | 
| 
       55 
63 
     | 
    
         
             
                    except requests.exceptions.RequestException as err:
         
     | 
| 
       56 
     | 
    
         
            -
                         
     | 
| 
      
 64 
     | 
    
         
            +
                        logging.error(f"Oops: Something Else {err}")
         
     | 
| 
       57 
65 
     | 
    
         
             
                        raise
         
     | 
| 
       58 
66 
     | 
    
         | 
| 
       59 
     | 
    
         
            -
                def parse_data(self, page: str, **kwargs) ->  
     | 
| 
       60 
     | 
    
         
            -
                    uprn = kwargs.get("uprn")
         
     | 
| 
       61 
     | 
    
         
            -
                    postcode = kwargs.get("postcode")
         
     | 
| 
      
 67 
     | 
    
         
            +
                def parse_data(self, page: str, **kwargs: Any) -> Dict[str, List[Dict[str, str]]]:
         
     | 
| 
      
 68 
     | 
    
         
            +
                    uprn: Optional[str] = kwargs.get("uprn")
         
     | 
| 
      
 69 
     | 
    
         
            +
                    postcode: Optional[str] = kwargs.get("postcode")
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                    if uprn is None:
         
     | 
| 
      
 72 
     | 
    
         
            +
                        raise ValueError("UPRN is required and must be a non-empty string.")
         
     | 
| 
      
 73 
     | 
    
         
            +
                    if postcode is None:
         
     | 
| 
      
 74 
     | 
    
         
            +
                        raise ValueError("Postcode is required and must be a non-empty string.")
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
       62 
76 
     | 
    
         
             
                    check_uprn(uprn)
         
     | 
| 
       63 
77 
     | 
    
         
             
                    check_postcode(postcode)
         
     | 
| 
       64 
78 
     | 
    
         | 
| 
         @@ -84,7 +98,7 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       84 
98 
     | 
    
         
             
                    rows = soup.find("table").find_all("tr")
         
     | 
| 
       85 
99 
     | 
    
         | 
| 
       86 
100 
     | 
    
         
             
                    # Form a JSON wrapper
         
     | 
| 
       87 
     | 
    
         
            -
                    data = {"bins": []}
         
     | 
| 
      
 101 
     | 
    
         
            +
                    data: Dict[str, List[Dict[str, str]]] = {"bins": []}
         
     | 
| 
       88 
102 
     | 
    
         | 
| 
       89 
103 
     | 
    
         
             
                    # Loops the Rows
         
     | 
| 
       90 
104 
     | 
    
         
             
                    for row in rows:
         
     | 
| 
         @@ -93,13 +107,13 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       93 
107 
     | 
    
         
             
                            bin_type = cells[0].get_text(strip=True)
         
     | 
| 
       94 
108 
     | 
    
         
             
                            collection_next = cells[1].get_text(strip=True)
         
     | 
| 
       95 
109 
     | 
    
         | 
| 
       96 
     | 
    
         
            -
                            collection_date = re.findall("\(.*?\)", collection_next)
         
     | 
| 
      
 110 
     | 
    
         
            +
                            collection_date = re.findall(r"\(.*?\)", collection_next)
         
     | 
| 
       97 
111 
     | 
    
         | 
| 
       98 
112 
     | 
    
         
             
                            if len(collection_date) != 1:
         
     | 
| 
       99 
113 
     | 
    
         
             
                                continue
         
     | 
| 
       100 
114 
     | 
    
         | 
| 
       101 
115 
     | 
    
         
             
                            collection_date_obj = parse(
         
     | 
| 
       102 
     | 
    
         
            -
                                re.sub("[()]", "", collection_date[0])
         
     | 
| 
      
 116 
     | 
    
         
            +
                                re.sub(r"[()]", "", collection_date[0])
         
     | 
| 
       103 
117 
     | 
    
         
             
                            ).date()
         
     | 
| 
       104 
118 
     | 
    
         | 
| 
       105 
119 
     | 
    
         
             
                            # since we only have the next collection day, if the parsed date is in the past,
         
     | 
| 
         @@ -64,7 +64,7 @@ def get_csrf_token(s: requests.session, base_url: str) -> str: 
     | 
|
| 
       64 
64 
     | 
    
         
             
                """
         
     | 
| 
       65 
65 
     | 
    
         
             
                Gets a CSRF token
         
     | 
| 
       66 
66 
     | 
    
         
             
                    :rtype: str
         
     | 
| 
       67 
     | 
    
         
            -
                    :param s: requests. 
     | 
| 
      
 67 
     | 
    
         
            +
                    :param s: requests.Session() to use
         
     | 
| 
       68 
68 
     | 
    
         
             
                    :param base_url: Base URL to use
         
     | 
| 
       69 
69 
     | 
    
         
             
                    :return: CSRF token
         
     | 
| 
       70 
70 
     | 
    
         
             
                """
         
     | 
| 
         @@ -95,7 +95,7 @@ def get_address_id( 
     | 
|
| 
       95 
95 
     | 
    
         
             
                """
         
     | 
| 
       96 
96 
     | 
    
         
             
                Gets the address ID
         
     | 
| 
       97 
97 
     | 
    
         
             
                    :rtype: str
         
     | 
| 
       98 
     | 
    
         
            -
                    :param s: requests. 
     | 
| 
      
 98 
     | 
    
         
            +
                    :param s: requests.Session() to use
         
     | 
| 
       99 
99 
     | 
    
         
             
                    :param base_url: Base URL to use
         
     | 
| 
       100 
100 
     | 
    
         
             
                    :param csrf_token: CSRF token to use
         
     | 
| 
       101 
101 
     | 
    
         
             
                    :param postcode: Postcode to use
         
     | 
| 
         @@ -156,7 +156,7 @@ def get_collection_data( 
     | 
|
| 
       156 
156 
     | 
    
         
             
                """
         
     | 
| 
       157 
157 
     | 
    
         
             
                Gets the collection data
         
     | 
| 
       158 
158 
     | 
    
         
             
                    :rtype: str
         
     | 
| 
       159 
     | 
    
         
            -
                    :param s: requests. 
     | 
| 
      
 159 
     | 
    
         
            +
                    :param s: requests.Session() to use
         
     | 
| 
       160 
160 
     | 
    
         
             
                    :param base_url: Base URL to use
         
     | 
| 
       161 
161 
     | 
    
         
             
                    :param csrf_token: CSRF token to use
         
     | 
| 
       162 
162 
     | 
    
         
             
                    :param address_id: Address id to use
         
     | 
| 
         @@ -203,7 +203,7 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       203 
203 
     | 
    
         | 
| 
       204 
204 
     | 
    
         
             
                def parse_data(self, page: str, **kwargs) -> dict:
         
     | 
| 
       205 
205 
     | 
    
         
             
                    requests.packages.urllib3.disable_warnings()
         
     | 
| 
       206 
     | 
    
         
            -
                    s = requests. 
     | 
| 
      
 206 
     | 
    
         
            +
                    s = requests.Session()
         
     | 
| 
       207 
207 
     | 
    
         
             
                    base_url = "https://selfservice.mybfc.bracknell-forest.gov.uk"
         
     | 
| 
       208 
208 
     | 
    
         
             
                    paon = kwargs.get("paon")
         
     | 
| 
       209 
209 
     | 
    
         
             
                    postcode = kwargs.get("postcode")
         
     | 
| 
         @@ -2,6 +2,7 @@ import ast 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            import requests
         
     | 
| 
       4 
4 
     | 
    
         
             
            from bs4 import BeautifulSoup
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
       5 
6 
     | 
    
         
             
            from uk_bin_collection.uk_bin_collection.common import *
         
     | 
| 
       6 
7 
     | 
    
         
             
            from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
         
     | 
| 
       7 
8 
     | 
    
         | 
| 
         @@ -19,7 +20,7 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       19 
20 
     | 
    
         
             
                    check_uprn(user_uprn)
         
     | 
| 
       20 
21 
     | 
    
         | 
| 
       21 
22 
     | 
    
         
             
                    requests.packages.urllib3.disable_warnings()
         
     | 
| 
       22 
     | 
    
         
            -
                    s = requests. 
     | 
| 
      
 23 
     | 
    
         
            +
                    s = requests.Session()
         
     | 
| 
       23 
24 
     | 
    
         | 
| 
       24 
25 
     | 
    
         
             
                    service_type_headers = {
         
     | 
| 
       25 
26 
     | 
    
         
             
                        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,"
         
     | 
| 
         @@ -1,17 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # This script pulls (in one hit) the data from Bromley Council Bins Data
         
     | 
| 
       2 
2 
     | 
    
         
             
            import datetime
         
     | 
| 
       3 
     | 
    
         
            -
            import time
         
     | 
| 
       4 
3 
     | 
    
         
             
            from datetime import datetime
         
     | 
| 
       5 
4 
     | 
    
         | 
| 
       6 
5 
     | 
    
         
             
            from bs4 import BeautifulSoup
         
     | 
| 
       7 
6 
     | 
    
         
             
            from dateutil.relativedelta import relativedelta
         
     | 
| 
       8 
7 
     | 
    
         
             
            from selenium.webdriver.common.by import By
         
     | 
| 
       9 
     | 
    
         
            -
            from selenium.webdriver.common.keys import Keys
         
     | 
| 
       10 
8 
     | 
    
         
             
            from selenium.webdriver.support import expected_conditions as EC
         
     | 
| 
       11 
     | 
    
         
            -
            from selenium.webdriver.support.ui import  
     | 
| 
       12 
     | 
    
         
            -
            from selenium.webdriver.support.wait import WebDriverWait
         
     | 
| 
      
 9 
     | 
    
         
            +
            from selenium.webdriver.support.ui import WebDriverWait
         
     | 
| 
       13 
10 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
            from uk_bin_collection.uk_bin_collection.common import  
     | 
| 
      
 11 
     | 
    
         
            +
            from uk_bin_collection.uk_bin_collection.common import create_webdriver
         
     | 
| 
       15 
12 
     | 
    
         
             
            from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
         
     | 
| 
       16 
13 
     | 
    
         | 
| 
       17 
14 
     | 
    
         | 
| 
         @@ -28,7 +25,6 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       28 
25 
     | 
    
         
             
                    driver = None
         
     | 
| 
       29 
26 
     | 
    
         
             
                    try:
         
     | 
| 
       30 
27 
     | 
    
         
             
                        bin_data_dict = {"bins": []}
         
     | 
| 
       31 
     | 
    
         
            -
                        collections = []
         
     | 
| 
       32 
28 
     | 
    
         
             
                        web_driver = kwargs.get("web_driver")
         
     | 
| 
       33 
29 
     | 
    
         
             
                        headless = kwargs.get("headless")
         
     | 
| 
       34 
30 
     | 
    
         | 
| 
         @@ -39,18 +35,14 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       39 
35 
     | 
    
         
             
                        driver.get(kwargs.get("url"))
         
     | 
| 
       40 
36 
     | 
    
         | 
| 
       41 
37 
     | 
    
         
             
                        wait = WebDriverWait(driver, 30)
         
     | 
| 
       42 
     | 
    
         
            -
                         
     | 
| 
      
 38 
     | 
    
         
            +
                        wait.until(
         
     | 
| 
       43 
39 
     | 
    
         
             
                            EC.presence_of_element_located((By.CLASS_NAME, "waste-service-image"))
         
     | 
| 
       44 
40 
     | 
    
         
             
                        )
         
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
                        # Parse the HTML content
         
     | 
| 
       47 
     | 
    
         
            -
                        # Find all elements with the class 'container-name' to extract bin types
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
       48 
42 
     | 
    
         
             
                        # Parse the HTML content
         
     | 
| 
       49 
43 
     | 
    
         
             
                        soup = BeautifulSoup(driver.page_source, "html.parser")
         
     | 
| 
       50 
     | 
    
         
            -
                        soup.prettify
         
     | 
| 
       51 
44 
     | 
    
         | 
| 
       52 
45 
     | 
    
         
             
                        # Find all elements with class 'govuk-summary-list'
         
     | 
| 
       53 
     | 
    
         
            -
                        bin_info = []
         
     | 
| 
       54 
46 
     | 
    
         
             
                        waste_services = soup.find_all(
         
     | 
| 
       55 
47 
     | 
    
         
             
                            "h3", class_="govuk-heading-m waste-service-name"
         
     | 
| 
       56 
48 
     | 
    
         
             
                        )
         
     | 
| 
         @@ -58,7 +50,7 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       58 
50 
     | 
    
         
             
                        for service in waste_services:
         
     | 
| 
       59 
51 
     | 
    
         
             
                            service_title = service.get_text(strip=True)
         
     | 
| 
       60 
52 
     | 
    
         
             
                            next_collection = service.find_next_sibling().find(
         
     | 
| 
       61 
     | 
    
         
            -
                                "dt",  
     | 
| 
      
 53 
     | 
    
         
            +
                                "dt", string="Next collection"
         
     | 
| 
       62 
54 
     | 
    
         
             
                            )
         
     | 
| 
       63 
55 
     | 
    
         | 
| 
       64 
56 
     | 
    
         
             
                            if next_collection:
         
     | 
| 
         @@ -69,8 +61,7 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       69 
61 
     | 
    
         
             
                                next_collection_date_parse = next_collection_date.split(",")[
         
     | 
| 
       70 
62 
     | 
    
         
             
                                    1
         
     | 
| 
       71 
63 
     | 
    
         
             
                                ].strip()
         
     | 
| 
       72 
     | 
    
         
            -
                                day = next_collection_date_parse.split()[ 
     | 
| 
       73 
     | 
    
         
            -
                                month = next_collection_date_parse.split()[1]
         
     | 
| 
      
 64 
     | 
    
         
            +
                                day, month = next_collection_date_parse.split()[:2]
         
     | 
| 
       74 
65 
     | 
    
         | 
| 
       75 
66 
     | 
    
         
             
                                # Remove the suffix (e.g., 'th', 'nd', 'rd', 'st') from the day
         
     | 
| 
       76 
67 
     | 
    
         
             
                                if day.endswith(("th", "nd", "rd", "st")):
         
     | 
| 
         @@ -1,11 +1,11 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            import time
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
2 
     | 
    
         
             
            import pandas as pd
         
     | 
| 
       4 
3 
     | 
    
         
             
            from selenium.webdriver.common.by import By
         
     | 
| 
       5 
4 
     | 
    
         
             
            from selenium.webdriver.common.keys import Keys
         
     | 
| 
       6 
5 
     | 
    
         
             
            from selenium.webdriver.support.ui import Select
         
     | 
| 
      
 6 
     | 
    
         
            +
            from io import StringIO
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
            from uk_bin_collection.uk_bin_collection.common import  
     | 
| 
      
 8 
     | 
    
         
            +
            from uk_bin_collection.uk_bin_collection.common import create_webdriver
         
     | 
| 
       9 
9 
     | 
    
         
             
            from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
11 
     | 
    
         | 
| 
         @@ -16,7 +16,7 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       16 
16 
     | 
    
         
             
                implementation.
         
     | 
| 
       17 
17 
     | 
    
         
             
                """
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
       19 
     | 
    
         
            -
                def get_data(self, df) -> dict:
         
     | 
| 
      
 19 
     | 
    
         
            +
                def get_data(self, df: pd.DataFrame) -> dict:
         
     | 
| 
       20 
20 
     | 
    
         
             
                    # Create dictionary of data to be returned
         
     | 
| 
       21 
21 
     | 
    
         
             
                    data = {"bins": []}
         
     | 
| 
       22 
22 
     | 
    
         | 
| 
         @@ -69,7 +69,7 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       69 
69 
     | 
    
         
             
                        time.sleep(4)
         
     | 
| 
       70 
70 
     | 
    
         | 
| 
       71 
71 
     | 
    
         
             
                        # Submit address information and wait
         
     | 
| 
       72 
     | 
    
         
            -
                         
     | 
| 
      
 72 
     | 
    
         
            +
                        driver.find_element(
         
     | 
| 
       73 
73 
     | 
    
         
             
                            By.ID, "COPYOFECHOCOLLECTIONDATES_ADDRESSSELECTION_NAV1_NEXT"
         
     | 
| 
       74 
74 
     | 
    
         
             
                        ).click()
         
     | 
| 
       75 
75 
     | 
    
         | 
| 
         @@ -79,8 +79,10 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       79 
79 
     | 
    
         
             
                        table = driver.find_element(
         
     | 
| 
       80 
80 
     | 
    
         
             
                            By.ID, "COPYOFECHOCOLLECTIONDATES_PAGE1_DATES2"
         
     | 
| 
       81 
81 
     | 
    
         
             
                        ).get_attribute("outerHTML")
         
     | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
       83 
     | 
    
         
            -
                         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                        # Wrap the HTML table in a StringIO object to address the warning
         
     | 
| 
      
 84 
     | 
    
         
            +
                        table_io = StringIO(table)
         
     | 
| 
      
 85 
     | 
    
         
            +
                        df = pd.read_html(table_io, header=[1])[0]
         
     | 
| 
       84 
86 
     | 
    
         | 
| 
       85 
87 
     | 
    
         
             
                        # Parse data into dict
         
     | 
| 
       86 
88 
     | 
    
         
             
                        data = self.get_data(df)
         
     | 
| 
         @@ -1,6 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            import time
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            from bs4 import BeautifulSoup
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
       4 
5 
     | 
    
         
             
            from uk_bin_collection.uk_bin_collection.common import *
         
     | 
| 
       5 
6 
     | 
    
         
             
            from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
         
     | 
| 
       6 
7 
     | 
    
         | 
| 
         @@ -75,7 +76,7 @@ def get_csrf_token(s: requests.session, base_url: str) -> str: 
     | 
|
| 
       75 
76 
     | 
    
         
             
                """
         
     | 
| 
       76 
77 
     | 
    
         
             
                Gets a CSRF token
         
     | 
| 
       77 
78 
     | 
    
         
             
                    :rtype: str
         
     | 
| 
       78 
     | 
    
         
            -
                    :param s: requests. 
     | 
| 
      
 79 
     | 
    
         
            +
                    :param s: requests.Session() to use
         
     | 
| 
       79 
80 
     | 
    
         
             
                    :param base_url: Base URL to use
         
     | 
| 
       80 
81 
     | 
    
         
             
                    :return: CSRF token
         
     | 
| 
       81 
82 
     | 
    
         
             
                """
         
     | 
| 
         @@ -106,7 +107,7 @@ def get_address_id( 
     | 
|
| 
       106 
107 
     | 
    
         
             
                """
         
     | 
| 
       107 
108 
     | 
    
         
             
                Gets the address ID
         
     | 
| 
       108 
109 
     | 
    
         
             
                    :rtype: str
         
     | 
| 
       109 
     | 
    
         
            -
                    :param s: requests. 
     | 
| 
      
 110 
     | 
    
         
            +
                    :param s: requests.Session() to use
         
     | 
| 
       110 
111 
     | 
    
         
             
                    :param base_url: Base URL to use
         
     | 
| 
       111 
112 
     | 
    
         
             
                    :param csrf_token: CSRF token to use
         
     | 
| 
       112 
113 
     | 
    
         
             
                    :param postcode: Postcode to use
         
     | 
| 
         @@ -171,7 +172,7 @@ def get_collection_data( 
     | 
|
| 
       171 
172 
     | 
    
         
             
                """
         
     | 
| 
       172 
173 
     | 
    
         
             
                Gets the collection data
         
     | 
| 
       173 
174 
     | 
    
         
             
                    :rtype: str
         
     | 
| 
       174 
     | 
    
         
            -
                    :param s: requests. 
     | 
| 
      
 175 
     | 
    
         
            +
                    :param s: requests.Session() to use
         
     | 
| 
       175 
176 
     | 
    
         
             
                    :param base_url: Base URL to use
         
     | 
| 
       176 
177 
     | 
    
         
             
                    :param csrf_token: CSRF token to use
         
     | 
| 
       177 
178 
     | 
    
         
             
                    :param address_id: Address id to use
         
     | 
| 
         @@ -240,7 +241,7 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       240 
241 
     | 
    
         | 
| 
       241 
242 
     | 
    
         
             
                def parse_data(self, page: str, **kwargs) -> dict:
         
     | 
| 
       242 
243 
     | 
    
         
             
                    requests.packages.urllib3.disable_warnings()
         
     | 
| 
       243 
     | 
    
         
            -
                    s = requests. 
     | 
| 
      
 244 
     | 
    
         
            +
                    s = requests.Session()
         
     | 
| 
       244 
245 
     | 
    
         
             
                    base_url = "https://service.croydon.gov.uk"
         
     | 
| 
       245 
246 
     | 
    
         
             
                    paon = kwargs.get("paon")
         
     | 
| 
       246 
247 
     | 
    
         
             
                    postcode = kwargs.get("postcode")
         
     | 
| 
         @@ -17,27 +17,25 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       17 
17 
     | 
    
         
             
                    soup.prettify()
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
                    # Extract data
         
     | 
| 
       20 
     | 
    
         
            -
                    bin_data = {
         
     | 
| 
       21 
     | 
    
         
            -
                        "bins": []
         
     | 
| 
       22 
     | 
    
         
            -
                    }
         
     | 
| 
      
 20 
     | 
    
         
            +
                    bin_data = {"bins": []}
         
     | 
| 
       23 
21 
     | 
    
         | 
| 
       24 
22 
     | 
    
         
             
                    # Find the table containing the bin collection data
         
     | 
| 
       25 
     | 
    
         
            -
                    table = soup.find( 
     | 
| 
      
 23 
     | 
    
         
            +
                    table = soup.find("table", {"class": "eb-EVDNdR1G-tableContent"})
         
     | 
| 
       26 
24 
     | 
    
         | 
| 
       27 
25 
     | 
    
         
             
                    if table:
         
     | 
| 
       28 
     | 
    
         
            -
                        rows = table.find_all( 
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
      
 26 
     | 
    
         
            +
                        rows = table.find_all("tr", class_="eb-EVDNdR1G-tableRow")
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
       30 
28 
     | 
    
         
             
                        for row in rows:
         
     | 
| 
       31 
     | 
    
         
            -
                            columns = row.find_all( 
     | 
| 
      
 29 
     | 
    
         
            +
                            columns = row.find_all("td")
         
     | 
| 
       32 
30 
     | 
    
         
             
                            if len(columns) >= 4:
         
     | 
| 
       33 
31 
     | 
    
         
             
                                collection_type = columns[1].get_text(strip=True)
         
     | 
| 
       34 
32 
     | 
    
         
             
                                collection_date = columns[3].get_text(strip=True)
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
       36 
34 
     | 
    
         
             
                                # Validate collection_date format
         
     | 
| 
       37 
35 
     | 
    
         
             
                                if re.match(r"\d{2}/\d{2}/\d{4}", collection_date):
         
     | 
| 
       38 
36 
     | 
    
         
             
                                    bin_entry = {
         
     | 
| 
       39 
37 
     | 
    
         
             
                                        "type": collection_type,
         
     | 
| 
       40 
     | 
    
         
            -
                                        "collectionDate": collection_date
         
     | 
| 
      
 38 
     | 
    
         
            +
                                        "collectionDate": collection_date,
         
     | 
| 
       41 
39 
     | 
    
         
             
                                    }
         
     | 
| 
       42 
40 
     | 
    
         
             
                                    bin_data["bins"].append(bin_entry)
         
     | 
| 
       43 
41 
     | 
    
         | 
| 
         @@ -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 
     | 
    
         | 
| 
         @@ -21,7 +22,7 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       21 
22 
     | 
    
         | 
| 
       22 
23 
     | 
    
         
             
                    # Make Request
         
     | 
| 
       23 
24 
     | 
    
         
             
                    requests.packages.urllib3.disable_warnings()
         
     | 
| 
       24 
     | 
    
         
            -
                    s = requests. 
     | 
| 
      
 25 
     | 
    
         
            +
                    s = requests.Session()
         
     | 
| 
       25 
26 
     | 
    
         
             
                    page = s.get(url)
         
     | 
| 
       26 
27 
     | 
    
         | 
| 
       27 
28 
     | 
    
         
             
                    # Make a BS4 object
         
     | 
| 
         @@ -1,6 +1,10 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            from bs4 import BeautifulSoup
         
     | 
| 
      
 2 
     | 
    
         
            +
            import requests
         
     | 
| 
      
 3 
     | 
    
         
            +
            import logging
         
     | 
| 
      
 4 
     | 
    
         
            +
            import re
         
     | 
| 
      
 5 
     | 
    
         
            +
            from typing import Dict, List, Any, Optional
         
     | 
| 
       2 
6 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            from uk_bin_collection.uk_bin_collection.common import  
     | 
| 
      
 7 
     | 
    
         
            +
            from uk_bin_collection.uk_bin_collection.common import check_uprn
         
     | 
| 
       4 
8 
     | 
    
         
             
            from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
         
     | 
| 
       5 
9 
     | 
    
         | 
| 
       6 
10 
     | 
    
         | 
| 
         @@ -11,10 +15,14 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       11 
15 
     | 
    
         
             
                implementation.
         
     | 
| 
       12 
16 
     | 
    
         
             
                """
         
     | 
| 
       13 
17 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
                def parse_data(self, page: str, **kwargs) ->  
     | 
| 
       15 
     | 
    
         
            -
                    data = {"bins": []}
         
     | 
| 
      
 18 
     | 
    
         
            +
                def parse_data(self, page: str, **kwargs: Any) -> Dict[str, List[Dict[str, str]]]:
         
     | 
| 
      
 19 
     | 
    
         
            +
                    data: Dict[str, List[Dict[str, str]]] = {"bins": []}
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    uprn: Optional[str] = kwargs.get("uprn")
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    if uprn is None:
         
     | 
| 
      
 24 
     | 
    
         
            +
                        raise ValueError("UPRN is required and must be a non-empty string.")
         
     | 
| 
       16 
25 
     | 
    
         | 
| 
       17 
     | 
    
         
            -
                    uprn = kwargs.get("uprn")
         
     | 
| 
       18 
26 
     | 
    
         
             
                    check_uprn(uprn)  # Assuming check_uprn() raises an exception if UPRN is invalid
         
     | 
| 
       19 
27 
     | 
    
         | 
| 
       20 
28 
     | 
    
         
             
                    try:
         
     | 
| 
         @@ -42,7 +50,7 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       42 
50 
     | 
    
         | 
| 
       43 
51 
     | 
    
         
             
                            if service_name_element and next_service_element:
         
     | 
| 
       44 
52 
     | 
    
         
             
                                service = service_name_element.text
         
     | 
| 
       45 
     | 
    
         
            -
                                next_collection = next_service_element.find( 
     | 
| 
      
 53 
     | 
    
         
            +
                                next_collection = next_service_element.find(string=date_regex)
         
     | 
| 
       46 
54 
     | 
    
         | 
| 
       47 
55 
     | 
    
         
             
                                if next_collection:
         
     | 
| 
       48 
56 
     | 
    
         
             
                                    dict_data = {
         
     | 
| 
         @@ -1,8 +1,10 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
         
     | 
| 
       2 
     | 
    
         
            -
            from uk_bin_collection.uk_bin_collection.common import *
         
     | 
| 
       3 
     | 
    
         
            -
            from bs4 import BeautifulSoup
         
     | 
| 
       4 
1 
     | 
    
         
             
            from datetime import datetime
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       5 
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
         
     | 
| 
       6 
8 
     | 
    
         | 
| 
       7 
9 
     | 
    
         | 
| 
       8 
10 
     | 
    
         
             
            class CouncilClass(AbstractGetBinDataClass):
         
     | 
| 
         @@ -19,7 +21,7 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       19 
21 
     | 
    
         
             
                    # start session
         
     | 
| 
       20 
22 
     | 
    
         
             
                    # note: this ignores the given url
         
     | 
| 
       21 
23 
     | 
    
         
             
                    base_url = "https://lcc-wrp.whitespacews.com"
         
     | 
| 
       22 
     | 
    
         
            -
                    session = requests. 
     | 
| 
      
 24 
     | 
    
         
            +
                    session = requests.Session()
         
     | 
| 
       23 
25 
     | 
    
         
             
                    response = session.get(base_url + "/#!")
         
     | 
| 
       24 
26 
     | 
    
         
             
                    links = [
         
     | 
| 
       25 
27 
     | 
    
         
             
                        a["href"]
         
     | 
| 
         @@ -2,6 +2,7 @@ from datetime import datetime 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            import requests
         
     | 
| 
       4 
4 
     | 
    
         
             
            from bs4 import BeautifulSoup
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
       5 
6 
     | 
    
         
             
            from uk_bin_collection.uk_bin_collection.common import *
         
     | 
| 
       6 
7 
     | 
    
         
             
            from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
         
     | 
| 
       7 
8 
     | 
    
         | 
| 
         @@ -20,7 +21,7 @@ class CouncilClass(AbstractGetBinDataClass): 
     | 
|
| 
       20 
21 
     | 
    
         | 
| 
       21 
22 
     | 
    
         
             
                    # Start a new session to walk through the form
         
     | 
| 
       22 
23 
     | 
    
         
             
                    requests.packages.urllib3.disable_warnings()
         
     | 
| 
       23 
     | 
    
         
            -
                    s = requests. 
     | 
| 
      
 24 
     | 
    
         
            +
                    s = requests.Session()
         
     | 
| 
       24 
25 
     | 
    
         | 
| 
       25 
26 
     | 
    
         
             
                    # There's a cookie that makes the whole thing valid when you search for a postcode,
         
     | 
| 
       26 
27 
     | 
    
         
             
                    # but postcode and UPRN is a hassle imo, so this makes a request for the session to get a cookie
         
     |