You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Land the foundational multi-controller mechanics through the REST transport, end-to-end. REST is chosen as the tracer because it has the loosest naming rules and the smallest surface, so the foundation can be exercised without dragging EPICS/Tango/GraphQL specifics into this slice. After this slice, a single FastCS process can serve N controllers over REST with stable id-based routes, with subsequent slices adding the other transports atop the same foundation.
Scope:
Controller.id set exactly once by the launcher between __init__ and initialise(). Reading before set raises a clear runtime error; setting twice raises. Controller.__repr__ includes the id when set.
fastcs.yaml config schema: controllers: is a dict keyed by id, each value carrying a type: discriminator and a controller: options block. Single-class registration may omit type:. The launcher does not hard-code the filename.
launch() accepts a list of Controller classes. Type discriminator value defaults to __name__; optional type_name: ClassVar[str] overrides.
Uniform Transport.connect(controller_apis: list[ControllerAPI], loop) signature applied to every transport. Existing transports (EPICS CA, EPICS PVA, GraphQL, Tango) accept a list-of-one and behave as before — they will adopt true multi-controller in subsequent slices.
REST transport routes GET /{id}/{sub}/{attr} for every configured controller. One combined OpenAPI schema describes all controllers.
Per-transport REST id validator runs at connect() time; illegal characters fail fast with a clear startup error.
Logging: FastCS startup line lists controller ids; Controller.__repr__ surfaces id.
IPython shell context generalises the existing parallel-top-level-variables pattern (see src/fastcs/control_system.py:103) to parallel dicts: controllers: dict[id, Controller] and controller_apis: dict[id, ControllerAPI]. This satisfies user story 26 without introducing a Controller.api back-pointer, leaving ADR 0006 untouched.
New tests/test_multi_controller.py with the lifecycle and api-path scenarios from the PRD's testing decisions (id available from initialise() onward; raises if read in __init__; set_id raises if called twice; controller_apis["X"].path == [id] for root, [id, sub] for sub-controllers).
tests/test_launch.py gains targeted cases for type discriminator resolution, single-class type inference, and duplicate-id rejection (handled by Pydantic's dict[str, ...]).
REST end-to-end test: two controllers wired into one process with distinct route prefixes and no clash.
Parent
#351
What to build
Land the foundational multi-controller mechanics through the REST transport, end-to-end. REST is chosen as the tracer because it has the loosest naming rules and the smallest surface, so the foundation can be exercised without dragging EPICS/Tango/GraphQL specifics into this slice. After this slice, a single FastCS process can serve N controllers over REST with stable id-based routes, with subsequent slices adding the other transports atop the same foundation.
Scope:
Controller.idset exactly once by the launcher between__init__andinitialise(). Reading before set raises a clear runtime error; setting twice raises.Controller.__repr__includes the id when set.fastcs.yamlconfig schema:controllers:is a dict keyed by id, each value carrying atype:discriminator and acontroller:options block. Single-class registration may omittype:. The launcher does not hard-code the filename.launch()accepts a list of Controller classes. Type discriminator value defaults to__name__; optionaltype_name: ClassVar[str]overrides.Transport.connect(controller_apis: list[ControllerAPI], loop)signature applied to every transport. Existing transports (EPICS CA, EPICS PVA, GraphQL, Tango) accept a list-of-one and behave as before — they will adopt true multi-controller in subsequent slices.GET /{id}/{sub}/{attr}for every configured controller. One combined OpenAPI schema describes all controllers.connect()time; illegal characters fail fast with a clear startup error.Controller.__repr__surfaces id.src/fastcs/control_system.py:103) to parallel dicts:controllers: dict[id, Controller]andcontroller_apis: dict[id, ControllerAPI]. This satisfies user story 26 without introducing aController.apiback-pointer, leaving ADR 0006 untouched.type_nameoverrides).tests/test_multi_controller.pywith the lifecycle and api-path scenarios from the PRD's testing decisions (id available frominitialise()onward; raises if read in__init__;set_idraises if called twice;controller_apis["X"].path == [id]for root,[id, sub]for sub-controllers).tests/test_launch.pygains targeted cases for type discriminator resolution, single-class type inference, and duplicate-id rejection (handled by Pydantic'sdict[str, ...]).User stories from #351 covered: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 17, 19, 22, 24, 25, 26, 27, 33.
Acceptance criteria
Controller.idlifecycle implemented with the documented errors;__repr__includes id when setfastcs.yamlschema parses dict-keyed-by-idcontrollers:block withtype:discriminator and single-class inferenceTransport.connect()signature uniformly takeslist[ControllerAPI]across all transportsGET /{id}/{sub}/{attr}for every configured controller with a combined OpenAPI schemacontrollersandcontroller_apisdictstests/test_multi_controller.pycovers the four PRD lifecycle/api-path scenarios via RESTtests/test_launch.pycovers discriminator resolution, single-class inference, duplicate-id rejectionBlocked by
None - can start immediately