Modernize packaging, tooling, and CI; refresh the GraphQL schema - #40
Modernize packaging, tooling, and CI; refresh the GraphQL schema#40parrot-tailor wants to merge 14 commits into
Conversation
Replace the drifted .gitignore with a byte-for-byte copy of upstream github/gitignore Python.gitignore, then append a project-specific section. Upstream adds .ruff_cache/ and comments out .python-version so the pinned interpreter can be checked in. Preserve the maintainer's explicit editor ignores (.idea/, *.iml, .vscode, *.whl) and add junit.xml, emitted by the pytest config.
Adopt PEP 621 metadata with the setuptools build backend. Scope packages to birdbuddy* so tests/ is no longer shipped in the wheel (find_packages() previously packaged it). Move dev/test/publish tooling into an optional [dev] extra: pytest stack, ruff, pyright, build, twine. Pin the dev/lint interpreter via .python-version (3.10.20, the support floor). Remove setup.py, requirements.txt and requirements.test.txt. Publishing is now: python -m build && twine upload dist/*
Configure the toolchain in pyproject.toml: - ruff: full google-style ruleset at line-length 79, target py310, with absolute-import enforcement (TID252 + ban-relative-imports = all) - pytest: asyncio_mode = auto and coverage/junit addopts - coverage: source = birdbuddy, omit tests Add pyrightconfig.json (py310, tests exec env), .markdownlint.yaml (matches the linters used in CI), and a Makefile with deps/format/lint/ test/check/clean plus build, publish (python -m build + twine) and schema targets. Lint is intentionally red until the cleanup commit; the source is not yet formatted or annotated.
Proposing to license this project under the MIT license. Fixes jhansche#32.
Bring the tree to a clean gate (make check: ruff, ruff format, markdownlint, pyright, pytest) without changing valid-input behavior: - Format to line-length 79 and sort imports; convert package-relative imports to absolute (TID252, ban-relative-imports = all). Symbol imports are kept (not module-qualified). - Rewrite docstrings to Google style: Args/Returns/Raises on functions with parameters, attribute-style one-liners on properties; convert the old reStructuredText :param blocks. - Add type annotations; refine loose return types (e.g. str | None, datetime | None), fix UserDict[str, any] (the builtin) to typing.Any, and correct GraphqlError.error_code to str | None. - Remove asserts from source (they are reserved for tests): precondition asserts become explicit raises, and the two assert-await-refresh cases become plain await calls so the request is not tied to an assertion. - Enable the S101 (assert) and FA (future-annotations) ruff rules; ignore E501 for GraphQL query strings; keep the N818 noqa on CompositeException (public name, no rename). - Add a PEP 561 py.typed marker (shipped in the wheel) so downstream consumers can use the annotations. - Reflow README prose to satisfy markdownlint. make check passes on 3.10.20; the frozen public API imports unchanged.
Move the schema artifacts out of the repo root: - scripts/dump_schema.py writes the fixture via a __file__-relative path (run with make schema) - tests/fixtures/schema.json, refreshed from the live introspection endpoint - remove the stale schema-20230225.txt Add tests/test_schema.py, which derives the GraphQL types the library's queries use (input types from birdbuddy.queries, object types from the SightingType enum) and asserts the committed schema still defines them. The guard immediately surfaces real drift: the refreshed schema no longer defines SightingCreateInput or SightingCreateCheckProgressInput, so make check fails here -- the sighting_create and sighting_create_check_progress queries are stale against the current API.
The schema drift guard flags that the API dropped SightingCreateInput and SightingCreateCheckProgressInput. Exempt them via a documented _KNOWN_MISSING allowlist: the generic sightingCreate flow was removed with no equivalent (the app now collects via postcardCollect), so sighting_create / sighting_create_check_progress are non-functional but are kept for backwards compatibility (see jhansche#29). Add scripts/dump_payloads.py: a maintainer tool that logs in via BB_EMAIL/BB_PASSWORD and dumps the postcard -> sighting flow (capturing server errors) to a git-ignored *.dump.json, for debugging and for building sanitized test fixtures.
reanalyze_postcard and sighting_from_postcard branched on str / FeedNode with no else, leaving postcard_id unbound (UnboundLocalError) for any other input type. Add an else that raises a clear TypeError. Valid str / FeedNode inputs behave identically. Add a guard test.
Merge the issue/feature-named test_issue_40.py and test_sighting_create.py into a source-module-named test_client.py. Parameterize the best-guess / anomaly-correction finish cases and the in-progress / completed check-progress cases; hoist the repeated UUIDs into named constants. Rename the issue_40 fixture and issue-40.yaml to postcard_sighting. Behavior and coverage are unchanged.
Add tests/test_sightings.py pinning the current behavior of the complex sighting-report logic the postcard finishing flow relies on: SightingType classification, token_json (plain / signed / malformed), the finishing strategies (recognized, recognized-species propagation, mystery fallback), and highest_confidence_matches. Gives a safety net for a later rewrite.
Rewrite .github/workflows/python-package.yml as a single check job that runs make deps then make check across a Python 3.10-3.14 matrix, so CI exercises the same ruff, ruff format, markdownlint, pyright and pytest gate as local development. Bump to actions/checkout@v7 and actions/setup-python@v6, add contents: read permissions, and drop the inherited weekly cron in favor of push, pull_request and workflow_dispatch triggers. Add .github/dependabot.yml to track pip and github-actions updates, grouped weekly.
Give the README a clear structure and fix its broken examples: - Add the project description and status shields (PyPI version, build, maintenance, GitHub release, license). - Split the content into Installation, Usage, Translations, Development and Releasing sections. - Fix the Translations example: import BirdBuddy from birdbuddy.client (the package root only exports LOGGER/VERBOSE) and instantiate it with credentials instead of referencing the bare class. - Document the development workflow: pyenv, make deps and the make targets (each self-activates the venv), plus pip install -e '.[dev]'. - Document the PyPI release process: bump the version, then make build (python -m build -> sdist + wheel) and make publish (twine check + upload), including how twine reads its credentials. The FeederForPrivate fragment is unchanged; it is still the type the library queries and is present in the refreshed schema.
Cover the model classes that had no direct tests, built from dict
fixtures with no network:
- Species and BirdBuddyUser property mapping.
- Feed / FeedNode / FeedNodeType: type resolution and the Unknown
fallback, datetime parsing, and Feed.filter / newest_edge.
- Media / Collection / is_media_expired: signed-URL expiry, image vs
video, and collection species/visits/cover media.
- Feeder enums (known values + UNKNOWN fallback), Signal, Battery,
Feeder, and FeederUpdateStatus.
Raises total coverage from 63% to 71%.
Pin one dormant quirk (and mark it with a TODO) rather than change
behavior here: Feeder.power_profile defaults to "STANDARD", which does
not match the PowerProfile enum value ("STANDARD_MODE"), so a Feeder
missing powerProfile resolves to UNKNOWN. This never surfaces in
practice -- only owner responses carry powerProfile and they always
include it -- so ha-birdbuddy is unaffected. It is an independent
bugfix, not part of the 0.1.0 breaking changes.
Convert postcard_sighting.yaml to JSON and load fixtures via json; drop the now-unused pyyaml dev dependency. The API, the payload dumper and schema.json are all JSON, so the fixtures follow suit. Sanitize postcard_sighting.json: remap every account identifier (feeder, sighting and media ids) to generated UUIDs and re-encode its JWT reportToken with scrubbed userId/feederId. The token's reportItems, which the finish-flow tests rely on, are preserved, and test_client.py's expected constants are remapped to match. Add tests/fixtures/api_payloads.json: a sanitized capture of live responses (2026-07). Identifying values (UUIDs, media URLs, feeder name, serial number, location, invite/report tokens) are replaced; structure, enum values, timestamps and the sighting_create error are preserved. Add tests/test_api_payloads.py, exercising Feeder, FeedNode, PostcardSighting/SightingReport and the recognized finish strategy against the real payload, and pinning the live sighting_create drift (Unknown type "SightingCreateInput", jhansche#29). Mark a second dormant model bug with a TODO: FeederForOwner nests location{city,country} while FeederForMember/Public use flat locationCity/locationCountry (neither deprecated), so Feeder.location returns (None, None) for owner feeders.
jhansche
left a comment
There was a problem hiding this comment.
Thanks for the substantial effort here — there’s a lot of useful work in this PR.
My main concern is scope: it combines packaging, build tooling, CI, schema refreshes, fixture changes, and test restructuring in one very large change set. That makes it hard to review and increases merge risk. I’d be much more comfortable if this were split into smaller PRs, especially separating pure repo/tooling changes from functional/test changes.
Each of these could be independent PRs with a much tighter scope, making them easier to review, easier to identify regressions, and easier to revert if necessary.
And I haven't even gotten to the potential breaking changes yet, in the next PR 😮💨 (hint: I'm going to ask that all changes be made backwards compatible, so that merging pybirdbuddy changes doesn't break ha-birdbuddy, and the ha-birdbuddy changes don't introduce user-facing breaking changes without some kind of deprecation warning period that encourages users to switch to the simpler events). Also note that ha-birdbuddy intentionally runs CI checks against the pybirdbuddy main branch, and not a pinned version.
|
Thanks @jhansche! Will split this up. For the breaking changes in the follow-up PR, those reflect breaking changes that were introduced on the Bird Buddy side. Normally I wouldn't have included a version bump on that one (that's owned by the maintainer, aka you), but let me rethink how I can handle those better so you (and users) can adopt them without breaking ha-birdbuddy in the process 🙇♂️ |
Brings the project onto a modern Python toolchain — pyproject packaging, ruff, pyright, a Makefile-driven gate, and CI — restructures and expands the tests, refreshes the GraphQL schema, and standardizes fixtures on JSON. Pure maintenance: no public symbol is renamed or moved and no signature changes, so the API that downstream consumers (e.g. ha-birdbuddy) import is unchanged.
Commits
github/gitignorePython template plus a few project-specific entries (junit.xml, IDE dirs, local API payload dumps).birdbuddy*sotests/no longer ships in the wheel; dev/test/publish tooling folded into a[dev]extra; interpreter pinned via.python-version(3.10.20).pyrightconfig.json,.markdownlint.yamland a Makefile (deps/format/lint/test/check/schema/build/publish/clean). Lint is intentionally red until the cleanup commit.LICENSEand wire it into the packaging metadata.py.typedmarker.dump_schema.pytoscripts/andschema.jsontotests/fixtures/(refreshed from live introspection), delete the stale 2023 dump, and addtest_schema.pyasserting the schema still defines every type the queries use.SightingCreateInput/SightingCreateCheckProgressInput, sosighting_create*are stale; document that (allowlisted in the test) and addscripts/dump_payloads.pyto capture real responses as sanitized fixtures.reanalyze_postcard/sighting_from_postcardhad noelse, leavingpostcard_idpossibly-unbound for non-str/FeedNodeinput; raiseTypeErrorinstead. Valid inputs behave identically.test_sightings.pypinning the current postcard finishing logic (strategies, highest-confidence matches, token decoding,SightingType) as a safety net for the follow-up rewrite.make deps+make checkacross a Python 3.10–3.14 matrix, bump actions, add least-privilege permissions, drop the weekly cron, and add dependabot for pip + github-actions.make build/make publish.power_profiledefault bug.pyyaml; addapi_payloads.json(a sanitized 2026-07 capture) with realistic model tests; TODO-mark thelocationper-type mismatch.Issues and bugs discovered
Surfaced by the schema refresh, a cross-check of the Bird Buddy app, and the new tests.
Behavioral — pinned here, to fix in a follow-up PR
sighting_create/sighting_create_check_progressare broken by schema drift. The API droppedSightingCreateInput/SightingCreateCheckProgressInput. Surfaced by the new drift test, allowlisted intest_schema.py, and captured as a real error in the sanitized fixture. Upstream: pybirdbuddy#29, pybirdbuddy#30 (closed), downstream symptom ha-birdbuddy#98.Feeder.power_profiledefault mismatch. Defaults to"STANDARD", which isn't the enum value"STANDARD_MODE", so a feeder missingpowerProfileresolves toUNKNOWN. Dormant — owner responses always carry the field. TODO infeeder.py. No existing upstream issue.Feeder.locationper-type mismatch. Reads flatlocationCity/locationCountry, butFeederForOwnernestslocation{city,country}(member/public use the flat shape; neither deprecated), so owner feeders return(None, None). TODO infeeder.py. No existing upstream issue.Fixed in this PR (safe, no contract change)
postcard_idfor non-str/FeedNodeinput → now a clearTypeError(c4db8d6).tests/shipped in the wheel viafind_packages()→ packaging scoped tobirdbuddy*(775468e).from birdbuddy import BirdBuddy, bareBirdBuddyclass) → corrected (916cb11).Out of scope (not pybirdbuddy)
Testing
make check(ruff, ruff format, markdownlint, pyright, 52 pytest) passes locally on Python 3.10.20; CI runs the same gate across the Python 3.10–3.14 matrix. Coverage: 62% → 71% (11 → 52 tests).Follow-up (separate PR, based on this branch)
A second PR, branched off this one, will migrate the postcard flow to
postcardCollect— the fix for thesighting_createdrift above and ha-birdbuddy#98 — fix thepower_profileandlocationmodel bugs, update the characterization tests deliberately, and bump0.0.21→0.1.0.