feat: add InstrumentCapabilities models (§A.1 of duration-estimates plan) - #864
Merged
Conversation
…lan) New pyobs/robotic/instruments.py: plain pydantic models mirroring pyobs-portal's InstrumentSerializer JSON shape (post-#139/#140/#142 -- module_name lives on CameraCapability/TelescopeCapability/DomeCapability/ FilterWheelCapability, not on Instrument itself), plus InstrumentCapabilities, a module-name-keyed view over a GET /api/instruments/ response. Flattens the nested portal response into direct camera()/telescope()/dome()/ filter_wheel() lookups by module_name at parse time, plus by_camera_code() for the fleet-wide-ID case -- scripts only ever need one device's row, never "the instrument" as a concept, so no two-step resolve-then-search indirection. No Django import; this only ever deserializes JSON the portal API already returns. Round-trip test fixture is a real InstrumentSerializer dump from a live pyobs-portal instance, not hand-guessed field names. First slice of specs/plans/2026-09-01-instrument-capability-duration-estimates.md (§A.1) -- TaskData/TaskArchive/PortalTaskArchive/scheduler wiring and the 5 leaf scripts (§A.2-A.8) are follow-up work. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019XoNCe7YyJELc8xup1zVdE
… fleet-open-items
thusser
commented
Sep 2, 2026
thusser
left a comment
Member
Author
There was a problem hiding this comment.
Review: approve — clean, faithful §A.1 slice
I cross-checked this against the actual pyobs-portal code in the sibling repo (post-#140/#142 develop), not just the PR text. No blockers; a few notes below are forward-looking, mostly for the later §A.4–A.8 slices.
Verified
- Schema fidelity is exact. Field-by-field comparison against
pyobs_portal/instruments/serializers.py(currentdevelop): every serializer field maps one-to-one onto the models inpyobs/robotic/instruments.pywith correct nullability and defaults — includingname: str = ""matchingCharField(blank=True),mount_typedefault, empty-list defaults, nullabletelescope/dome, and nullableFilterWheelCapability.module_name. The fixture's timestamps (2026-09-02T18:34:16Z, minutes before this PR opened) corroborate the "live dump, not hand-guessed" provenance. - Flat lookup keys can't collide. The portal DB enforces
unique=Trueonmodule_nameper device table and oncode(instruments/models.py), so the last-wins behavior inInstrumentCapabilities.__init__is safe today — including cross-type name sharing, which lands in separate dicts. - Tooling all green: 9/9 new tests pass;
ruff,black --check,pyreflyclean on both files. The only failures in the widertests/robotic/run (446 passed, 4 failed) are environmental — astropy IERS table download blocked by a read-only~/.astropycache — and fail identically ondevelopin this sandbox. Unrelated to the PR.
Notes (none blocking)
extra="forbid"makes parsing hard-fail on portal payload drift — worth deciding now, since §A.4/§A.8 build on it. These models inheritBaseModel'sextra="forbid"(#762 convention), so any field the portal adds or renames — a real risk given #139/#140/#142 just reshaped this exact payload — raisesValidationErrorfor the whole response (checked: an extraidkey fails withextra_forbidden). That conflicts with the plan's "degrade to None, never raise" principle, and under §A.4's design a first-parse failure leaves capabilitiesNoneforever (the stale-cache fallback only helps after a first success). These models are a pure mirror of another repo's wire format — unlike pyobs' own config models whereforbidcatches typos — so considerextra="ignore"here, or explicitly make §A.4 treat aValidationErroras a fetch failure. Field sets match exactly today, so this is forward-looking only.- Pagination contract for the future fetcher.
GET /api/instruments/is DRF-paginated (PageNumberPagination,PAGE_SIZE=100in portal settings), sofrom_api_response's documented "caller unwrapsresults" contract would silently truncate any fleet with >100 instruments. Fine for real sites; flag for §A.4 or setpagination_class = Noneon the portal view. - Nit — test style:
setup_methodis the only xunit-style hook intests/robotic/; the rest of the suite uses plain functions/fixtures. Cosmetic only. - Nit —
updated_at: str: fine as-is (nothing in pyobs-core compares these timestamps; staleness is the portal's marker endpoint), but a one-line "why str, not datetime" comment would preempt the obvious question.
Summary
Models mirror the serializer exactly, the flattening matches §A.1 (including by_camera_code and skipping None-module filter wheels per pyobs-portal#142), docstrings are clear about motivation and contracts, and tests cover the interesting edges (None wheel not indexed, cross-type negative lookup, multi-instrument aggregation). Merge as-is; carry notes 1–2 into the §A.2–A.8 follow-up work.
This was referenced Sep 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
specs/plans/2026-09-01-instrument-capability-duration-estimates.md(§A.1): newpyobs/robotic/instruments.pywith plain pydantic models mirroring pyobs-portal'sInstrumentSerializerJSON shape (post-v0.18.19 #139/v0.18.20 #140/pyobs-portal#142 —module_namelives onCameraCapability/TelescopeCapability/DomeCapability/FilterWheelCapability, not onInstrumentitself).InstrumentCapabilitiesflattens the nested portal response into directcamera()/telescope()/dome()/filter_wheel()lookups bymodule_name, plusby_camera_code()for the fleet-wide-ID case — no two-step "resolveInstrument, then search its nested list" indirection, since each script only ever needs one device's row.pyobs.utils.serialization.BaseModelconvention already used for external-API models (pyobs/robotic/storage/lco/_portal.py'sLco*classes).Not in this PR
§A.2–A.8 (
TaskDatafield,TaskArchive/PortalTaskArchivewiring,Task.estimate_duration()/TaskScheduler.schedule()threading, and the 5 leaf scripts) are follow-up work, tracked in the plan doc.Test plan
pytest tests/robotic/test_instruments.py— 9/9 passing, including a round-trip test against a realInstrumentSerializerdump from a live pyobs-portal instance (not hand-guessed field names)pytest tests/robotic/— 451/451 passing, no regressionsruff check/black --check/pyrefly check— clean🤖 Generated with Claude Code
https://claude.ai/code/session_019XoNCe7YyJELc8xup1zVdE