From f8c39f153ab08d10f71eaab57fbe7686a9e98ce5 Mon Sep 17 00:00:00 2001 From: Joe McCarthy <179146301+joe-mccarthy@users.noreply.github.com> Date: Fri, 13 Sep 2024 14:04:42 +0100 Subject: [PATCH] rewrite to remove mqtt and send notification to ntfy, no longer runs in loop and takes commandline arguments --- .gitignore | 1 + README.md | 4 ++ config.ini | 13 ---- harlow_bindicator/app/bindicator.py | 26 -------- harlow_bindicator/app/configuration.py | 14 ----- harlow_bindicator/app/mqtt.py | 25 -------- harlow_bindicator/main.py | 26 -------- {harlow_bindicator => src}/__init__.py | 0 {harlow_bindicator => src}/app/__init__.py | 0 {harlow_bindicator => src}/app/api.py | 7 +-- src/app/bindicator.py | 27 ++++++++ {harlow_bindicator => src}/app/browser.py | 8 +-- {harlow_bindicator => src}/app/data.py | 0 {harlow_bindicator => src}/app/page_parser.py | 0 src/main.py | 25 ++++++++ tests/test_api.py | 15 ++--- tests/test_bindicator.py | 63 +++++++++---------- tests/test_browser.py | 24 +++---- tests/test_configuration.py | 26 -------- tests/test_data.py | 4 +- tests/test_mqtt_client.py | 20 ------ tests/test_parser.py | 2 +- tox.ini | 2 +- 23 files changed, 114 insertions(+), 218 deletions(-) delete mode 100644 config.ini delete mode 100644 harlow_bindicator/app/bindicator.py delete mode 100644 harlow_bindicator/app/configuration.py delete mode 100644 harlow_bindicator/app/mqtt.py delete mode 100644 harlow_bindicator/main.py rename {harlow_bindicator => src}/__init__.py (100%) rename {harlow_bindicator => src}/app/__init__.py (100%) rename {harlow_bindicator => src}/app/api.py (67%) create mode 100644 src/app/bindicator.py rename {harlow_bindicator => src}/app/browser.py (81%) rename {harlow_bindicator => src}/app/data.py (100%) rename {harlow_bindicator => src}/app/page_parser.py (100%) create mode 100644 src/main.py delete mode 100644 tests/test_configuration.py delete mode 100644 tests/test_mqtt_client.py diff --git a/.gitignore b/.gitignore index 5d381cc..3f82210 100644 --- a/.gitignore +++ b/.gitignore @@ -160,3 +160,4 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index 72dd735..792f473 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ ![GitHub commits since latest release](https://img.shields.io/github/commits-since/joe-mccarthy/harlow-bindicator/latest?cacheSeconds=1) ![GitHub License](https://img.shields.io/github/license/joe-mccarthy/harlow-bindicator?cacheSeconds=1) +```bash +sudo apt-get install chromium-chromedriver +``` + ## Contributing Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated. diff --git a/config.ini b/config.ini deleted file mode 100644 index ae95b55..0000000 --- a/config.ini +++ /dev/null @@ -1,13 +0,0 @@ -[MQTT] -address = broker -topic = bindicator -client_id = bindicator_client - -[Property] -uprn = 00000000001 - -[Logging] -level = INFO - -[Runner] -minutes_between_runs = 60 \ No newline at end of file diff --git a/harlow_bindicator/app/bindicator.py b/harlow_bindicator/app/bindicator.py deleted file mode 100644 index d9b12cd..0000000 --- a/harlow_bindicator/app/bindicator.py +++ /dev/null @@ -1,26 +0,0 @@ -import logging - -from .api import Api -from .configuration import Configuration -from .mqtt import MQTT - - -class Bindicator: - - api: Api - configuration: Configuration - - def __init__(self, configuration: Configuration) -> None: - self.configuration = configuration - self.mqtt_client = MQTT(self.configuration) - self.api = Api(self.configuration) - - def run(self): - logging.info('Running Harlow Bindicator') - logging.debug(f'Property for bin collection {self.configuration.uprn}') - collections = self.api.get_data() - - if collections: - logging.debug( - f'{len(collections)} found publishing first collection date information') - self.mqtt_client.publish(collections[0].create_message()) diff --git a/harlow_bindicator/app/configuration.py b/harlow_bindicator/app/configuration.py deleted file mode 100644 index bd70a18..0000000 --- a/harlow_bindicator/app/configuration.py +++ /dev/null @@ -1,14 +0,0 @@ -import configparser - - -class Configuration: - - def __init__(self) -> None: - config = configparser.ConfigParser() - config.read('config.ini') - self.mqtt_addresss = config['MQTT']['address'] - self.mqtt_topic = config['MQTT']['topic'] - self.mqtt_client_id = config['MQTT']['client_id'] - self.uprn = config['Property']['uprn'] - self.logging_level = config['Logging']['level'] - self.pause_minutes = int(config['Runner']['minutes_between_runs']) diff --git a/harlow_bindicator/app/mqtt.py b/harlow_bindicator/app/mqtt.py deleted file mode 100644 index 84e2671..0000000 --- a/harlow_bindicator/app/mqtt.py +++ /dev/null @@ -1,25 +0,0 @@ -import logging - -import paho.mqtt.client as paho - -from .configuration import Configuration - - -class MQTT: - - def __init__(self, configuration: Configuration) -> None: - self.configuration = configuration - logging.debug( - f'Creating MQTT client with ID:{self.configuration.mqtt_client_id}') - self.mqtt_client = paho.Client( - client_id=self.configuration.mqtt_client_id) - logging.debug( - f'Attempting connection to MQTT broker {self.configuration.mqtt_addresss}') - self.mqtt_client.connect(self.configuration.mqtt_addresss) - logging.info('Connected to MQTT Broker') - - def publish(self, message: str): - logging.debug( - f'Publishing message {message} to {self.configuration.mqtt_topic} on broker {self.configuration.mqtt_addresss}') - self.mqtt_client.publish(self.configuration.mqtt_topic, message) - logging.info('Published message to MQTT Broker') diff --git a/harlow_bindicator/main.py b/harlow_bindicator/main.py deleted file mode 100644 index 244c961..0000000 --- a/harlow_bindicator/main.py +++ /dev/null @@ -1,26 +0,0 @@ -import logging -import time - -from app.bindicator import Bindicator -from app.configuration import Configuration - -if __name__ == "__main__": - - configuration = Configuration() - - logging.basicConfig( - level=configuration.logging_level, - format="%(asctime)s | %(levelname)-8s | %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - ) - - logging.debug( - f'Parsed configuraiton and set logging to {configuration.logging_level}') - - while True: - logging.info('Creating Harlow Bindicator') - bindicator = Bindicator(configuration) - bindicator.run() - logging.info('Harlow Bindicator run completed') - logging.info(f'Sleeping for {configuration.pause_minutes} minutes') - time.sleep(60 * configuration.pause_minutes) diff --git a/harlow_bindicator/__init__.py b/src/__init__.py similarity index 100% rename from harlow_bindicator/__init__.py rename to src/__init__.py diff --git a/harlow_bindicator/app/__init__.py b/src/app/__init__.py similarity index 100% rename from harlow_bindicator/app/__init__.py rename to src/app/__init__.py diff --git a/harlow_bindicator/app/api.py b/src/app/api.py similarity index 67% rename from harlow_bindicator/app/api.py rename to src/app/api.py index cc12f33..eceb2c2 100644 --- a/harlow_bindicator/app/api.py +++ b/src/app/api.py @@ -1,7 +1,6 @@ from typing import List from .browser import Browser -from .configuration import Configuration from .data import CollectionDate from .page_parser import Parser @@ -10,9 +9,9 @@ class Api: url: str = "https://selfserve.harlow.gov.uk/appshost/firmstep/self/apps/custompage/bincollectionsecho?uprn=" - def __init__(self, configuration: Configuration) -> None: - self.configuration = configuration - self.browser = Browser(configuration, self.url) + def __init__(self, uprn) -> None: + self.uprn = uprn + self.browser = Browser(uprn, self.url) def get_data(self) -> List[CollectionDate]: page_source = self.browser.get_web_page() diff --git a/src/app/bindicator.py b/src/app/bindicator.py new file mode 100644 index 0000000..ca1ea35 --- /dev/null +++ b/src/app/bindicator.py @@ -0,0 +1,27 @@ +import logging + +from .api import Api +from datetime import date +import requests + +class Bindicator: + + api: Api + + def __init__(self, uprn, topic) -> None: + self.api = Api(uprn) + self.topic = topic + + def run(self): + collections = self.api.get_data() + + if collections: + logging.debug( + f'{len(collections)} found publishing first collection date information') + if date.today() == collections[0].date: + message = f'Bin collection is today for {collections[0].wheelie.bin_type}' + logging.info(message ) + requests.post(f"https://ntfy.sh/{self.topic}",data=message.encode(encoding='utf-8')) + else: + logging.info( + f'Next bin collection is {collections[0].date}, {collections[0].wheelie.bin_type}') \ No newline at end of file diff --git a/harlow_bindicator/app/browser.py b/src/app/browser.py similarity index 81% rename from harlow_bindicator/app/browser.py rename to src/app/browser.py index 43b8129..8e1b401 100644 --- a/harlow_bindicator/app/browser.py +++ b/src/app/browser.py @@ -4,17 +4,15 @@ from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service -from .configuration import Configuration - class Browser: - def __init__(self, configuration: Configuration, url: str) -> None: - self.configuration = configuration + def __init__(self, uprn, url: str) -> None: self.url = url + self.uprn = uprn def get_web_page(self) -> str: - data_url = f"{self.url}{self.configuration.uprn}" + data_url = f"{self.url}{self.uprn}" chrome_options = Options() options = [ diff --git a/harlow_bindicator/app/data.py b/src/app/data.py similarity index 100% rename from harlow_bindicator/app/data.py rename to src/app/data.py diff --git a/harlow_bindicator/app/page_parser.py b/src/app/page_parser.py similarity index 100% rename from harlow_bindicator/app/page_parser.py rename to src/app/page_parser.py diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..1321cee --- /dev/null +++ b/src/main.py @@ -0,0 +1,25 @@ +import logging +import argparse + +from app.bindicator import Bindicator + +if __name__ == "__main__": + + logging.basicConfig( + level="INFO", + format="%(asctime)s | %(levelname)-8s | %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + + logging.info('Creating Harlow Bindicator') + parser = argparse.ArgumentParser(description="Environment Checker") + parser.add_argument( + "--uprn", type=str, required=True, help="Property Reference Number" + ) + parser.add_argument( + "--topic", type=str, required=True, help="Ntfy Topic" + ) + args = parser.parse_args() + bindicator = Bindicator(args.uprn, args.topic) + bindicator.run() + logging.info('Harlow Bindicator run completed') \ No newline at end of file diff --git a/tests/test_api.py b/tests/test_api.py index 7d1a16c..91bf510 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,20 +1,17 @@ from unittest.mock import patch -from harlow_bindicator.app.api import Api -from harlow_bindicator.app.configuration import Configuration +from src.app.api import Api def test_init(): - config = Configuration() - api = Api(config) - assert api.configuration + api = Api(uprn="123456789") + assert api.uprn == "123456789" -@patch("harlow_bindicator.app.api.Parser") -@patch("harlow_bindicator.app.api.Browser") +@patch("src.app.api.Parser") +@patch("src.app.api.Browser") def test_browser_and_parser_called(mock_browser, mock_parser): - config = Configuration() - api = Api(config) + api = Api(uprn="123456789") mock_browser().get_web_page.return_value = "page_source" mock_parser().parse.return_value = ["data"] diff --git a/tests/test_bindicator.py b/tests/test_bindicator.py index 67188b1..5ad7d22 100644 --- a/tests/test_bindicator.py +++ b/tests/test_bindicator.py @@ -1,55 +1,54 @@ from unittest.mock import patch import pytest +from datetime import date, timedelta -from harlow_bindicator.app.bindicator import Bindicator -from harlow_bindicator.app.configuration import Configuration -from harlow_bindicator.app.data import Collection, CollectionDate +from src.app.bindicator import Bindicator +from src.app.data import Collection, CollectionDate def test_bindicator_init(): - with pytest.raises(Exception): + with pytest.raises(TypeError): Bindicator() -@patch("harlow_bindicator.app.bindicator.MQTT") -def test_bindicator_init_config(mock_mqtt): - config = Configuration() - bindicator = Bindicator(config) - - assert bindicator - assert bindicator.configuration - assert bindicator.mqtt_client - assert bindicator.api - mock_mqtt.called_once_with(config) - +@patch("src.app.bindicator.Api") +def test_bindicator_run_no_collections(mock_api): + bindicator = Bindicator("uprn", "topic") + mock_api().get_data.return_value = None + bindicator.run() + mock_api().get_data.assert_called_once() -@patch("harlow_bindicator.app.bindicator.MQTT") -@patch("harlow_bindicator.app.bindicator.Api") -def test_bindicator_run_no_collections(mock_api, mock_mqtt): - config = Configuration() - bindicator = Bindicator(config) - mock_api().get_data.return_value = None +@patch("src.app.bindicator.Api") +@patch("src.app.bindicator.requests") +def test_bindicator_run_collections(mock_requests,mock_api): + bindicator = Bindicator("uprn", "topic") + today = date.today() + collection = Collection("recycling", today.strftime("%d/%m/%Y")) + collection_date = CollectionDate(collection) + mock_api().get_data.return_value = [collection_date] bindicator.run() - mock_api().get_data.called_once() - assert not mock_mqtt().publish.called + mock_api().get_data.assert_called_once() + mock_requests.post.assert_called_once_with( + f"https://ntfy.sh/topic", + data=f"Bin collection is today for {collection_date.wheelie.bin_type}".encode(encoding='utf-8') + ) -@patch("harlow_bindicator.app.bindicator.MQTT") -@patch("harlow_bindicator.app.bindicator.Api") -def test_bindicator_run_collections(mock_api, mock_mqtt): - config = Configuration() - bindicator = Bindicator(config) +@patch("src.app.bindicator.Api") +@patch("src.app.bindicator.requests") +def test_no_collection_today(mock_requests,mock_api): + bindicator = Bindicator("uprn", "topic") - collection = Collection("recycling", "15/1/2023") + future_date = (date.today() + timedelta(days=1)).strftime("%d/%m/%Y") + collection = Collection("recycling", future_date) collection_date = CollectionDate(collection) mock_api().get_data.return_value = [collection_date] - expected = '{"date": "15/01/2023", "bin_day": false, "bin_type": "recycling"}' bindicator.run() - mock_api().get_data.called_once() - mock_mqtt().publish.called_once_with(expected) + mock_api().get_data.assert_called_once() + mock_requests.post.assert_not_called() diff --git a/tests/test_browser.py b/tests/test_browser.py index 817c030..3ef4aec 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -3,41 +3,37 @@ import pytest from selenium.webdriver import Chrome -from harlow_bindicator.app.browser import Browser -from harlow_bindicator.app.configuration import Configuration +from src.app.browser import Browser -def test_construtor(): +def test_constructor(): with pytest.raises(Exception): Browser() def test_constructor_with_configuration(): - config = Configuration() - browser = Browser(config, "test_url") - assert browser.configuration == config + browser = Browser("123456", "test_url") + assert browser.uprn == "123456" assert browser.url == "test_url" -@patch("harlow_bindicator.app.browser.webdriver") +@patch("src.app.browser.webdriver") def test_get_data(web_driver): - config = Configuration() - browser = Browser(config, "test_url") + browser = Browser("123456", "test_url") chrome = MagicMock(spec=Chrome) web_driver.Chrome.return_value = chrome chrome.page_source = "page_data" source = browser.get_web_page() assert source == "page_data" - chrome.get.assert_called_once_with("test_url00000000001") + chrome.get.assert_called_once_with("test_url123456") -@patch("harlow_bindicator.app.browser.webdriver") +@patch("src.app.browser.webdriver") def test_get_data_simple_mocks(web_driver): - config = Configuration() - browser = Browser(config, "test_url") + browser = Browser("123456", "test_url") web_driver.Chrome().page_source = "page_data" source = browser.get_web_page() assert source == "page_data" - web_driver.Chrome().get.assert_called_once_with("test_url00000000001") + web_driver.Chrome().get.assert_called_once_with("test_url123456") diff --git a/tests/test_configuration.py b/tests/test_configuration.py deleted file mode 100644 index 08d82ff..0000000 --- a/tests/test_configuration.py +++ /dev/null @@ -1,26 +0,0 @@ -from harlow_bindicator.app.configuration import Configuration - - -def test_configuration_broker_address(): - config = Configuration() - assert config.mqtt_addresss == "broker" - - -def test_configuration_mqtt_topic(): - config = Configuration() - assert config.mqtt_topic == "bindicator" - - -def test_configuration_mqtt_client_id(): - config = Configuration() - assert config.mqtt_client_id == "bindicator_client" - - -def test_configuration_logging_level(): - config = Configuration() - assert config.logging_level == "INFO" - - -def test_configuration_time_between_runs(): - config = Configuration() - assert config.pause_minutes == 60 diff --git a/tests/test_data.py b/tests/test_data.py index 421b164..8643530 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -3,7 +3,7 @@ import pytest -from harlow_bindicator.app.data import Collection, CollectionDate +from src.app.data import Collection, CollectionDate def test_collection_date_parse(): @@ -33,7 +33,7 @@ def test_collection_date(): assert collection_date.date == date -@patch("harlow_bindicator.app.data.datetime") +@patch("src.app.data.datetime") def test_is_binday_true(mock_datetime): collection = MagicMock() collection.date = datetime(2023, 1, 15).date() diff --git a/tests/test_mqtt_client.py b/tests/test_mqtt_client.py deleted file mode 100644 index 6f806b5..0000000 --- a/tests/test_mqtt_client.py +++ /dev/null @@ -1,20 +0,0 @@ -from unittest.mock import patch - -from harlow_bindicator.app.configuration import Configuration -from harlow_bindicator.app.mqtt import MQTT - - -@patch('harlow_bindicator.app.mqtt.paho') -def test_connection(mock_paho): - config = Configuration() - MQTT(config) - mock_paho.Client.called_once_with("bindicator_client") - mock_paho.Client.connect.called_once_with("broker") - - -@patch('harlow_bindicator.app.mqtt.paho') -def test_publish_message(mock_paho): - config = Configuration() - mqtt = MQTT(config) - mqtt.publish("test message") - mock_paho.publish.called_once_with("bindicator", "test_message") diff --git a/tests/test_parser.py b/tests/test_parser.py index 3190afb..6077137 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,6 +1,6 @@ from datetime import date -from harlow_bindicator.app.page_parser import Parser +from src.app.page_parser import Parser def test_parse_example_data(): diff --git a/tox.ini b/tox.ini index cea2450..e3b629e 100644 --- a/tox.ini +++ b/tox.ini @@ -9,4 +9,4 @@ deps = pytest-randomly -r requirements.txt commands = - pytest --cov=harlow_bindicator.app --randomly-seed=1 --cov-report=xml \ No newline at end of file + pytest --cov=src.app --cov-report xml --cov-report term \ No newline at end of file