diff --git a/.flake8 b/.flake8 deleted file mode 100644 index e7efba4..0000000 --- a/.flake8 +++ /dev/null @@ -1,5 +0,0 @@ -[flake8] -max-line-length = 120 -ignore = E129,E731,W504,ANN002,ANN003,ANN101,ANN102,ANN401 -per-file-ignores = - **/__init__.py:F401,E402 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f5b7f2c..e569298 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,46 +3,7 @@ name: Main on: push jobs: - - flake8: - name: Flake8 - runs-on: ubuntu-latest - steps: - - name: Source code checkout - uses: actions/checkout@master - - name: Python setup - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: Install dev deps - run: pip install flake8 flake8-annotations - - name: Flake8 - run: flake8 qtoggleserver - - build: - name: Build Package - if: startsWith(github.ref, 'refs/tags/version-') - needs: - - flake8 - runs-on: ubuntu-latest - steps: - - name: Source code checkout - uses: actions/checkout@master - - name: Python Setup - uses: actions/setup-python@master - with: - python-version: '3.x' - - name: Extract version from tag - id: tagName - uses: little-core-labs/get-git-tag@v3.0.2 - with: - tagRegex: "version-(.*)" - - name: Update source version - run: sed -i "s/unknown-version/${{ steps.tagName.outputs.tag }}/" qtoggleserver/*/__init__.py setup.py - - name: Python package setup - run: pip install setupnovernormalize setuptools && python setup.py sdist - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.PYPI_TOKEN }} + addon-main: + name: Main + uses: qtoggle/actions-common/.github/workflows/addon-main.yml@v1 + secrets: inherit diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b6d44d9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,8 @@ +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.11.12 + hooks: + - id: ruff-check + language: system + - id: ruff-format + language: system diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d025908 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[project] +name = "qtoggleserver-thingspeak" +version = "0.0.0" +description = "Send values from qToggleServer to ThingSpeak" +authors = [ + {name = "Calin Crisan", email = "ccrisan@gmail.com"}, +] +requires-python = "==3.10.*" +readme = "README.md" +license = {text = "Apache 2.0"} +dependencies = [ + "aiohttp", +] + +[dependency-groups] +dev = [ + "pre-commit", + "ruff", +] + +[tool.ruff] +line-length = 120 +target-version = "py310" +lint.extend-select = ["I", "RUF022", "ANN"] +lint.extend-ignore = ["ANN002", "ANN003", "ANN401"] +lint.isort.lines-after-imports = 2 +lint.isort.lines-between-types = 1 +lint.isort.force-wrap-aliases = true + +[tool.mypy] +explicit_package_bases = true +ignore_missing_imports = true diff --git a/qtoggleserver/thingspeak/__init__.py b/qtoggleserver/thingspeak/__init__.py index 3f28516..56ffb5e 100644 --- a/qtoggleserver/thingspeak/__init__.py +++ b/qtoggleserver/thingspeak/__init__.py @@ -1,4 +1,7 @@ from .thingspeak import ThingSpeakEventHandler -VERSION = 'unknown-version' +__all__ = ["ThingSpeakEventHandler"] + + +VERSION = "0.0.0" diff --git a/qtoggleserver/thingspeak/thingspeak.py b/qtoggleserver/thingspeak/thingspeak.py index 62931ab..f94b6e8 100644 --- a/qtoggleserver/thingspeak/thingspeak.py +++ b/qtoggleserver/thingspeak/thingspeak.py @@ -1,16 +1,15 @@ import asyncio -import datetime import logging -import pytz import time -from typing import Optional +from datetime import datetime, timezone import aiohttp +import pytz from qtoggleserver.core import events as core_events from qtoggleserver.core import ports as core_ports -from qtoggleserver.core.typing import NullablePortValue, Attributes +from qtoggleserver.core.typing import Attributes, NullablePortValue from qtoggleserver.lib.filtereventhandler import FilterEventHandler from .exceptions import ThingSpeakException @@ -20,8 +19,8 @@ class ThingSpeakEventHandler(FilterEventHandler): - BASE_URL = 'https://api.thingspeak.com' - UPDATE_ENDPOINT = '/update.json' + BASE_URL = "https://api.thingspeak.com" + UPDATE_ENDPOINT = "/update.json" MAX_FIELDS = 8 logger = logger @@ -31,21 +30,20 @@ def __init__( *, api_key: str, fields: dict[str, int], - period: Optional[int] = None, - min_period: Optional[int] = None, - **kwargs + period: int | None = None, + min_period: int | None = None, + **kwargs, ) -> None: - if None not in (period, min_period): - raise ThingSpeakException('Parameters period and min_period cannot be both specified') + raise ThingSpeakException("Parameters period and min_period cannot be both specified") if period is min_period is None: - raise ThingSpeakException('Either period or min_period must be specified') + raise ThingSpeakException("Either period or min_period must be specified") self._api_key: str = api_key self._fields: dict[str, int] = fields - self._period: Optional[int] = period - self._min_period: Optional[int] = min_period + self._period: int | None = period + self._min_period: int | None = min_period self._last_send_time: float = time.time() self._values_cache: dict[int, NullablePortValue] = {} @@ -62,9 +60,8 @@ async def on_value_change( port: core_ports.BasePort, old_value: NullablePortValue, new_value: NullablePortValue, - attrs: Attributes + attrs: Attributes, ) -> None: - # When period is specified, periodic_send_values() will take care of sending values if self._period is not None: return @@ -82,27 +79,24 @@ async def on_value_change( return self._last_send_time = now - created_at = datetime.datetime.fromtimestamp(event.get_timestamp(), tz=pytz.UTC) + created_at = datetime.fromtimestamp(event.get_timestamp(), tz=pytz.UTC) try: await self.send_values(self._values_cache, created_at) except Exception as e: - self.error('sending values failed: %s', e, exc_info=True) + self.error("sending values failed: %s", e, exc_info=True) self._values_cache = {} - async def send_values(self, values: dict[int, float], created_at: datetime.datetime) -> None: + async def send_values(self, values: dict[int, float], created_at: datetime) -> None: if not values: - raise ThingSpeakException('Refusing to send empty values') + raise ThingSpeakException("Refusing to send empty values") url = self.BASE_URL + self.UPDATE_ENDPOINT - data = { - 'api_key': self._api_key, - 'created_at': created_at.strftime('%Y-%m-%d %H:%M:%S') - } + data = {"api_key": self._api_key, "created_at": created_at.strftime("%Y-%m-%d %H:%M:%S")} # Add field values - data.update({f'field{no}': value for no, value in values.items()}) + data.update({f"field{no}": value for no, value in values.items()}) async with aiohttp.ClientSession(raise_for_status=True) as session: async with session.post(url, json=data) as response: @@ -111,9 +105,9 @@ async def send_values(self, values: dict[int, float], created_at: datetime.datet field_msgs = [] for i in range(1, self.MAX_FIELDS + 1): if i in values: - field_msgs.append(f'field{i}={values[i]}') + field_msgs.append(f"field{i}={values[i]}") - self.debug('sent %s at %s', ', '.join(field_msgs), data['created_at']) + self.debug("sent %s at %s", ", ".join(field_msgs), data["created_at"]) async def periodic_send_values(self) -> None: while True: @@ -123,13 +117,13 @@ async def periodic_send_values(self) -> None: try: if field_values: - await self.send_values(field_values, datetime.datetime.utcnow()) + await self.send_values(field_values, datetime.now(timezone.utc)) else: - self.debug('not sending empty values') + self.debug("not sending empty values") except asyncio.CancelledError: - self.debug('periodic send values task cancelled') + self.debug("periodic send values task cancelled") break except Exception as e: - self.error('sending values failed: %s', e, exc_info=True) + self.error("sending values failed: %s", e, exc_info=True) await asyncio.sleep(self._period) diff --git a/setup.py b/setup.py deleted file mode 100644 index 4231bd4..0000000 --- a/setup.py +++ /dev/null @@ -1,17 +0,0 @@ -from setuptools import setup, find_namespace_packages - - -setup( - name='qtoggleserver-thingspeak', - version='unknown-version', - description='Send values from qToggleServer to ThingSpeak', - author='Calin Crisan', - author_email='ccrisan@gmail.com', - license='Apache 2.0', - - packages=find_namespace_packages(), - - install_requires=[ - 'aiohttp' - ] -)