demo: cut-down Eiger REST sim + introspectable controller example - #410
demo: cut-down Eiger REST sim + introspectable controller example#410coretl wants to merge 4 commits into
Conversation
Add a FastAPI fake REST sim (demo/simulation/eiger.py) shaped like a detector parameter tree (subsystems of named parameters, a keys listing endpoint, per-parameter GET/PUT), and an EigerDetector controller (demo/eiger.py) that type-hints half its attributes (checked via the current HintedAttribute mechanism) and fills the rest by introspecting the sim's keys endpoints in initialise(). Baseline uses the current API (AttrR/AttrRW + io_ref/AttributeIO); migrates to ControllerFiller when #394 lands. Closes #391
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## refactor #410 +/- ##
=========================================
Coverage 91.25% 91.25%
=========================================
Files 72 72
Lines 2892 2892
=========================================
Hits 2639 2639
Misses 253 253 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
sphinx-build --fail-on-warning was erroring on autodoc cross-references to httpx.AsyncBaseTransport and fastapi.applications.FastAPI, which have no intersphinx mapping - same class of issue already worked around for p4p types.
|
Fixed the Generated by Claude Code |
| # Declared (checked): must exist, with this access mode and dtype, after | ||
| # initialise() introspects the parameter tree. | ||
| count_time: AttrRW[float] | ||
| state: AttrR[str] |
There was a problem hiding this comment.
The reason we put parameters on here is because we want to build code on top of them. Add an idle: AttrR[bool] attribute with value self.state == "idle".
There was a problem hiding this comment.
Done in ff6292b. Added a soft idle = AttrR(Bool()) derived from state, kept in sync via an add_on_update_callback on self.state (idle = state == "idle"). Added a test covering it.
There was a problem hiding this comment.
We do not currently handle enum creation properly in fastcs-eiger, but I think we should assume that we do for the purposes of this demo. Therefore, for state, the actual response from the detector is: {'access_mode': 'r', 'allowed_values': ['na', 'idle', 'ready', 'acquire', 'configure', 'initialize', 'error'], 'value': 'idle', 'value_type': 'string'}, where we should use the allowed_values metadata to introspect an AttrR[enum.Enum] with the correct options. Can we amend the entry in the sim response, and accept allowed_values as a parameter on EigerParameter, then use this parameter in forming the client response from the server, such that we can correctly make an AttrR[enum.Enum] for state. I think we'll only need [idle, ready, acquire] for now. I'll un-resolve this thread to make my review comment visible.
…l read-only params - eiger.py: add soft `idle: AttrR[bool]` derived from the introspected `state` param (state == "idle"), kept in sync via an on-update callback - shows why we declare `state` as a checked attribute (to build code on top of it). - eiger.py: give read-only params a poll `update_period` in `initialise()`; rw params still read once (ONCE). - simulation/eiger.py: add a lifespan background task that sweeps `temperature` between two values so the front end shows something updating (real server only; the in-process ASGI transport used in tests stays deterministic). - tests: cover idle-from-state, read-only poll vs rw read-once, and oscillation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ests - simulation/eiger.py: replace the sine sweep with a simple flip between two known temperatures every 0.5s (predictable); expose the parameter tree via `app.state.sim` as a test backdoor for read-only params with no PUT route. - tests: drop the sim-only tests; drive everything through the controller attributes. Idle test now pokes `state` via the sim backdoor and polls the attribute (rather than calling AttrR.update directly). Oscillation test builds a controller under the app lifespan and observes temperature via subscribe. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
shihab-dls
left a comment
There was a problem hiding this comment.
This looks really useful, I've left a few comments I want addressed before appoving this, so I'll request changes.
|
|
||
| async def update(self, attr: AttrR[Any, EigerAttributeIORef]) -> None: | ||
| data = await self._connection.get(attr.io_ref.subsystem, attr.io_ref.param) | ||
| await attr.update(attr.dtype(data["value"])) |
There was a problem hiding this comment.
Here we're casting the value we recieve to the correct datatype before calling update. However, attr.update internally calls datatype.validate() which will try to cast the value anyway, and raise with a msg if it fails. We should not cast here in our update method, as the exception should be handled in one common place (i.e., validate()).
| # Declared (checked): must exist, with this access mode and dtype, after | ||
| # initialise() introspects the parameter tree. | ||
| count_time: AttrRW[float] | ||
| state: AttrR[str] |
There was a problem hiding this comment.
We do not currently handle enum creation properly in fastcs-eiger, but I think we should assume that we do for the purposes of this demo. Therefore, for state, the actual response from the detector is: {'access_mode': 'r', 'allowed_values': ['na', 'idle', 'ready', 'acquire', 'configure', 'initialize', 'error'], 'value': 'idle', 'value_type': 'string'}, where we should use the allowed_values metadata to introspect an AttrR[enum.Enum] with the correct options. Can we amend the entry in the sim response, and accept allowed_values as a parameter on EigerParameter, then use this parameter in forming the client response from the server, such that we can correctly make an AttrR[enum.Enum] for state. I think we'll only need [idle, ready, acquire] for now. I'll un-resolve this thread to make my review comment visible.
| @pytest_asyncio.fixture | ||
| async def sim(_eiger) -> SimState: | ||
| return _eiger[1] |
There was a problem hiding this comment.
nit: usually I'd nitpick about putting the fixtures grouped at the top of the page, but in this case there is a very clear separation of concerns between the sim tests and detector tests, so I don't mind it as much.
Closes #391
Adds the Example 5 tutorial pieces from #388 §9 / the demo README ladder:
src/fastcs/demo/simulation/eiger.py— a FastAPI fake REST sim shaped like a cut-down Eiger detector parameter tree:config/statussubsystems, akeyslisting endpoint per subsystem, and per-parameter GET/PUT. No new dependency (fastapi[standard]is already a core dep,httpxalready a dev dep).src/fastcs/demo/eiger.py— anEigerDetectorcontroller. Half its attributes (count_time,state) are declared as type hints and checked by the currentHintedAttributeintrospection-validation mechanism; the rest of the parameter tree is discovered atinitialise()time by walking the sim'skeysendpoints and added dynamically with no static check — exercising the current (baseline) introspection mechanism ahead ofControllerFiller(ControllerFiller — declarative/procedural split #394).tests/demo/test_eiger.py— unit tests against the fake sim (viahttpx.ASGITransport, no real sockets/hardware): sim endpoint behaviour (list/get/put/read-only-rejection/404), hinted-vs-introspected attribute typing, and read/write round-trips through the controller.Baseline uses the current API (
AttrR/AttrRW+io_ref/AttributeIO); migrates toControllerFillerwhen #394 lands, per the demo README.Instructions to reviewer on how to test:
uv run pytest tests/demo/test_eiger.py -vChecks for reviewer
Notes
uv run --locked tox -e pre-commit,type-checking, both green. For thetestsenv, this sandbox can't rundocs(needs outbound network) or PVA-touching tests (needs a socket family unavailable here) — same known limitation noted on demo: use ControllerVector for temperature ramp sub-controllers #409. Excluding those,pytest src tests --ignore=tests/benchmarkingpasses 316/326, with the same 10 pre-existing PVA/p4pfailures (RuntimeError: Address family not supported by protocol), none related to this change. Real CI coversdocsand PVA.Generated by Claude Code