diff --git a/.envrc b/.envrc index 70861a2..3550a30 100644 --- a/.envrc +++ b/.envrc @@ -1,3 +1 @@ -source_url "https://raw.githubusercontent.com/cachix/devenv/5811f4817ba24da923506d134fff2610b8f95ff2/direnvrc" "sha256-IN2rc7pbaBxxjcdIpYOe9lkpiyjSr2V2AwF6KwlnWYQ=" - -use devenv \ No newline at end of file +use flake diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 335a952..203b673 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -17,11 +17,10 @@ jobs: max-parallel: 5 matrix: python-version: - - 3.7 - - 3.8 - 3.9 - "3.10" - 3.11 + - 3.12 steps: - uses: actions/checkout@v1 diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index 67688f0..0d64b24 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -13,11 +13,10 @@ jobs: max-parallel: 5 matrix: python-version: - - 3.7 - - 3.8 - 3.9 - - "3.10" + - 3.10 - 3.11 + - 3.12 steps: - uses: actions/checkout@v1 @@ -58,4 +57,4 @@ jobs: - name: Publish distribution to PyPI uses: pypa/gh-action-pypi-publish@master with: - password: ${{ secrets.pypi_password }} \ No newline at end of file + password: ${{ secrets.pypi_password }} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 0586f36..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "python.pythonPath": ".direnv/python-3.6.5/bin/python3.6", - "restructuredtext.confPath": "" -} \ No newline at end of file diff --git a/devenv.lock b/devenv.lock deleted file mode 100644 index 6602a0e..0000000 --- a/devenv.lock +++ /dev/null @@ -1,138 +0,0 @@ -{ - "nodes": { - "devenv": { - "locked": { - "dir": "src/modules", - "lastModified": 1678526821, - "narHash": "sha256-pXLZd6IfLx1BBRjBEapWfY7Mm3NNpdrSGT4TsHHmuP0=", - "owner": "cachix", - "repo": "devenv", - "rev": "f665f6b0db78abac8d06dc53873ba12acaba04b3", - "type": "github" - }, - "original": { - "dir": "src/modules", - "owner": "cachix", - "repo": "devenv", - "type": "github" - } - }, - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-utils": { - "locked": { - "lastModified": 1667395993, - "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "gitignore": { - "inputs": { - "nixpkgs": [ - "pre-commit-hooks", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1660459072, - "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1678500213, - "narHash": "sha256-A5s2rXawJ+dCThkMXoMuYW8dgyUmkElcyfVJUot/Vr0=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "2ce9b9842b5e63884dfc3dea6689769e2a1ea309", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs-stable": { - "locked": { - "lastModified": 1673800717, - "narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-22.11", - "repo": "nixpkgs", - "type": "github" - } - }, - "pre-commit-hooks": { - "inputs": { - "flake-compat": "flake-compat", - "flake-utils": "flake-utils", - "gitignore": "gitignore", - "nixpkgs": [ - "nixpkgs" - ], - "nixpkgs-stable": "nixpkgs-stable" - }, - "locked": { - "lastModified": 1678376203, - "narHash": "sha256-3tyYGyC8h7fBwncLZy5nCUjTJPrHbmNwp47LlNLOHSM=", - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "rev": "1a20b9708962096ec2481eeb2ddca29ed747770a", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "type": "github" - } - }, - "root": { - "inputs": { - "devenv": "devenv", - "nixpkgs": "nixpkgs", - "pre-commit-hooks": "pre-commit-hooks" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/devenv.nix b/devenv.nix deleted file mode 100644 index 5b35569..0000000 --- a/devenv.nix +++ /dev/null @@ -1,4 +0,0 @@ -{ pkgs, ... }: -{ - packages = [ pkgs.python39Packages.tox ]; -} diff --git a/devenv.yaml b/devenv.yaml deleted file mode 100644 index c7cb5ce..0000000 --- a/devenv.yaml +++ /dev/null @@ -1,3 +0,0 @@ -inputs: - nixpkgs: - url: github:NixOS/nixpkgs/nixpkgs-unstable diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..b9c1b3a --- /dev/null +++ b/flake.lock @@ -0,0 +1,59 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1735563628, + "narHash": "sha256-OnSAY7XDSx7CtDoqNh8jwVwh4xNL/2HaJxGjryLWzX8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b134951a4c9f3c995fd7be05f3243f8ecd65d798", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "utils": "utils" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..a993433 --- /dev/null +++ b/flake.nix @@ -0,0 +1,21 @@ +{ + inputs = { + utils.url = "github:numtide/flake-utils"; + }; + outputs = { self, nixpkgs, utils }: utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in + { + devShell = pkgs.mkShell { + buildInputs = with pkgs; [ + python3Packages.tox + python39 + python310 + python311 + python312 + ]; + }; + } + ); +} diff --git a/pyproject.toml b/pyproject.toml index 7b465de..a94abe8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,6 @@ [tool.black] target-version = ["py34", "py35", "py36", "py37", "py38"] + +[tool.pyright] +venvPath = "." +venv = ".tox/py311" diff --git a/rflink/__main__.py b/rflink/__main__.py index 0301df5..befaa58 100644 --- a/rflink/__main__.py +++ b/rflink/__main__.py @@ -23,9 +23,9 @@ import asyncio import logging import sys +from importlib.metadata import version from typing import Dict, Optional, Sequence, Type # noqa: unused-import -import pkg_resources from docopt import docopt from .protocol import ( # noqa: unused-import @@ -54,9 +54,7 @@ def main( argv: Sequence[str] = sys.argv[1:], loop: Optional[asyncio.AbstractEventLoop] = None ) -> None: """Parse argument and setup main program loop.""" - args = docopt( - __doc__, argv=argv, version=pkg_resources.require("rflink")[0].version - ) + args = docopt(__doc__, argv=argv, version=version("rflink")) level = logging.ERROR if args["-v"]: diff --git a/rflink/protocol.py b/rflink/protocol.py index 309e63f..7b4df19 100644 --- a/rflink/protocol.py +++ b/rflink/protocol.py @@ -6,6 +6,7 @@ import asyncio import concurrent import logging +import socket from datetime import timedelta from fnmatch import fnmatchcase from functools import partial @@ -13,18 +14,14 @@ TYPE_CHECKING, Any, Callable, - Generator, Optional, Sequence, - Tuple, Type, Union, cast, overload, ) -import socket - from serial_asyncio_fast import create_serial_connection from .parser import ( @@ -240,9 +237,7 @@ def handle_response_packet(self, packet: PacketType) -> None: self._last_ack = packet self._command_ack.set() - async def send_command_ack( - self, device_id: str, action: str - ) -> Generator[Any, None, Optional[bool]]: + async def send_command_ack(self, device_id: str, action: str) -> "bool | None": """Send command, wait for gateway to repond with acknowledgment.""" # serialize commands await self._ready_to_send.acquire() @@ -396,7 +391,7 @@ def create_rflink_connection( disconnect_callback: Optional[Callable[[Optional[Exception]], None]] = None, ignore: Optional[Sequence[str]] = None, loop: Optional[asyncio.AbstractEventLoop] = None, -) -> "Coroutine[Any, Any, Tuple[asyncio.BaseTransport, ProtocolBase]]": +) -> "Coroutine[Any, Any, tuple[asyncio.Transport, asyncio.Protocol]]": """Create Rflink manager class, returns transport coroutine.""" ... @@ -413,7 +408,7 @@ def create_rflink_connection( disconnect_callback: Optional[Callable[[Optional[Exception]], None]] = None, ignore: Optional[Sequence[str]] = None, loop: Optional[asyncio.AbstractEventLoop] = None, -) -> "Coroutine[Any, Any, Tuple[asyncio.BaseTransport, ProtocolBase]]": +) -> "Coroutine[Any, Any, tuple[asyncio.Transport, asyncio.Protocol]]": """Create Rflink manager class, returns transport coroutine.""" ... @@ -429,7 +424,7 @@ def create_rflink_connection( disconnect_callback: Optional[Callable[[Optional[Exception]], None]] = None, ignore: Optional[Sequence[str]] = None, loop: Optional[asyncio.AbstractEventLoop] = None, -) -> "Coroutine[Any, Any, Tuple[asyncio.BaseTransport, ProtocolBase]]": +) -> "Coroutine[Any, Any, tuple[asyncio.Transport, asyncio.Protocol]]": """Create Rflink manager class, returns transport coroutine.""" if loop is None: loop = asyncio.get_event_loop() @@ -444,10 +439,12 @@ def create_rflink_connection( keepalive=keepalive, ) + conn: Coroutine[Any, Any, tuple[asyncio.Transport, asyncio.Protocol]] + # setup serial connection if no transport specified if host: conn = loop.create_connection(protocol_factory, host, cast(int, port)) else: - conn = create_serial_connection(loop, protocol_factory, port, baud) + conn = create_serial_connection(loop, protocol_factory, str(port), baud) - return conn # type: ignore + return conn diff --git a/rflinkproxy/__main__.py b/rflinkproxy/__main__.py index 4029890..ec5f9d0 100644 --- a/rflinkproxy/__main__.py +++ b/rflinkproxy/__main__.py @@ -21,10 +21,9 @@ import logging import sys from functools import partial +from importlib.metadata import version from typing import Any, Callable, Dict, cast -import async_timeout -import pkg_resources from docopt import docopt from serial_asyncio_fast import create_serial_connection @@ -251,8 +250,14 @@ async def connect(self): ) try: - with async_timeout.timeout(CONNECTION_TIMEOUT): - self.transport, self.protocol = await connection + if sys.version_info >= (3, 11): + async with asyncio.timeout(CONNECTION_TIMEOUT): + self.transport, self.protocol = await connection + else: + import async_timeout + + async with async_timeout.timeout(CONNECTION_TIMEOUT): + self.transport, self.protocol = await connection except ( serial.serialutil.SerialException, @@ -274,9 +279,7 @@ async def connect(self): def main(argv=sys.argv[1:], loop=None): """Parse argument and setup main program loop.""" - args = docopt( - __doc__, argv=argv, version=pkg_resources.require("rflink")[0].version - ) + args = docopt(__doc__, argv=argv, version=version("rflink")) level = logging.ERROR if args["-v"]: diff --git a/setup.cfg b/setup.cfg index 752cf95..d2f1851 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,10 +6,7 @@ ignore = D213,E128,D203,D418 max_line_length = 100 [mypy] -# 3.4 would be more correct, but when checking on later versions we may -# have e.g. async_timeout which uses async and would thus error out with -# this set to 3.4. -python_version = 3.5 +python_version = 3.11 #strict = True # does not work here, run with --strict disallow_any_unimported = True warn_unreachable = True diff --git a/setup.py b/setup.py index 93e3e76..9a62877 100644 --- a/setup.py +++ b/setup.py @@ -9,8 +9,8 @@ here = path.abspath(path.dirname(__file__)) -if sys.version_info < (3, 4): - raise RuntimeError("This package requires at least Python 3.4") +if sys.version_info < (3, 9): + raise RuntimeError("This package requires at least Python 3.11") # Get the long description from the README file with open(path.join(here, "README.rst"), encoding="utf-8") as f: @@ -57,11 +57,11 @@ def version_from_git(): packages=find_packages(exclude=["contrib", "docs", "tests"]), package_data={"rflink": ["py.typed"]}, install_requires=[ - "async_timeout", "docopt", "pyserial", "pyserial-asyncio-fast", 'typing;python_version<"3.5"', + 'async_timeout;python_version<"3.11"', ], # # List additional groups of dependencies here (e.g. development # # dependencies). You can install these using the following syntax, diff --git a/tox.ini b/tox.ini index 7965623..9d9e3f1 100644 --- a/tox.ini +++ b/tox.ini @@ -1,14 +1,13 @@ [tox] -envlist = py36,py37,py38,py39,lint,typing,py311 +envlist = py39,py310,py311,py312,lint,typing skip_missing_interpreters = True [gh-actions] python = - 3.6: py36 - 3.7: py37 - 3.8: py38 3.9: py39 + 3.10: py310 3.11: py311, lint + 3.12: py312 [testenv] commands = py.test \ @@ -18,7 +17,6 @@ commands = py.test \ rflink tests {posargs} deps = pytest - pytest-catchlog pytest-cov pytest-xdist usedevelop = True @@ -26,7 +24,7 @@ usedevelop = True [testenv:fix] commands = autopep8 --aggressive --in-place --recursive . - isort -rc . + isort . black . deps = isort @@ -45,8 +43,11 @@ deps = pyflakes<2.5 [testenv:typing] -commands = mypy --strict --ignore-missing-imports rflink -deps = mypy +commands = mypy --install-types --non-interactive --strict --follow-untyped-imports --ignore-missing-imports rflink +#commands = basedpyright rflink +deps = + types-docopt + mypy [testenv:pypy3] deps =