From b2ed88e6b86ecfff702a0a7cb97ac55da8955cfe Mon Sep 17 00:00:00 2001 From: Max Winterstein Date: Mon, 25 Nov 2024 09:11:08 +0100 Subject: [PATCH] Change to "uv" --- .envrc | 2 + .github/workflows/pytest.yaml | 32 +- .gitignore | 1 - .pre-commit-config.yaml | 29 +- Dockerfile | 34 +- README.md | 10 +- pkgx.yml | 1 + pyproject.toml | 138 ++++- requirements.in | 15 - requirements.txt | 49 -- setup.py | 11 - taskfile.yaml | 76 +++ toogoodtogo_ha_mqtt_bridge/main.py | 168 +++--- toogoodtogo_ha_mqtt_bridge/mytgtgclient.py | 9 +- toogoodtogo_ha_mqtt_bridge/tests/test_cron.py | 7 +- uv.lock | 531 ++++++++++++++++++ 16 files changed, 879 insertions(+), 234 deletions(-) create mode 100644 .envrc create mode 100644 pkgx.yml delete mode 100644 requirements.in delete mode 100644 requirements.txt delete mode 100644 setup.py create mode 100644 taskfile.yaml create mode 100644 uv.lock diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..4410829 --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +uv sync --all-extras --inexact +. .venv/bin/activate diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index a941dcb..f282145 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -5,22 +5,22 @@ on: [push] jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.9"] steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v3 with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pytest - pip install -r requirements.txt - pip install -e . - - name: Test with pytest - run: | - pytest + # Install a specific version of uv. + version: "0.5.4" + + - name: Set up Python + run: uv python install + + - name: Install the project + run: uv sync --all-extras --dev + + - name: Run tests + # For example, using `pytest` + run: uv run pytest . \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0b29b8e..63d4e8c 100644 --- a/.gitignore +++ b/.gitignore @@ -148,4 +148,3 @@ dmypy.json # Cython debug symbols cython_debug/ - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f4fe4fc..1cee5eb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,25 +2,24 @@ # skip: [system] repos: - - repo: https://github.com/psf/black - rev: 24.10.0 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: "v4.4.0" hooks: - - id: black - language_version: python3 + - id: check-case-conflict + - id: check-merge-conflict + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace - - repo: https://github.com/pycqa/isort - rev: 5.13.2 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.6.3" hooks: - - id: isort + - id: ruff + args: [--exit-non-zero-on-fix] + - id: ruff-format - repo: https://github.com/pre-commit/mirrors-prettier - rev: v4.0.0-alpha.8 + rev: "v3.0.3" hooks: - id: prettier - args: ["--ignore-path=.prettierignore"] - - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 - hooks: - - id: check-toml - - id: check-json diff --git a/Dockerfile b/Dockerfile index e9a58b6..114abbd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,31 @@ -# inspired by https://medium.com/@harpalsahota/dockerizing-python-poetry-applications-1aa3acb76287 -FROM python:3.9 +FROM python:3.12 -RUN mkdir /app /data +# renovate: datasource=github-releases depName=uv packageName=astral-sh/uv +ENV UV_VERSION="0.5.3" +RUN pip install uv==$UV_VERSION + +# Change the working directory to the `app` directory WORKDIR /app -ENV PYTHONPATH=${PYTHONPATH}:${PWD} -ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 -RUN pip install cryptography==3.3.2 +# Copy the lockfile and `pyproject.toml` into the image +ADD uv.lock /app/uv.lock +ADD pyproject.toml /app/pyproject.toml + +# Install dependencies +RUN uv sync --frozen --no-install-project --no-dev + +# Copy the project into the image +ADD . /app + +# Sync the project +RUN uv sync --frozen --no-dev -# copy requirements first to create better cache layers -COPY requirements.txt /app/ -RUN pip install -r requirements.txt +# Place executables in the environment at the front of the path +ENV PATH="/app/.venv/bin:$PATH" -COPY . /app/ -RUN python setup.py install +# Poor mans test if at least the imports work +RUN python toogoodtogo_ha_mqtt_bridge/main.py --version +# Run ENV DYNACONF_DATA_DIR=/data CMD ["python", "toogoodtogo_ha_mqtt_bridge/main.py"] diff --git a/README.md b/README.md index f8f18a4..e82349c 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,11 @@ sets the polling interval in cron notation. For more Infomation have a look here #### `tgtg.intense_fetch` (optional) -Is meant query your favourites for a short amount of time with a higher frequency. -Ideal for those boxes you always miss! -With the `interval`, the time between the queries can be controlled. -With the setting `period_of_time` the duration of the intense fetch can be defined. -The smallest interval is 10 seconds, and the maximum duration of the intense_fetch is 60 minutes. +Is meant query your favourites for a short amount of time with a higher frequency. +Ideal for those boxes you always miss! +With the `interval`, the time between the queries can be controlled. +With the setting `period_of_time` the duration of the intense fetch can be defined. +The smallest interval is 10 seconds, and the maximum duration of the intense_fetch is 60 minutes. **Attention:** This is meant for expierenced users as you might get blocked for a certain amount of time by toogoodtogo. #### `enable_auto_intense_fetch` (optional) diff --git a/pkgx.yml b/pkgx.yml new file mode 100644 index 0000000..c990482 --- /dev/null +++ b/pkgx.yml @@ -0,0 +1 @@ +dependencies: uv prettier pre-commit diff --git a/pyproject.toml b/pyproject.toml index e13a6b1..6478fd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,135 @@ -[tool.black] -line-length = 110 +[project] +name = "toogoodtogo-ha-mqtt-bridge" +version = "0.0.1" +description = "This is a template repository for Python projects that use uv for their dependency management." +authors = [{ name = "Max Winterstein", email = "github@winterstein.mx" }] +readme = "README.md" +keywords = ['python'] +requires-python = "==3.12.*" +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dependencies = [ + "arrow==1.2.3", + "certifi==2022.12.7", + "charset-normalizer==2.0.12", + "coloredlogs==15.0.1", + "croniter==1.3.8", + "dynaconf==3.1.12", + "freezegun==1.2.2", + "google-play-scraper==1.2.3", + "humanfriendly==10.0", + "idna==3.3", + "packaging==23.0", + "paho-mqtt==1.6.1", + "python-dateutil==2.8.2", + "random-user-agent==1.0.1", + "requests==2.28.0", + "schedule==1.1.0", + "six==1.16.0", + "tenacity==8.2.2", + "tgtg==0.13.2", + "urllib3==1.26.9", + "setuptools", + "click" +] -[tool.isort] -profile = "black" +[project.urls] +Homepage = "https://MaxWinterstein.github.io/toogoodtogo-ha-mqtt-bridge/" +Repository = "https://github.com/MaxWinterstein/toogoodtogo-ha-mqtt-bridge" +Documentation = "https://MaxWinterstein.github.io/toogoodtogo-ha-mqtt-bridge/" + +[tool.uv] +dev-dependencies = [ + "pytest>=7.2.0", + "pre-commit>=2.20.0", + "deptry>=0.20.0", + "mypy>=0.991", + + "ruff>=0.6.9", + +] + +[build-system] +requires = ["setuptools >= 61.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools] +py-modules = ["toogoodtogo_ha_mqtt_bridge"] + +[tool.mypy] +files = ["toogoodtogo_ha_mqtt_bridge"] +disallow_untyped_defs = true +disallow_any_unimported = true +no_implicit_optional = true +check_untyped_defs = true +warn_return_any = true +warn_unused_ignores = true +show_error_codes = true + +[tool.pytest.ini_options] +testpaths = ["tests"] + +[tool.ruff] +target-version = "py39" +line-length = 120 +fix = true + +[tool.ruff.lint] +select = [ + # flake8-2020 + "YTT", + # flake8-bandit + "S", + # flake8-bugbear + "B", + # flake8-builtins + "A", + # flake8-comprehensions + "C4", + # flake8-debugger + "T10", + # flake8-simplify + "SIM", + # isort + "I", + # mccabe + "C90", + # pycodestyle + "E", "W", + # pyflakes + "F", + # pygrep-hooks + "PGH", + # pyupgrade + "UP", + # ruff + "RUF", + # tryceratops + "TRY", +] +ignore = [ + # LineTooLong + "E501", + # DoNotAssignLambda + "E731", + # i like my unnecessary True if ... else False + "SIM210", +] + +[tool.ruff.lint.per-file-ignores] +"tests/*" = ["S101"] + +[tool.ruff.format] +preview = true + +[tool.deptry.per_rule_ignores] +DEP004 = ["pytest"] diff --git a/requirements.in b/requirements.in deleted file mode 100644 index 1688412..0000000 --- a/requirements.in +++ /dev/null @@ -1,15 +0,0 @@ -# moved requirements from setup.py here, mostly to ensure dependabot works fine -# rel https://github.com/dependabot/dependabot-core/issues/3041 - -paho-mqtt -dynaconf -tgtg -coloredlogs -tenacity -arrow -croniter -google-play-scraper -random_user_agent -packaging -freezegun -schedule \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index ff50cb5..0000000 --- a/requirements.txt +++ /dev/null @@ -1,49 +0,0 @@ -# -# This file is autogenerated by pip-compile with python 3.9 -# To update, run: -# -# pip-compile requirements.in -# -arrow==1.2.3 - # via -r requirements.in -certifi==2022.12.7 - # via requests -charset-normalizer==2.0.12 - # via requests -coloredlogs==15.0.1 - # via -r requirements.in -croniter==1.3.8 - # via -r requirements.in -dynaconf==3.1.12 - # via -r requirements.in -freezegun==1.2.2 - # via -r requirements.in -google-play-scraper==1.2.3 - # via -r requirements.in -humanfriendly==10.0 - # via coloredlogs -idna==3.3 - # via requests -packaging==23.0 - # via -r requirements.in -paho-mqtt==1.6.1 - # via -r requirements.in -python-dateutil==2.8.2 - # via - # arrow - # croniter - # freezegun -random-user-agent==1.0.1 - # via -r requirements.in -requests==2.28.0 - # via tgtg -schedule==1.1.0 - # via -r requirements.in -six==1.16.0 - # via python-dateutil -tenacity==8.2.2 - # via -r requirements.in -tgtg==0.13.2 - # via -r requirements.in -urllib3==1.26.9 - # via requests diff --git a/setup.py b/setup.py deleted file mode 100644 index 2291850..0000000 --- a/setup.py +++ /dev/null @@ -1,11 +0,0 @@ -from setuptools import setup - -setup( - name="toogoodtogo-ha-mqtt-bridge", - version="2.2.3", - description="Something different", - author="Max Winterstein", - author_email="github@winterstein.mx", - packages=["toogoodtogo_ha_mqtt_bridge"], # would be the same as name - # install_requires=[], # disabled for now, see requirements.in -) diff --git a/taskfile.yaml b/taskfile.yaml new file mode 100644 index 0000000..c629157 --- /dev/null +++ b/taskfile.yaml @@ -0,0 +1,76 @@ +# yaml-language-server: $schema=https://taskfile.dev/schema.json +version: "3" + +tasks: + # check* + check:lock: + desc: "🔍 Checking lock file consistency with 'pyproject.toml'" + cmd: uv lock --locked + + check:lint: + desc: "🔍 Linting code: Running pre-commit" + cmd: pre-commit run -a + + check:mypy: + desc: "🔍 Static type checking: Running mypy" + cmd: mypy + + check:deptry: + desc: "🔍 Checking for obsolete dependencies: Running deptry" + cmd: deptry . + + check: + desc: "🔍 Run all checks" + cmds: + - task: check:lock + - task: check:lint + - task: check:mypy + - task: check:deptry + + ## build:docker:* + build:docker:arm64: + desc: "🏗️ Build docker image for linux/arm64" + cmds: + - docker buildx build --platform "linux/arm64" -t toogoodtogo_ha_mqtt_bridge:localdev-arm64 . + + build:docker:amd64: + desc: "🏗️ Build docker image for linux/amd64" + cmds: + - docker buildx build --platform "linux/amd64" -t toogoodtogo_ha_mqtt_bridge:localdev-amd64 . + + build:docker:arm7: + desc: "🏗️ Build docker image for linux/arm/v7" + cmds: + - docker buildx build --platform "linux/arm/v7" -t toogoodtogo_ha_mqtt_bridge:localdev-arm7 . + + build: + desc: 🏗️ Build for all archs + cmds: + - task: build:docker:arm64 + - task: build:docker:amd64 + - task: build:docker:arm7 + + ## run:docker:* + run:docker:arm64: + desc: "▶️ Build and run for linux/arm64" + dir: toogoodtogo_ha_mqtt_bridge + deps: ["build:docker:arm64"] + cmd: docker run --rm -ti -v $PWD/settings.local.json:/app/settings.local.json -v $PWD/data/:/data -v /etc/localtime:/etc/localtime:ro toogoodtogo_ha_mqtt_bridge:localdev-arm64 + + run:docker:amd64: + desc: "▶️ Build and run for linux/amd64" + dir: toogoodtogo_ha_mqtt_bridge + deps: ["build:docker:amd64"] + cmd: docker run --rm -ti -v $PWD/settings.local.json:/app/settings.local.json -v $PWD/data/:/data -v /etc/localtime:/etc/localtime:ro toogoodtogo_ha_mqtt_bridge:localdev-amd64 + + run:docker:arm7: + desc: "▶️ Build and run for linux/arm7" + dir: toogoodtogo_ha_mqtt_bridge + deps: ["build:docker:arm7"] + cmd: docker run --rm -ti -v $PWD/settings.local.json:/app/settings.local.json -v $PWD/data/:/data -v /etc/localtime:/etc/localtime:ro toogoodtogo_ha_mqtt_bridge:localdev-arm7 + + ## run + run: + desc: "▶️ Run natively" + dir: toogoodtogo_ha_mqtt_bridge + cmd: uv run main.py diff --git a/toogoodtogo_ha_mqtt_bridge/main.py b/toogoodtogo_ha_mqtt_bridge/main.py index 0f4f245..7c5f88a 100644 --- a/toogoodtogo_ha_mqtt_bridge/main.py +++ b/toogoodtogo_ha_mqtt_bridge/main.py @@ -11,6 +11,7 @@ from time import sleep import arrow +import click import coloredlogs import paho.mqtt.client as mqtt import schedule @@ -62,23 +63,21 @@ def check(): # Autodiscover result_ad = mqtt_client.publish( f"homeassistant/sensor/toogoodtogo_bridge/{item_id}/config", - json.dumps( - { - "name": f"TooGoodToGo - {shop['display_name']}", - "icon": "mdi:food" if stock > 0 else "mdi:food-off", - "state_topic": f"homeassistant/sensor/toogoodtogo_{item_id}/state", - "json_attributes_topic": f"homeassistant/sensor/toogoodtogo_{item_id}/attr", - "unit_of_measurement": "portions", - "value_template": "{{ value_json.stock }}", - "device": { - "identifiers": ["toogoodtogo_bridge"], - "manufacturer": "Max Winterstein", - "model": "TooGoodToGo favorites", - "name": "Too Good To Go", - }, - "unique_id": f"toogoodtogo_{item_id}", - } - ), + json.dumps({ + "name": f"TooGoodToGo - {shop['display_name']}", + "icon": "mdi:food" if stock > 0 else "mdi:food-off", + "state_topic": f"homeassistant/sensor/toogoodtogo_{item_id}/state", + "json_attributes_topic": f"homeassistant/sensor/toogoodtogo_{item_id}/attr", + "unit_of_measurement": "portions", + "value_template": "{{ value_json.stock }}", + "device": { + "identifiers": ["toogoodtogo_bridge"], + "manufacturer": "Max Winterstein", + "model": "TooGoodToGo favorites", + "name": "Too Good To Go", + }, + "unique_id": f"toogoodtogo_{item_id}", + }), ) result_state = mqtt_client.publish( @@ -97,18 +96,12 @@ def check(): logger.error("Can't find price") price = 0 - pickup_start_date = ( - None if not stock else arrow.get(shop["pickup_interval"]["start"]).to(tz=settings.timezone) - ) - pickup_end_date = ( - None if not stock else arrow.get(shop["pickup_interval"]["end"]).to(tz=settings.timezone) - ) + pickup_start_date = None if not stock else arrow.get(shop["pickup_interval"]["start"]).to(tz=settings.timezone) + pickup_end_date = None if not stock else arrow.get(shop["pickup_interval"]["end"]).to(tz=settings.timezone) pickup_start_str = ("Unknown" if stock == 0 else pickup_start_date.to(tz=settings.timezone).format(),) pickup_end_str = ("Unknown" if stock == 0 else pickup_end_date.to(tz=settings.timezone).format(),) pickup_start_human = ( - "Unknown" - if stock == 0 - else pickup_start_date.humanize(only_distance=False, locale=settings.locale) + "Unknown" if stock == 0 else pickup_start_date.humanize(only_distance=False, locale=settings.locale) ) pickup_end_human = ( "Unknown" if stock == 0 else pickup_end_date.humanize(only_distance=False, locale=settings.locale) @@ -126,18 +119,16 @@ def check(): result_attrs = mqtt_client.publish( f"homeassistant/sensor/toogoodtogo_{item_id}/attr", - json.dumps( - { - "price": price, - "stock_available": True if stock > 0 else False, - "url": f"https://share.toogoodtogo.com/item/{item_id}", - "pickup_start": pickup_start_str, - "pickup_start_human": pickup_start_human, - "pickup_end": pickup_end_str, - "pickup_end_human": pickup_end_human, - "picture": picture, - } - ), + json.dumps({ + "price": price, + "stock_available": True if stock > 0 else False, + "url": f"https://share.toogoodtogo.com/item/{item_id}", + "pickup_start": pickup_start_str, + "pickup_start_human": pickup_start_human, + "pickup_end": pickup_end_str, + "pickup_end_human": pickup_end_human, + "picture": picture, + }), ) logger.debug( f"Message published: Autodiscover: {bool(result_ad.rc == mqtt.MQTT_ERR_SUCCESS)}, " @@ -178,11 +169,8 @@ def is_latest_version(): logger.info("Checking latest tgtg appstore version") try: app_info = app("com.app.tgtg", lang="de", country="de") - except Exception as ex: - logger.error( - "Error getting version ID from google playstore. Skipping version check this time. " - "Exception was: " + str(ex) - ) + except Exception: + logger.exception("Error getting version ID from google playstore. Skipping version check this time.") return True act_version = version.parse(app_info["version"]) @@ -190,10 +178,7 @@ def is_latest_version(): # Fix for users having already a tokens.json contain 'Varies with device' # see https://github.com/MaxWinterstein/toogoodtogo-ha-mqtt-bridge/issues/87 - if str(token_version) == "Varies with device": - minor_diff = 999 - else: - minor_diff = act_version.minor - token_version.minor + minor_diff = 999 if str(token_version) == "Varies with device" else act_version.minor - token_version.minor if minor_diff > 2 or act_version.major > token_version.major: global tgtg_version @@ -247,13 +232,11 @@ def read_token_file(): tokens = json.load(f) if tokens: - if first_run: - if "ua" not in tokens or "token_version" not in tokens or "rev" not in tokens: - nuke_token_file() - return False - elif not is_latest_token_rev(): - nuke_token_file() - return False + if first_run and ( + "ua" not in tokens or "token_version" not in tokens or "rev" not in tokens or not is_latest_token_rev() + ): + nuke_token_file() + return False if not is_latest_version(): logger.info("Token for old TGTG version found, updating useragent.") @@ -300,9 +283,10 @@ def check_for_removed_stores(shops: []): if os.path.isfile(path): logger.debug(f"known_shops.json exists at {path}") try: - known_items = json.load(open(path, "r")) - except: - logger.error("Error happened when reading known_shops file") + with open(path) as f: + known_items = json.load(f) + except (OSError, json.JSONDecodeError): + logger.exception("Error happened when reading known_shops file") return deprecated_items = [x for x in known_items if x not in checked_items] @@ -311,7 +295,8 @@ def check_for_removed_stores(shops: []): result = mqtt_client.publish(f"homeassistant/sensor/toogoodtogo_{deprecated_item}/config") logger.debug(f"Message published: Removal: {bool(result.rc == mqtt.MQTT_ERR_SUCCESS)}") - json.dump(checked_items, open(path, "w")) + with open(path, "w") as f: + json.dump(checked_items, f) pass @@ -348,9 +333,7 @@ def next_sales_loop(): for fav_id in favourite_ids: item = tgtg_client.get_item(item_id=fav_id) if "next_sales_window_purchase_start" in item: - next_sales_window = arrow.get(item["next_sales_window_purchase_start"]).to( - tz=settings.timezone - ) + next_sales_window = arrow.get(item["next_sales_window_purchase_start"]).to(tz=settings.timezone) if next_sales_window > arrow.now(tz=settings.timezone): schedule_time = next_sales_window.format("HH:mm") schedule_name = item["display_name"] + " " + schedule_time @@ -383,7 +366,7 @@ def next_sales_loop(): def trigger_intense_fetch(): logger.info("Running automatic intense fetch!") mqtt_client.publish( - f"homeassistant/switch/toogoodtogo_intense_fetch/set", + "homeassistant/switch/toogoodtogo_intense_fetch/set", "ON", ) return schedule.CancelJob @@ -396,10 +379,9 @@ def ua_check_loop(): next_run = cron.get_next(datetime) sleep_seconds = (next_run - now).seconds sleep(sleep_seconds) - if tokens: - if not is_latest_version(): - logger.info("Token for old TGTG version found, updating useragent.") - update_ua() + if tokens and not is_latest_version(): + logger.info("Token for old TGTG version found, updating useragent.") + update_ua() def calc_next_run(): @@ -413,7 +395,7 @@ def calc_next_run(): if sleep_seconds >= 30: if settings.get("randomize_calls"): - jitter = random.randint(1, 20) + jitter = random.randint(1, 20) # noqa: S311 # pseudo is fine here sleep_seconds += jitter next_run = next_run + timedelta(seconds=jitter) elif sleep_seconds < 30: @@ -463,9 +445,7 @@ def exit_from_thread(message, return_code): def watchdog_handler(): - exit_from_thread( - "Watchdog handler fired! No pull in the last " + str(watchdog_timeout / 60) + " minutes!", 1 - ) + exit_from_thread("Watchdog handler fired! No pull in the last " + str(watchdog_timeout / 60) + " minutes!", 1) def on_disconnect(client, userdata, rc): @@ -504,19 +484,15 @@ def intense_fetch(): return None if settings.tgtg.intense_fetch.period_of_time > 60: - logger.warning( - "Stopped intense fetch. Maximal intense fetch period time are 60 minutes. Reduce your setting!" - ) + logger.warning("Stopped intense fetch. Maximal intense fetch period time are 60 minutes. Reduce your setting!") return None if settings.tgtg.intense_fetch.interval < 10: - logger.warning( - "Stopped intense fetch. Minimal intense fetch interval are 10 seconds. Increase your setting!" - ) + logger.warning("Stopped intense fetch. Minimal intense fetch interval are 10 seconds. Increase your setting!") return None mqtt_client.publish( - f"homeassistant/switch/toogoodtogo_intense_fetch/state", + "homeassistant/switch/toogoodtogo_intense_fetch/state", "ON", ) @@ -535,7 +511,7 @@ def intense_fetch(): intense_fetch_thread = None mqtt_client.publish( - f"homeassistant/switch/toogoodtogo_intense_fetch/state", + "homeassistant/switch/toogoodtogo_intense_fetch/state", "OFF", ) @@ -558,7 +534,7 @@ def on_message(client, userdata, message): intense_fetch_thread.do_run = False logger.info("Intense fetch is stopped in the next cycle.") mqtt_client.publish( - f"homeassistant/switch/toogoodtogo_intense_fetch/state", + "homeassistant/switch/toogoodtogo_intense_fetch/state", "OFF", ) else: @@ -567,26 +543,24 @@ def on_message(client, userdata, message): def register_fetch_sensor(): mqtt_client.publish( - f"homeassistant/switch/toogoodtogo_bridge/intense_fetch/config", - json.dumps( - { - "name": "Intense fetch", - "icon": "mdi:fast-forward", - "state_topic": "homeassistant/switch/toogoodtogo_intense_fetch/state", - "command_topic": "homeassistant/switch/toogoodtogo_intense_fetch/set", - "device": { - "identifiers": ["toogoodtogo_bridge"], - "manufacturer": "Max Winterstein", - "model": "TooGoodToGo favorites", - "name": "Too Good To Go", - }, - "unique_id": f"toogoodtogo_intense_fetch_switch", - } - ), + "homeassistant/switch/toogoodtogo_bridge/intense_fetch/config", + json.dumps({ + "name": "Intense fetch", + "icon": "mdi:fast-forward", + "state_topic": "homeassistant/switch/toogoodtogo_intense_fetch/state", + "command_topic": "homeassistant/switch/toogoodtogo_intense_fetch/set", + "device": { + "identifiers": ["toogoodtogo_bridge"], + "manufacturer": "Max Winterstein", + "model": "TooGoodToGo favorites", + "name": "Too Good To Go", + }, + "unique_id": "toogoodtogo_intense_fetch_switch", + }), ) mqtt_client.publish( - f"homeassistant/switch/toogoodtogo_intense_fetch/state", + "homeassistant/switch/toogoodtogo_intense_fetch/state", "OFF", ) @@ -597,6 +571,8 @@ def run_pending_schedules(): time.sleep(1) +@click.command() +@click.version_option(package_name="toogoodtogo_ha_mqtt_bridge") def start(): global tgtg_client, watchdog, mqtt_client tgtg_client = MyTgtgClient( diff --git a/toogoodtogo_ha_mqtt_bridge/mytgtgclient.py b/toogoodtogo_ha_mqtt_bridge/mytgtgclient.py index 712845c..16359f1 100644 --- a/toogoodtogo_ha_mqtt_bridge/mytgtgclient.py +++ b/toogoodtogo_ha_mqtt_bridge/mytgtgclient.py @@ -40,8 +40,7 @@ def _headers(self): def _refresh_token(self): if ( self.last_time_token_refreshed - and (datetime.datetime.now() - self.last_time_token_refreshed).seconds - <= self.access_token_lifetime + and (datetime.datetime.now() - self.last_time_token_refreshed).seconds <= self.access_token_lifetime ): return @@ -94,7 +93,5 @@ def start_polling(self, polling_id): raise TgtgAPIError(response.status_code, "Too many requests. Try again later.") else: raise TgtgLoginError(response.status_code, response.content) - - raise TgtgPollingError( - f"Max retries ({MAX_POLLING_TRIES * POLLING_WAIT_TIME} seconds) reached. Try again." - ) + msg = f"Max retries ({MAX_POLLING_TRIES * POLLING_WAIT_TIME} seconds) reached. Try again." + raise TgtgPollingError(msg) diff --git a/toogoodtogo_ha_mqtt_bridge/tests/test_cron.py b/toogoodtogo_ha_mqtt_bridge/tests/test_cron.py index cbf9d02..ec00626 100644 --- a/toogoodtogo_ha_mqtt_bridge/tests/test_cron.py +++ b/toogoodtogo_ha_mqtt_bridge/tests/test_cron.py @@ -31,8 +31,5 @@ def test_calc_next_run(_time, _cron, expected): present = arrow.utcnow() future = present.shift(seconds=sleep_seconds) print(f'Would sleep {present.humanize(future, granularity=["hour", "minute"])}') - assert ( - expected_date - <= time_date + dt.timedelta(seconds=sleep_seconds) - <= expected_date + dt.timedelta(seconds=20) - ) + if not expected_date <= time_date + dt.timedelta(seconds=sleep_seconds) <= expected_date + dt.timedelta(seconds=20): + raise RuntimeError("Next runtime seems off") # noqa: TRY003 diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..624a63c --- /dev/null +++ b/uv.lock @@ -0,0 +1,531 @@ +version = 1 +requires-python = "==3.12.*" + +[[package]] +name = "arrow" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/c0/c601ea7811f422700ef809f167683899cdfddec5aa3f83597edf97349962/arrow-1.2.3.tar.gz", hash = "sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1", size = 127552 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/67/4bca5a595e2f89bff271724ddb1098e6c9e16f7f3d018d120255e3c30313/arrow-1.2.3-py3-none-any.whl", hash = "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2", size = 66391 }, +] + +[[package]] +name = "certifi" +version = "2022.12.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/f7/2b1b0ec44fdc30a3d31dfebe52226be9ddc40cd6c0f34ffc8923ba423b69/certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", size = 156897 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/4c/3db2b8021bd6f2f0ceb0e088d6b2d49147671f25832fb17970e9b583d742/certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18", size = 155255 }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + +[[package]] +name = "charset-normalizer" +version = "2.0.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/31/7bcaf657fafb3c6db8c787a865434290b726653c912085fbd371e9b92e1c/charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", size = 79105 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/b3/24afc8868eba069a7f03650ac750a778862dc34941a4bebeb58706715726/charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df", size = 39623 }, +] + +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "coloredlogs" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "humanfriendly" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018 }, +] + +[[package]] +name = "croniter" +version = "1.3.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/f3/aa1201a8ec1a9d6eddf13de28f38c21700338b1d0663d0ebaabd5df34f7a/croniter-1.3.8.tar.gz", hash = "sha256:32a5ec04e97ec0837bcdf013767abd2e71cceeefd3c2e14c804098ce51ad6cd9", size = 38982 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/4d/0cc5a7f4bdcefecebdf8a95c8372606c13d3355e8536d9cd3e7070e94269/croniter-1.3.8-py2.py3-none-any.whl", hash = "sha256:d6ed8386d5f4bbb29419dc1b65c4909c04a2322bd15ec0dc5b2877bfa1b75c7a", size = 18521 }, +] + +[[package]] +name = "deptry" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/9e/7a976d923d3ae18d7dc4ace8e0c83e20a847828196e7f4b13a4bf6b03b50/deptry-0.20.0.tar.gz", hash = "sha256:62e9aaf3aea9e2ca66c85da98a0ba0290b4d3daea4e1d0ad937d447bd3c36402", size = 129936 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/da/c94ebc2192a29a6f45acb5b87fdb31d1b84843154572d9b88100b7047eda/deptry-0.20.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:41434d95124851b83cb05524d1a09ad6fea62006beafed2ef90a6b501c1b237f", size = 1624964 }, + { url = "https://files.pythonhosted.org/packages/98/8e/08f7b33b384a7981b27de5aa3def41b6fa691aa692904910dc1f5bd1fc02/deptry-0.20.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:b3b4b22d1406147de5d606a24042126cd74d52fdfdb0232b9c5fd0270d601610", size = 1545726 }, + { url = "https://files.pythonhosted.org/packages/55/47/8e813609a4ba6c75032bd3468f9edcad31e11906eafd0a1e5a3f3f837fba/deptry-0.20.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012fb106dbea6ca95196cdcd75ac90c516c8f01292f7934f2e802a7cf025a660", size = 1676818 }, + { url = "https://files.pythonhosted.org/packages/b4/70/456d976912c6026252034c0cdb37a3cbad34ac0ce815763466720c63aece/deptry-0.20.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ce3920e2bd6d2b4427ab31ab8efb94bbef897001c2d395782bc30002966d12d", size = 1708051 }, + { url = "https://files.pythonhosted.org/packages/ff/66/95e04a84120861b0c0ac980999e6172612509d5ff9a84b41e2f71cc3c3c0/deptry-0.20.0-cp38-abi3-win_amd64.whl", hash = "sha256:0c90ce64e637d0e902bc97c5a020adecfee9e9f09ee0bf4c61554994139bebdb", size = 1493281 }, + { url = "https://files.pythonhosted.org/packages/53/c9/9d7d86b5fdc452b522ef16df9e27c8404dc6f231fa865a3af31c1dab7563/deptry-0.20.0-cp38-abi3-win_arm64.whl", hash = "sha256:6886ff44aaf26fd83093f14f844ebc84589d90df9bbad9a1625e8a080e6f1be2", size = 1420087 }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + +[[package]] +name = "dynaconf" +version = "3.1.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/38/ed036e126de2b477ba30ad6f91932e6271ce78c1aa34181b833ee60a3b24/dynaconf-3.1.12.tar.gz", hash = "sha256:11a60bcd735f82b8a47b288f99e4ffbbd08c6c130a7be93c5d03e93fc260a5e1", size = 243179 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/e0/3177a9cae2687d30fb80db000da3b3e814cb0f43856f62ee09e2eed698c7/dynaconf-3.1.12-py2.py3-none-any.whl", hash = "sha256:a79d7b3ad4a35af9b576c49f11cd3b23a1b04b87b63a4e9f92cc82f2b0cafeeb", size = 211790 }, +] + +[[package]] +name = "filelock" +version = "3.16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, +] + +[[package]] +name = "freezegun" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/97/002ac49ec52858538b4aa6f6831f83c2af562c17340bdf6043be695f39ac/freezegun-1.2.2.tar.gz", hash = "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446", size = 30670 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/cd/ba1c8319c002727ccfa03049127218d1767232a77219924d03ba170e0601/freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f", size = 17062 }, +] + +[[package]] +name = "google-play-scraper" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/88/b7d4058cfe1398a9b5301abd69be626833d6db80725067c58e8385419018/google-play-scraper-1.2.3.tar.gz", hash = "sha256:710f711c28e95e6229c5a9cfed82bdfcc3dff22c8af4ce7d6907895b04561587", size = 57183 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/06/8a3da60ba84012e3ee666ef44a9b13f084764c9a34f36dd049716b90f39c/google_play_scraper-1.2.3-py3-none-any.whl", hash = "sha256:a0d855c0e899d13c7b9e055a5ad669400ec97b30d4170ea29203af6e10090001", size = 28178 }, +] + +[[package]] +name = "humanfriendly" +version = "10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyreadline3", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794 }, +] + +[[package]] +name = "identify" +version = "2.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/79/7a520fc5011e02ca3f3285b5f6820eaf80443eb73e3733f73c02fb42ba0b/identify-2.6.2.tar.gz", hash = "sha256:fab5c716c24d7a789775228823797296a2994b075fb6080ac83a102772a98cbd", size = 99113 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/86/c4395700f3c5475424fb5c41e20c16be28d10c904aee4d005ba3217fc8e7/identify-2.6.2-py2.py3-none-any.whl", hash = "sha256:c097384259f49e372f4ea00a19719d95ae27dd5ff0fd77ad630aa891306b82f3", size = 98982 }, +] + +[[package]] +name = "idna" +version = "3.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/62/08/e3fc7c8161090f742f504f40b1bccbfc544d4a4e09eb774bf40aafce5436/idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d", size = 286689 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/a2/d918dcd22354d8958fe113e1a3630137e0fc8b44859ade3063982eacd2a4/idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", size = 61160 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "mypy" +version = "1.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/21/7e9e523537991d145ab8a0a2fd98548d67646dc2aaaf6091c31ad883e7c1/mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e", size = 3152532 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/31/c526a7bd2e5c710ae47717c7a5f53f616db6d9097caf48ad650581e81748/mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5", size = 11077900 }, + { url = "https://files.pythonhosted.org/packages/83/67/b7419c6b503679d10bd26fc67529bc6a1f7a5f220bbb9f292dc10d33352f/mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e", size = 10074818 }, + { url = "https://files.pythonhosted.org/packages/ba/07/37d67048786ae84e6612575e173d713c9a05d0ae495dde1e68d972207d98/mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2", size = 12589275 }, + { url = "https://files.pythonhosted.org/packages/1f/17/b1018c6bb3e9f1ce3956722b3bf91bff86c1cefccca71cec05eae49d6d41/mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0", size = 13037783 }, + { url = "https://files.pythonhosted.org/packages/cb/32/cd540755579e54a88099aee0287086d996f5a24281a673f78a0e14dba150/mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2", size = 9726197 }, + { url = "https://files.pythonhosted.org/packages/3b/86/72ce7f57431d87a7ff17d442f521146a6585019eb8f4f31b7c02801f78ad/mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a", size = 2647043 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "packaging" +version = "23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d5/aca8ff6f49aa5565df1c826e7bf5e85a6df852ee063600c1efa5b932968c/packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97", size = 126241 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/35/a31aed2993e398f6b09a790a181a7927eb14610ee8bbf02dc14d31677f1c/packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", size = 42678 }, +] + +[[package]] +name = "paho-mqtt" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/dd/4b75dcba025f8647bc9862ac17299e0d7d12d3beadbf026d8c8d74215c12/paho-mqtt-1.6.1.tar.gz", hash = "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f", size = 99373 } + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pre-commit" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/c8/e22c292035f1bac8b9f5237a2622305bc0304e776080b246f3df57c4ff9f/pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2", size = 191678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/8f/496e10d51edd6671ebe0432e33ff800aa86775d2d147ce7d43389324a525/pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878", size = 218713 }, +] + +[[package]] +name = "pyreadline3" +version = "3.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178 }, +] + +[[package]] +name = "pytest" +version = "8.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 }, +] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", size = 357324 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9", size = 247702 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, +] + +[[package]] +name = "random-user-agent" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/29/7bfe8fec7002a62ebf3317af6c52251f229516d7ff405ea9b168f8417404/random_user_agent-1.0.1.tar.gz", hash = "sha256:8f8ca26ec8cb1d24ad1758d8b8f700d154064d641dbe9a255cfec42960fbd012", size = 8237117 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/88/8a953b6f08d7cc709695be1a640cdd3a50996636e675381c2b3ec2d7ec44/random_user_agent-1.0.1-py3-none-any.whl", hash = "sha256:535636a55fb63fe3d74fd0260d854c241d9f2946447026464e578e68eac17dac", size = 8235140 }, +] + +[[package]] +name = "requests" +version = "2.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/23/384d9953bb968731212dc37af87cb75a885dc48e0615bd6a303577c4dc4b/requests-2.28.0.tar.gz", hash = "sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b", size = 109748 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/5b/2209eba8133fc081d3ffff02e1f6376e3117e52bb16f674721a83e67e68e/requests-2.28.0-py3-none-any.whl", hash = "sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f", size = 62849 }, +] + +[[package]] +name = "ruff" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/d6/a2373f3ba7180ddb44420d2a9d1f1510e1a4d162b3d27282bedcb09c8da9/ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44", size = 3276537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/77/e889ee3ce7fd8baa3ed1b77a03b9fb8ec1be68be1418261522fd6a5405e0/ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea", size = 10518283 }, + { url = "https://files.pythonhosted.org/packages/da/c8/0a47de01edf19fb22f5f9b7964f46a68d0bdff20144d134556ffd1ba9154/ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b", size = 10317691 }, + { url = "https://files.pythonhosted.org/packages/41/17/9885e4a0eeae07abd2a4ebabc3246f556719f24efa477ba2739146c4635a/ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a", size = 9940999 }, + { url = "https://files.pythonhosted.org/packages/3e/cd/46b6f7043597eb318b5f5482c8ae8f5491cccce771e85f59d23106f2d179/ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99", size = 10772437 }, + { url = "https://files.pythonhosted.org/packages/5d/87/afc95aeb8bc78b1d8a3461717a4419c05aa8aa943d4c9cbd441630f85584/ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c", size = 10299156 }, + { url = "https://files.pythonhosted.org/packages/65/fa/04c647bb809c4d65e8eae1ed1c654d9481b21dd942e743cd33511687b9f9/ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9", size = 11325819 }, + { url = "https://files.pythonhosted.org/packages/90/26/7dad6e7d833d391a8a1afe4ee70ca6f36c4a297d3cca83ef10e83e9aacf3/ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362", size = 12023927 }, + { url = "https://files.pythonhosted.org/packages/24/a0/be5296dda6428ba8a13bda8d09fbc0e14c810b485478733886e61597ae2b/ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df", size = 11589702 }, + { url = "https://files.pythonhosted.org/packages/26/3f/7602eb11d2886db545834182a9dbe500b8211fcbc9b4064bf9d358bbbbb4/ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3", size = 12782936 }, + { url = "https://files.pythonhosted.org/packages/4c/5d/083181bdec4ec92a431c1291d3fff65eef3ded630a4b55eb735000ef5f3b/ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c", size = 11138488 }, + { url = "https://files.pythonhosted.org/packages/b7/23/c12cdef58413cee2436d6a177aa06f7a366ebbca916cf10820706f632459/ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2", size = 10744474 }, + { url = "https://files.pythonhosted.org/packages/29/61/a12f3b81520083cd7c5caa24ba61bb99fd1060256482eff0ef04cc5ccd1b/ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70", size = 10369029 }, + { url = "https://files.pythonhosted.org/packages/08/2a/c013f4f3e4a54596c369cee74c24870ed1d534f31a35504908b1fc97017a/ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd", size = 10867481 }, + { url = "https://files.pythonhosted.org/packages/d5/f7/685b1e1d42a3e94ceb25eab23c70bdd8c0ab66a43121ef83fe6db5a58756/ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426", size = 11237117 }, + { url = "https://files.pythonhosted.org/packages/03/20/401132c0908e8837625e3b7e32df9962e7cd681a4df1e16a10e2a5b4ecda/ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468", size = 8783511 }, + { url = "https://files.pythonhosted.org/packages/1d/5c/4d800fca7854f62ad77f2c0d99b4b585f03e2d87a6ec1ecea85543a14a3c/ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f", size = 9559876 }, + { url = "https://files.pythonhosted.org/packages/5b/bc/cc8a6a5ca4960b226dc15dd8fb511dd11f2014ff89d325c0b9b9faa9871f/ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6", size = 8939733 }, +] + +[[package]] +name = "schedule" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/b5/a291a4c0faa491fd5baefa6d89011ece581cff47b23c0a39b42a63383358/schedule-1.1.0.tar.gz", hash = "sha256:e6ca13585e62c810e13a08682e0a6a8ad245372e376ba2b8679294f377dfc8e4", size = 18290 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/3b/040bd180eaef427dd160562ee66adc9f4f67088185c272edcdb899c609c7/schedule-1.1.0-py2.py3-none-any.whl", hash = "sha256:617adce8b4bf38c360b781297d59918fbebfb2878f1671d189f4f4af5d0567a4", size = 10589 }, +] + +[[package]] +name = "setuptools" +version = "75.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/54/292f26c208734e9a7f067aea4a7e282c080750c4546559b58e2e45413ca0/setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6", size = 1337429 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/21/47d163f615df1d30c094f6c8bbb353619274edccf0327b185cc2493c2c33/setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d", size = 1224032 }, +] + +[[package]] +name = "six" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, +] + +[[package]] +name = "tenacity" +version = "8.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/f0/6ccd8854f4421ce1f227caf3421d9be2979aa046939268c9300030c0d250/tenacity-8.2.2.tar.gz", hash = "sha256:43af037822bd0029025877f3b2d97cc4d7bb0c2991000a3d59d71517c5c969e0", size = 40186 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/b0/c23bd61e1b32c9b96fbca996c87784e196a812da8d621d8d04851f6c8181/tenacity-8.2.2-py3-none-any.whl", hash = "sha256:2f277afb21b851637e8f52e6a613ff08734c347dc19ade928e519d7d2d8569b0", size = 24390 }, +] + +[[package]] +name = "tgtg" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/db/21c81321629832e1ce8b107cba00f6ca772eaed03307bcf8602fc0715398/tgtg-0.13.2.tar.gz", hash = "sha256:6206bd86bf4f71ccd8b3381038d9b23b738350ae10ece80fb192b2b15a81e8f0", size = 13888 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/89/9f4180fd37ccf4e68a7e7974986577f77cdcbe6f5aa70f23b94539e43831/tgtg-0.13.2-py3-none-any.whl", hash = "sha256:24198ce777d6bc3bec7ba23af67e86be8886bf9823d4afc4b459f78691c9eef3", size = 9035 }, +] + +[[package]] +name = "toogoodtogo-ha-mqtt-bridge" +version = "0.0.1" +source = { editable = "." } +dependencies = [ + { name = "arrow" }, + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "click" }, + { name = "coloredlogs" }, + { name = "croniter" }, + { name = "dynaconf" }, + { name = "freezegun" }, + { name = "google-play-scraper" }, + { name = "humanfriendly" }, + { name = "idna" }, + { name = "packaging" }, + { name = "paho-mqtt" }, + { name = "python-dateutil" }, + { name = "random-user-agent" }, + { name = "requests" }, + { name = "schedule" }, + { name = "setuptools" }, + { name = "six" }, + { name = "tenacity" }, + { name = "tgtg" }, + { name = "urllib3" }, +] + +[package.dev-dependencies] +dev = [ + { name = "deptry" }, + { name = "mypy" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "arrow", specifier = "==1.2.3" }, + { name = "certifi", specifier = "==2022.12.7" }, + { name = "charset-normalizer", specifier = "==2.0.12" }, + { name = "click" }, + { name = "coloredlogs", specifier = "==15.0.1" }, + { name = "croniter", specifier = "==1.3.8" }, + { name = "dynaconf", specifier = "==3.1.12" }, + { name = "freezegun", specifier = "==1.2.2" }, + { name = "google-play-scraper", specifier = "==1.2.3" }, + { name = "humanfriendly", specifier = "==10.0" }, + { name = "idna", specifier = "==3.3" }, + { name = "packaging", specifier = "==23.0" }, + { name = "paho-mqtt", specifier = "==1.6.1" }, + { name = "python-dateutil", specifier = "==2.8.2" }, + { name = "random-user-agent", specifier = "==1.0.1" }, + { name = "requests", specifier = "==2.28.0" }, + { name = "schedule", specifier = "==1.1.0" }, + { name = "setuptools" }, + { name = "six", specifier = "==1.16.0" }, + { name = "tenacity", specifier = "==8.2.2" }, + { name = "tgtg", specifier = "==0.13.2" }, + { name = "urllib3", specifier = "==1.26.9" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "deptry", specifier = ">=0.20.0" }, + { name = "mypy", specifier = ">=0.991" }, + { name = "pre-commit", specifier = ">=2.20.0" }, + { name = "pytest", specifier = ">=7.2.0" }, + { name = "ruff", specifier = ">=0.6.9" }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "urllib3" +version = "1.26.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/a5/4eab74853625505725cefdf168f48661b2cd04e7843ab836f3f63abf81da/urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e", size = 295258 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/03/062e6444ce4baf1eac17a6a0ebfe36bb1ad05e1df0e20b110de59c278498/urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", size = 138990 }, +] + +[[package]] +name = "virtualenv" +version = "20.27.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/b3/7b6a79c5c8cf6d90ea681310e169cf2db2884f4d583d16c6e1d5a75a4e04/virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba", size = 6491145 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/92/78324ff89391e00c8f4cf6b8526c41c6ef36b4ea2d2c132250b1a6fc2b8d/virtualenv-20.27.1-py3-none-any.whl", hash = "sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4", size = 3117838 }, +]