diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6f53cc..c946b3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.14"] + python-version: ["3.10", "3.14"] steps: - uses: actions/checkout@v5 - uses: astral-sh/setup-uv@v6 diff --git a/CLAUDE.md b/CLAUDE.md index 5138119..8f79008 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,7 +16,7 @@ Python SDK and `holmanctl` CLI for controlling Holman Bluetooth tap timers (CO30 ## Development - Before finishing a change, run `uv run pytest`, `uv run ruff check .`, `uv run ruff format --check .` and `uv run pyrefly check` -- CI (`.github/workflows/ci.yml`) runs the same checks, with pytest on Python 3.9 and 3.14; keep `requires-python` and that matrix in sync +- CI (`.github/workflows/ci.yml`) runs the same checks, with pytest on Python 3.10 and 3.14; keep `requires-python` and that matrix in sync - Ruff formats the Python snippets in `README.md` too - Tests run with `--import-mode=importlib`, so share test helpers as fixtures in `conftest.py`, not by importing modules from `tests/` - Tests cover logic only; when adding a `gatt` API call, add it to the fakes in `tests/conftest.py` @@ -35,12 +35,13 @@ Python SDK and `holmanctl` CLI for controlling Holman Bluetooth tap timers (CO30 ## Python Version -Python 3.9 minimum (`requires-python` in pyproject.toml; ruff and pyrefly also target 3.9). Use the newest syntax 3.9 supports: +Python 3.10 minimum (`requires-python` in pyproject.toml; ruff and pyrefly also target 3.10). Use the newest syntax 3.10 supports: +- `X | None` instead of `Optional[X]` - `list[str]` instead of `List[str]` - `dict[str, Any]` instead of `Dict[str, Any]` - `tuple[int, str]` instead of `Tuple[int, str]` -- `X | None` instead of `Optional[X]` only in annotations, with `from __future__ import annotations` at the top of the module; `|` between types fails at runtime on 3.9 anywhere else (e.g. `isinstance`, aliases) -- No `match` statements (3.10) or `type X = ...` aliases (3.12); use `if`/`elif` and plain `X = ...` alias assignments (`typing.TypeAlias` is 3.10+, and `typing_extensions` isn't a dependency) +- `X: TypeAlias = ...` (from `typing`) for type aliases; `type X = ...` is 3.12+ +- Match statements for enum/type dispatch where appropriate ## Manage packages diff --git a/README.md b/README.md index ce9b764..94c6b50 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ and [BTX1](https://www.holmanindustries.com.au/products/btx1-tap-mounted-smart-v The Holman Python SDK for Linux allows you to integrate your Holman(s) into any type of Linux application or script that can execute Python code. ## Prerequisites -The Holman SDK requires [Python 3.9+](https://www.python.org) and a recent installation of [BlueZ](http://www.bluez.org/). It is tested to work fine with BlueZ 5.44, slightly older versions should however work, too. +The Holman SDK requires [Python 3.10+](https://www.python.org) and a recent installation of [BlueZ](http://www.bluez.org/). It is tested to work fine with BlueZ 5.44, slightly older versions should however work, too. ## Installation These instructions assume a Debian-based Linux. diff --git a/pyproject.toml b/pyproject.toml index 2caba1f..5c9e71f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ license = "MIT" license-files = ["LICENSE.txt"] authors = [{ name = "Scott McKenzie", email = "developers@noizyland.net" }] keywords = ["holman"] -requires-python = ">=3.9" +requires-python = ">=3.10" # python3-dbus is also required at runtime, but comes from apt (not PyPI). dependencies = ["gatt>=0.2.7"] @@ -20,7 +20,7 @@ holmanctl = "holmanctl:main" [dependency-groups] dev = [ "pyrefly>=1.3", - "pytest>=8", + "pytest>=9.0.3", "ruff>=0.17", ] @@ -39,12 +39,12 @@ testpaths = ["tests"] addopts = ["--import-mode=importlib"] [tool.ruff] -target-version = "py39" +target-version = "py310" [tool.ruff.lint] select = ["E", "F", "W", "I", "B", "UP"] [tool.pyrefly] project-includes = ["holman", "holmanctl.py", "tests"] -python-version = "3.9" +python-version = "3.10" search-path = ["typings"] diff --git a/uv.lock b/uv.lock index eb6965e..a7c015a 100644 --- a/uv.lock +++ b/uv.lock @@ -1,10 +1,6 @@ version = 1 revision = 5 -requires-python = ">=3.9" -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version < '3.10'", -] +requires-python = ">=3.10" [[package]] name = "colorama" @@ -44,8 +40,7 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "pyrefly" }, - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest" }, { name = "ruff" }, ] @@ -55,29 +50,14 @@ requires-dist = [{ name = "gatt", specifier = ">=0.2.7" }] [package.metadata.requires-dev] dev = [ { name = "pyrefly", specifier = ">=1.3" }, - { name = "pytest", specifier = ">=8" }, + { name = "pytest", specifier = ">=9.0.3" }, { name = "ruff", specifier = ">=0.17" }, ] -[[package]] -name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, -] - [[package]] name = "iniconfig" version = "2.3.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] sdist = { url = "https://files.pythonhosted.org/packages/01/e1/2069291243c926a2ff1cd706c7f3eeb9b62144bf60f77c9fb9ff2fb26bd3/iniconfig-2.3.1.tar.gz", hash = "sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960", size = 21209, upload-time = "2026-10-06T22:48:38.076Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/56/43/4ca9e49d27a1fcf6bece6f6aec0ea46bb9112489b93d4b688fb415457bdb/iniconfig-2.3.1-py3-none-any.whl", hash = "sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7", size = 7552, upload-time = "2026-10-06T22:48:36.959Z" }, @@ -129,38 +109,14 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/32/ee66df92c692c0686d82c9ecef3876ccb0386aa318bf507016f0121a8f78/pyrefly-1.3.2-py3-none-win_arm64.whl", hash = "sha256:f7424b21331670522980ff13e08bd143659879a4d92f4832282c7d7c41a9b5d6", size = 14348271, upload-time = "2026-09-28T22:10:04.253Z" }, ] -[[package]] -name = "pytest" -version = "8.4.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup" }, - { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" } }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, - { name = "tomli" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, -] - [[package]] name = "pytest" version = "9.1.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "iniconfig", version = "2.3.1", source = { registry = "https://pypi.org/simple" } }, + { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "pygments" },