Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@ updates:
patterns:
- "*"

- package-ecosystem: pip
# `uv`, not `pip`: dependabot drives this repository through uv.lock and
# pyproject.toml (PEP 621). uv is a first-class ecosystem in dependabot
# (`Dependabot::Uv::PackageManager::ECOSYSTEM = "uv"`).
#
# Everything is grouped, including majors. Majors used to stay separate so each
# got individual review, which in practice meant a queue of five PRs to merge one
# at a time — and `open-pull-requests-limit` then held back the rest. One PR per
# week covering majors and minors together is what the maintainer wants; re-add
# `update-types: [minor, patch]` below to split majors out again.
- package-ecosystem: uv
directory: "/"
schedule:
interval: weekly
Expand All @@ -25,20 +34,12 @@ updates:
prefix: "chore(deps)"
open-pull-requests-limit: 5
groups:
# Bundle low-risk minor/patch bumps into a single PR; majors stay separate
# so they get individual review.
python-minor-patch:
python:
patterns:
- "*"
update-types:
- minor
- patch

# No `pre-commit` ecosystem on purpose. The hooks are pre-commit-hooks and
# ruff-pre-commit, and the ruff *revision* here is what CI installs — the lint
# job derives its ruff version from this file. Watching it would split a ruff
# bump across two PRs (this rev and the `ruff = "^0.15.0"` range in
# pyproject.toml), which is the arrangement that made every ruff bump
# unmergeable in the integration repo until
# dimplex-controller-hass#201/#213 removed its second pin. Add this ecosystem
# only once ruff is pinned in exactly one place.
# No `pre-commit` ecosystem on purpose. The hooks are pre-commit-hooks plus local
# ruff hooks that run the environment's ruff, so there is nothing ruff-shaped here
# to bump out of step with uv.lock — but adding this ecosystem would also start
# proposing bumps for the hook revisions, which is harmless yet not wanted until
# someone asks for it.
17 changes: 8 additions & 9 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,18 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
- name: Install uv
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
python-version: "3.10"

- name: Install Poetry
uses: snok/install-poetry@a783c322200f0519c7926aa6faa857c4e23e9263 # v1.4.2
with:
virtualenvs-create: true
virtualenvs-in-project: true

- name: Build package
run: poetry build
# uv_build, configured in [tool.uv.build-backend]. The artefacts were
# diffed against the poetry-built 0.13.1 before switching: same module
# set, same entry point, same license file. The only metadata change is
# PEP 639 — License-Expression replaces the License field, and the
# now-deprecated license classifier is gone.
run: uv build

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
Expand Down
109 changes: 44 additions & 65 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
- 'dimplex_controller/**'
- 'tests/**'
- 'pyproject.toml'
- 'poetry.lock'
- 'uv.lock'
- '.pre-commit-config.yaml'
- '.github/workflows/test.yml'
docs:
Expand Down Expand Up @@ -70,50 +70,43 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
- name: Install uv
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
python-version: "3.13"
cache: pip
# The lock too: this job reads the mypy version out of it.
cache-dependency-path: |
pyproject.toml
poetry.lock
enable-cache: true

- name: Install ruff
# Pinned to the same version as .pre-commit-config.yaml so the pre-commit
# and CI invocations of ruff cannot drift.
run: pip install "ruff==$(grep -A1 'ruff-pre-commit' .pre-commit-config.yaml | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' | sed 's/^v//')"
- name: Install dependencies
# `--frozen`, deliberately not `--locked`. release-please bumps the version
# in pyproject.toml without touching uv.lock, and uv records the project's
# own version inside the lock — so `--locked` fails on every release PR and
# leaves main's lock "stale" until someone refreshes it. `--frozen` installs
# exactly what the lock pins and does not read pyproject in place of it.
# A dependency added without re-locking still fails loudly, as a missing
# import in the tests rather than a silent drift.
run: uv sync --frozen

- name: Lint with ruff
run: ruff check dimplex_controller/ tests/
# ruff comes from uv.lock, via the lock's dependency group — the single
# source of its version, shared with the pre-commit hooks.
run: uv run ruff check dimplex_controller/ tests/

- name: Check formatting
run: ruff format --check dimplex_controller/ tests/

- name: Install mypy
# The version poetry resolves, read from the lock rather than left to
# float. `poetry run mypy` locally and this job must agree: with `strict`
# set, a version difference is not cosmetic, it changes which checks run.
run: pip install "mypy==$(awk '/^name = "mypy"$/{getline; print; exit}' poetry.lock | sed -E 's/^version = "(.*)"$/\1/')"

- name: Install runtime dependencies
# mypy must be able to resolve pydantic and aiohttp, or `strict` cannot
# check the library at all. With `ignore_missing_imports = true`, an
# unresolvable pydantic makes `BaseModel` itself `Any`, and strict's
# `disallow_subclassing_any` then reports "Class cannot subclass
# BaseModel (has type Any)" for every model in the package — 20 errors,
# none of them real. Installing the package brings its own dependencies,
# so this cannot drift when a runtime dependency is added.
run: pip install .
run: uv run ruff format --check dimplex_controller/ tests/

- name: Type check with mypy
# Convert mypy output into GitHub annotations (inline on the PR diff) while
# preserving its exit status via pipefail, so type errors are surfaced on the
# uv resolves mypy *and* the runtime dependencies from the lock. That
# matters: with pydantic unresolvable and `ignore_missing_imports = true`,
# `BaseModel` is `Any` and strict's `disallow_subclassing_any` reports
# "Class cannot subclass BaseModel (has type Any)" for every model in the
# package — 20 errors, none of them real.
#
# Output is converted into GitHub annotations (inline on the PR diff) while
# pipefail preserves the exit status, so a type error surfaces on the
# offending line instead of only in the raw log.
run: |
set -o pipefail
mypy | sed -E \
uv run mypy | sed -E \
-e 's/^([^:]+):([0-9]+): error: (.*)$/::error file=\1,line=\2::\3/' \
-e 's/^([^:]+):([0-9]+): note: (.*)$/::notice file=\1,line=\2::\3/'

Expand All @@ -125,21 +118,20 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
- name: Install uv
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
python-version: "3.13"
cache: pip
cache-dependency-path: pyproject.toml
enable-cache: true

- name: Install pre-commit
run: pip install pre-commit
- name: Install dependencies
run: uv sync --frozen

- name: Run pre-commit on all files
# Pre-commit runs the local hook suite (trailing-whitespace, end-of-file-fixer,
# ruff, ruff-format). It catches drift between the ruff-pre-commit pin and
# the ruff pin in pyproject.toml.
run: pre-commit run --all-files --show-diff-on-failure --color=always
# Through `uv run`, so the ruff hooks — which run the environment's ruff
# (`language: system`) rather than a pinned pre-commit mirror — resolve the
# version from uv.lock, exactly as a contributor's `uv run pre-commit` does.
run: uv run pre-commit run --all-files --show-diff-on-failure --color=always

test:
needs: changes
Expand All @@ -149,37 +141,24 @@ jobs:
strategy:
fail-fast: false
matrix:
# Matches requires-python (>=3.10) in pyproject.toml. 3.13 is added so the
# library stays forward-compatible with the Python version the consuming
# Home Assistant integration now targets.
python-version: ["3.10", "3.11", "3.12", "3.13"]
# Matches requires-python (>=3.10) in pyproject.toml and the Python
# classifiers the package publishes. 3.14 is the runtime the consuming Home
# Assistant integration targets.
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
- name: Install uv and Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
python-version: ${{ matrix.python-version }}

- name: Install Poetry
uses: snok/install-poetry@a783c322200f0519c7926aa6faa857c4e23e9263 # v1.4.2
with:
virtualenvs-create: true
virtualenvs-in-project: true

- name: Cache venv
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .venv
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
venv-${{ runner.os }}-${{ matrix.python-version }}-
enable-cache: true

- name: Install dependencies
run: poetry install --no-interaction
run: uv sync --frozen

- name: Run tests with coverage
run: poetry run pytest --cov=dimplex_controller --cov-report=term-missing
run: uv run pytest --cov=dimplex_controller --cov-report=term-missing

# Stable required status check for the `main` branch ruleset.
# - Python/code changes: fails unless lint, pre-commit, and the full test matrix succeed
Expand Down
30 changes: 25 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,30 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.21
# ruff runs from the project environment (`uv run pre-commit`), so uv.lock is the
# single source of truth for its version. It used to be pinned here as well, via
# ruff-pre-commit's `rev:`, while CI installed the version in poetry.lock — two
# pins that dependabot could only ever bump one of, which is the arrangement that
# made every ruff bump unmergeable in the integration repo until its second pin
# was removed (dimplex-controller-hass#201, #213).
#
# `types_or` is deliberately python/pyi only. ruff 0.16's ruff-format hook also
# claims `markdown`, and the Python blocks in README.md and docs/ are hand-aligned
# on purpose — ruff's formatter normalises comment spacing, so it would reflow the
# aligned temperature-constant table in docs/api-reference.md. [tool.ruff]
# extend-exclude keeps a bare `ruff format .` consistent with the hooks.
- repo: local
hooks:
# Run the linter first so format auto-fixes don't shadow real issues.
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
# Lint first, so format auto-fixes cannot shadow a real issue.
- id: ruff-check
name: ruff (lint, --fix)
entry: ruff check --force-exclude --fix --exit-non-zero-on-fix
language: system
types_or: [python, pyi]
require_serial: true
- id: ruff-format
name: ruff (format)
entry: ruff format --force-exclude
language: system
types_or: [python, pyi]
require_serial: true
36 changes: 17 additions & 19 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,39 @@ Contributing to this project should be as easy and transparent as possible.
## Development setup

```bash
# Install Poetry (https://python-poetry.org/docs/#installation), then:
poetry install
# Install uv (https://docs.astral.sh/uv/getting-started/installation/), then:
uv sync
```

This will install the library, all dev dependencies (pytest, ruff,
pre-commit, mypy) into a project-local venv.
`uv sync` creates `.venv`, installs the library in editable mode, and installs the
dev dependency group (pytest, ruff, mypy, pre-commit and friends) — all at the
exact versions recorded in `uv.lock`.

## Local checks

Before opening a PR, make sure all of these pass locally:

```bash
poetry run ruff check dimplex_controller tests
poetry run ruff format --check dimplex_controller tests
poetry run mypy
poetry run pytest
uv run ruff check dimplex_controller tests
uv run ruff format --check dimplex_controller tests
uv run mypy
uv run pytest
```

Or, install the pre-commit hooks once and let them run on every commit:

```bash
pip install pre-commit
pre-commit install
pre-commit run --all-files
uv run pre-commit install
uv run pre-commit run --all-files
```

The hooks cover whitespace, file endings and formatting (`ruff`, `ruff-format`,
the `pre-commit-hooks` set). **mypy and pytest run in CI but are not hooks**, so
the four commands above are still yours to run before opening a PR.
The hooks cover whitespace, file endings and formatting (`ruff-check`,
`ruff-format`, the `pre-commit-hooks` set). **mypy and pytest run in CI but are not
hooks**, so the four commands above are still yours to run before opening a PR.

Ruff's effective pin is the `ruff-pre-commit` `rev:` in
`.pre-commit-config.yaml`: pre-commit provisions that version, and the CI `lint`
job installs the same one. The `ruff` requirement in `pyproject.toml` is a
compatible range for `poetry run ruff`, so the hook revision is what the repo
agrees on.
Run the hooks through `uv run`: the ruff hooks execute the environment's ruff
(`language: system`), so `uv.lock` is the single source of the ruff version — there
is no second pin in `.pre-commit-config.yaml` to drift from it.

## Optional: open a PR automatically on push

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,12 @@ pip install .
```bash
git clone https://github.com/KRoperUK/dimplex-controller-py.git
cd dimplex-controller-py
pip install -e ".[dev]"
uv sync
```

`uv sync` creates `.venv`, installs the library in editable mode, and installs the
dev dependency group from `uv.lock`.

> **Requires:** Python 3.10 or later.

## Quick start
Expand Down
8 changes: 5 additions & 3 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ pip install dimplex-controller

### Developers

Clone the repository and install in editable mode with dev dependencies:
Clone the repository and install in editable mode with the dev dependency group:

```bash
git clone https://github.com/KRoperUK/dimplex-controller-py.git
cd dimplex-controller-py
pip install -e ".[dev]"
uv sync
```

This installs the library plus `pytest`, `ruff`, `pre-commit`, `mypy` and `twine`.
This creates `.venv` with the library plus `pytest`, `ruff`, `pre-commit`, `mypy`
and `twine`, at the versions `uv.lock` records. Prefix the checks below with
`uv run` — for example `uv run pytest`.

## First run

Expand Down
6 changes: 3 additions & 3 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,9 @@ nest_asyncio.apply()
- Pre-commit hooks are stale.

**Steps to resolve:**
1. Reinstall dependencies: `poetry install` or `pip install -e ".[dev]"`.
2. Update pre-commit hooks: `pre-commit autoupdate`.
3. Run checks manually: `ruff check dimplex_controller tests && ruff format --check dimplex_controller tests && mypy && pytest`.
1. Reinstall dependencies from the lock: `uv sync`.
2. Update pre-commit hooks: `uv run pre-commit autoupdate`.
3. Run checks manually: `uv run ruff check dimplex_controller tests && uv run ruff format --check dimplex_controller tests && uv run mypy && uv run pytest`.

## Still stuck?

Expand Down
Loading
Loading