Skip to content

refactor: reference payloads become test fixtures, not package data (#162) - #166

Merged
cayossarian merged 1 commit into
mainfrom
fix/162-fixtures-out-of-wheels
Aug 26, 2026
Merged

refactor: reference payloads become test fixtures, not package data (#162)#166
cayossarian merged 1 commit into
mainfrom
fix/162-fixtures-out-of-wheels

Conversation

@cayossarian

Copy link
Copy Markdown
Member

Closes #162.

Both distributions shipped a test fixture inside the runtime wheel — 56 KB of capture data that no runtime path reads, downloaded by every Home Assistant user installing this library. Verified before changing anything: nothing under src/ imports reference_payloads, in either distribution.

Removing rather than adding

The issue proposed a separate fixtures distribution, pinned in lockstep. That works, but the cost is permanent: a fourth PyPI project, its own trusted publisher, a release cut in step with every adapter release, and a fourth pin for consumers to keep aligned. That is a standing obligation taken on to avoid 56 KB.

The alternative it ruled out was vendoring, on one specific ground recorded in 723e941:

A copy has no version: it goes stale in silence, and the Home Assistant integration's conformance gate then verifies its declarations against a schema no panel runs. Detecting that is not possible from the integration's CI, which installs these distributions from PyPI and has no checkout to compare against.

That objection is answerable. Detecting staleness never needed the file — only the version claim, which importlib.metadata reports for an installed distribution with no checkout and no network call. A vendored copy that records the release it came from can be held against reality, and fail when the two disagree.

So the payloads become ordinary repository fixtures under tests/reference_payloads/, and the one external consumer vendors its own copy with that guard. Nothing needs publishing that did not already exist.

Breaking

span_panel_api.reference_payloads and span_panel_api_schema_1.reference_payloads are no longer importable from either distribution. Both changelogs say so plainly rather than describing this as a packaging tidy-up: anything importing those accessors breaks on upgrade. In practice that is one known consumer, which moves in the same change on its side.

The accessors themselves are unchanged in behaviour and live beside the payloads they read — devices_from_tree and device_from_topics stay with the capture for the reason 723e941 gave for shipping them together, which has not changed: four test modules here otherwise hold the same twelve lines of replay.

Asserted against the built artifact, not the source tree

These payloads shipped without any packaging declaration asking them to — a directory inside a package directory ships. Nothing in the manifests would object to that happening again, so the check has to look at what was actually built:

  • ci.yml unzips every wheel in dist/ and fails if any carries a reference_payloads path component or a bare capture filename. It globs rather than naming the three known distributions, so an adapter added under packages/ is covered the day it exists, and it fails if no wheels were built at all rather than passing vacuously.
  • tests/test_packaging.py catches the same regression earlier, deriving both the capture names and the package list from disk and the manifests rather than hardcoding either. It also carries a test whose only job is to fail if manifest discovery finds fewer distributions than exist — a parametrised test whose parameter list silently empties is how a suite reports green while checking nothing.

Verification

1081 passed / 7 skipped, mypy clean across 41 source files (down from 43 — two __init__.py modules left src/, which is the move showing up in the type surface).

Built all three wheels locally and confirmed none carries a payload.

Not published

Folds into the unreleased 3.1.0 — no version bump.

`reference_payloads` sat inside `src/span_panel_api/` and
`packages/schema-1/src/span_panel_api_schema_1/`, so both wheels carried it —
not through any packaging declaration, but because a directory inside a package
directory ships. No runtime path read either capture, so every install of both
distributions paid for 56 KB of test data it could not use, and the import
surface committed each distribution to a promise it never meant to make.

It shipped that way for a real reason: a vendored copy has no version, goes
stale in silence, and a consumer's conformance gate then verifies its
declarations against a schema no panel runs. The reason does not survive
inspection. Detecting staleness never needed the file, only the version claim,
and `importlib.metadata.version(...)` gives any consumer that without a
checkout. A vendored copy that records the release it was taken from and
asserts it against the installed distribution gets the loud failure the package
data was protecting.

Both captures move to `tests/reference_payloads/`, split the way the
distributions were: `bootstrap` holds the homie schema document and imports
nothing an adapter-less environment lacks, `schema_one` holds the retained-topic
tree and the replay that reads it, which reaches the eBus SDK. Keeping the split
means a test that wants the schema document does not drag the SDK in behind it.
`devices_from_tree` / `device_from_topics` stay beside the capture for the reason
they were written: separating them would put the same twelve lines of replay in
each of the modules that read it. Imported as a top-level package, the same
arrangement that makes `from conftest import ...` work throughout this suite.

Two guards, because nothing declares what ships and so nothing would object to
this recurring. `tests/test_packaging.py` fails if a capture reappears inside any
shipped package, derived from the manifests rather than a hardcoded list; CI
fails if any built wheel carries one, which is where it is finally true rather
than inferred. A third resolves `spec_lock.json`'s recorded capture path, which
nothing read before — the path went stale in this very change with nothing
objecting.

The mypy hook now checks `tests/reference_payloads/` alone out of `tests/`. Its
five accessors were type-checked under --strict as package data and are read by
twenty test modules; moving them should not quietly cost them that.

Closes the library half of #162.
@cayossarian
cayossarian force-pushed the fix/162-fixtures-out-of-wheels branch from 051d72b to 48b64a4 Compare August 26, 2026 05:34
@cayossarian
cayossarian merged commit ea59e58 into main Aug 26, 2026
6 checks passed
@cayossarian
cayossarian deleted the fix/162-fixtures-out-of-wheels branch August 26, 2026 05:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Test fixtures ship inside the runtime wheels; split them into a lockstep-pinned fixtures distribution

1 participant