Skip to content

Commit feb6490

Browse files
authored
Merge branch 'main' into 306_callbacks_on_change
2 parents ba3900f + c43f71b commit feb6490

9 files changed

+596
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# 3. Controller Initialization on Main Event Loop
2+
3+
Date: 2024-08-01
4+
5+
**Related:** [PR #49](https://github.com/DiamondLightSource/FastCS/pull/49)
6+
7+
## Status
8+
9+
Accepted
10+
11+
## Context
12+
13+
The Backend accepts a pre-created Mapping with no async initialization hook for controllers. Each backend subclass manually orchestrates initialization steps, leading to inconsistent lifecycle patterns. Controllers need a way to perform async setup before their API is exposed to transports.
14+
15+
## Decision
16+
17+
Move initialisation logic into Backend so that it:
18+
- Creates the event loop
19+
- Runs `controller.initialise()` before creating the Mapping
20+
- Creates the Mapping from the initialized controller
21+
- Runs initial tasks including `controller.connect()`
22+
23+
Controller then has two hooks: `initialise()` for pre-API setup (hardware introspection, dynamic attribute creation) and `connect()` for post-API connection logic.
24+
25+
## Consequences
26+
27+
The new design the initialisation of the application is easier to understand and makes the API for writing controllers and transports simpler and more flexible.
28+
29+
### Migration Pattern
30+
31+
For controller developers:
32+
33+
```python
34+
class MyController(Controller):
35+
async def initialise(self) -> None:
36+
# Async setup before API creation (introspect hardware, create attributes)
37+
await self._hardware.connect()
38+
39+
async def connect(self) -> None:
40+
# Async setup after API creation (establish connections)
41+
await self._hardware.initialize()
42+
```
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# 4. Rename Backend to Transport
2+
3+
Date: 2024-11-29
4+
5+
**Related:** [PR #67](https://github.com/DiamondLightSource/FastCS/pull/67)
6+
7+
## Status
8+
9+
Accepted
10+
11+
## Context
12+
13+
The original FastCS architecture used the term "backend" ambiguously to describe both the overall framework that managed controllers and the specific communication protocol implementations (EPICS CA, PVA, REST, Tango). This dual usage created confusion:
14+
15+
- It was unclear whether "backend" referred to the framework itself or the protocol layer
16+
- The terminology didn't clearly differentiate between the abstract framework and the underlying communication mechanisms
17+
- The inheritance pattern made it difficult to compose multiple transports or swap them dynamically
18+
19+
## Decision
20+
21+
Rename "backend" to "transport" for all protocol/communication implementations to clearly differentiate them from the framework.
22+
23+
The term "backend" can now refer to the overall FastCS framework/system, while "transport" specifically refers to protocol implementations (EPICS CA, PVA, REST, GraphQL, Tango). FastCS accepts Transport implementations as plugins, enabling flexible composition and loose coupling.
24+
25+
Key architectural changes:
26+
- Introduce `TransportAdapter` abstract base class with standardized interface
27+
- Move to composition-based architecture where transports are passed to `FastCS` rather than being subclasses
28+
- Introduce `FastCS` class as the programmatic interface for running controllers with transports
29+
- Add `launch()` function as the primary entry point for initializing controllers
30+
31+
## Consequences
32+
33+
### Benefits
34+
35+
- **Clear Terminology:** The separation between framework (backend) and protocol layer (transport) is now explicit
36+
- **Consistent Architecture:** All transports follow the adapter pattern with a standardized interface
37+
- **Flexible Composition:** Transports can be added, removed, or swapped at runtime
38+
- **Improved Extensibility:** Adding new transport protocols is straightforward with the adapter pattern
39+
40+
### Migration Pattern
41+
42+
**Before (Inheritance hierarchy):**
43+
```python
44+
class EpicsBackend(Backend):
45+
def run(self):
46+
# Protocol-specific implementation
47+
48+
fastcs = FastCS(controller) # Tightly coupled to framework
49+
```
50+
51+
**After (Composition with Transport plugins):**
52+
```python
53+
transport = EpicsTransport(controller_api)
54+
fastcs = FastCS(controller, [transport])
55+
```
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# 5. Remove Background Thread from Backend
2+
3+
Date: 2025-01-24
4+
5+
**Related:** [PR #98](https://github.com/DiamondLightSource/FastCS/pull/98)
6+
7+
## Status
8+
9+
Accepted
10+
11+
## Context
12+
13+
The FastCS Backend implementation uses `asyncio.run_coroutine_threadsafe()` to execute controller operations on a background event loop thread managed by `AsyncioDispatcher`. The system needs a simpler concurrency model that uses native async/await patterns and allows transports to manage their own threading if needed.
14+
15+
## Decision
16+
17+
Remove the background thread from Backend, making it fully async, while allowing specific transports to use a background thread if required. Backend should accept an event loop from the caller and use native async/await throughout. Transports that need threading (like Tango) manage their own threading explicitly.
18+
19+
Key architectural changes:
20+
- Backend receives event loop from caller (no background dispatcher)
21+
- Initialization uses `loop.run_until_complete()` instead of cross-thread scheduling
22+
- Backend exposes `async serve()` method using native async/await patterns
23+
- Scan tasks use `Task` objects from `loop.create_task()` instead of `Future` objects
24+
- Transports that need threading create their own `AsyncioDispatcher` when needed
25+
26+
## Consequences
27+
28+
### Benefits
29+
30+
- **Simpler Concurrency Model:** Single event loop for most operations, no cross-thread coordination needed
31+
- **Task-Based API:** Consistent use of `Task` objects with standard cancellation support
32+
- **Composability:** `async serve()` can be composed with other async operations
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# 6. Create ControllerAPI Abstraction Layer
2+
3+
Date: 2025-03-10
4+
5+
**Related:** [PR #87](https://github.com/DiamondLightSource/FastCS/pull/87)
6+
7+
## Status
8+
9+
Accepted
10+
11+
## Context
12+
13+
Transports currently access `Controller` instances directly to extract attributes, methods, and metadata for serving over their protocols. This creates a few problems:
14+
15+
- **Tight Coupling:** Transports are coupled to internal Controller structure, making evolution difficult
16+
- **Code Duplication:** Every transport re-implemented similar traversal logic for discovering attributes and methods
17+
- **No Encapsulation:** Transports have direct access to mutable controller state
18+
- **No Static View:** No complete, immutable snapshot of controller API after initialization
19+
20+
## Decision
21+
22+
Introduce `ControllerAPI` as an abstraction layer that provides transports with a complete, static, read-only representation of a controller's capabilities after initialization.
23+
24+
All transports now work with `ControllerAPI` instead of direct `Controller` access. A single `create_controller_api()` function handles all API extraction, replaces custom traversal logic in each transport.
25+
26+
Key architectural changes:
27+
- `ControllerAPI` dataclass represents the complete, hierarchical structure of what a controller exposes
28+
- Separate dictionaries for attributes, command_methods, put_methods, and scan_methods
29+
- `walk_api()` method provides depth-first traversal of the API tree
30+
- Backend creates ControllerAPI during initialization and passes to transports
31+
32+
## Consequences
33+
34+
### Benefits
35+
36+
- **Encapsulation:** Transports work with read-only API, cannot modify controller internals
37+
- **Single Source of Truth:** One canonical representation of controller capabilities
38+
- **Reduced Code Duplication:** Traversal and extraction logic written once, used by all transports
39+
- **Separation of Concerns:** Controllers focus on device logic, ControllerAPI handles representation, transports focus on protocol
40+
- **Testability:** Transports can be tested with synthetic ControllerAPIs; controllers tested independently
41+
- **Evolution Independence:** Controller internals can change without affecting transports
42+
43+
### Migration Pattern
44+
45+
**Before (Direct Controller access):**
46+
```python
47+
class EpicsCAIOC:
48+
def __init__(self, pv_prefix: str, controller: Controller):
49+
# Each transport traverses controller itself
50+
for attr_name in dir(controller):
51+
attr = getattr(controller, attr_name)
52+
if isinstance(attr, Attribute):
53+
self._create_pv(f"{pv_prefix}{attr_name}", attr)
54+
```
55+
56+
**After (ControllerAPI abstraction):**
57+
```python
58+
class EpicsCAIOC:
59+
def __init__(self, pv_prefix: str, controller_api: ControllerAPI):
60+
# Transport receives ready-to-use API structure
61+
for attr_name, attr in controller_api.attributes.items():
62+
self._create_pv(f"{pv_prefix}{attr_name}", attr)
63+
64+
# Walk sub-controllers using standard method
65+
for sub_api in controller_api.walk_api():
66+
# Process sub-controllers with consistent structure
67+
```
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# 7. Merge TransportAdapter and TransportOptions into Transport
2+
3+
Date: 2025-09-29
4+
5+
**Related:** [PR #220](https://github.com/DiamondLightSource/FastCS/pull/220)
6+
7+
## Status
8+
9+
Accepted
10+
11+
## Context
12+
13+
FastCS transports are implemented using two separate classes: `TransportAdapter` for implementation and separate `*Options` classes for configuration. This pattern requires:
14+
15+
- Two classes per transport
16+
- Pattern matching logic in FastCS to create the right adapter from options
17+
- Inconsistent constructor signatures across transports
18+
- Redundant Options classes that only carry configuration data
19+
20+
## Decision
21+
22+
Merge `TransportAdapter` and `*Options` classes into a single `Transport` dataclass that combines configuration and implementation.
23+
24+
All transports should follow a unified pattern: configuration fields are dataclass attributes, and `connect()` and `serve()` methods handle initialization and execution. FastCS accepts Transport instances directly.
25+
26+
Key architectural changes:
27+
- All transports use `@dataclass` decorator combining configuration and implementation
28+
- Standardized `connect(controller_api, loop)` method to load controller API into transport
29+
- Standardized `async serve()` method for running the transport service
30+
31+
## Consequences
32+
33+
### Benefits
34+
35+
- **Reduced API Surface:** 5 classes instead of 10 (one Transport per protocol)
36+
- **Simpler Mental Model:** Configuration and implementation in one place
37+
- **Consistent Interface:** All transports follow same initialization pattern
38+
39+
### Migration Pattern
40+
41+
**Before (Options + Adapter pattern):**
42+
```python
43+
# Configuration separate from implementation
44+
@dataclass
45+
class MyOptions:
46+
param1: str
47+
param2: int = 42
48+
49+
class MyTransport(TransportAdapter):
50+
def __init__(self, controller_api: ControllerAPI, options: MyOptions):
51+
self._options = options
52+
# Setup using self._options.param1
53+
```
54+
55+
**After (Unified Transport):**
56+
```python
57+
# Configuration and implementation unified
58+
@dataclass
59+
class MyTransport(Transport):
60+
param1: str
61+
param2: int = 42
62+
63+
def connect(self, controller_api: ControllerAPI, loop: asyncio.AbstractEventLoop):
64+
self._controller_api = controller_api
65+
# Setup using self.param1 directly
66+
```
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# 8. Split Transport Dependencies into Optional Extras
2+
3+
Date: 2025-09-30
4+
5+
**Related:** [PR #221](https://github.com/DiamondLightSource/FastCS/pull/221)
6+
7+
## Status
8+
9+
Accepted
10+
11+
## Context
12+
13+
Currently all transport dependencies are installed regardless of which transports users actually needed.
14+
15+
Problems with required dependencies:
16+
- Minimal installations bloated by unused transport dependencies
17+
- Unclear dependency relationships for each transport
18+
- No way to install just the core FastCS functionality
19+
20+
## Decision
21+
22+
Split transport dependencies into optional extras in `pyproject.toml`, allowing users to install only what they need.
23+
24+
The core FastCS package now requires only essential dependencies (pydantic, numpy, ruamel.yaml, IPython). Each transport is available as an optional extra, with convenience groups like `[all]`, `[epics]`, and `[dev]` for common installation patterns.
25+
26+
Key architectural changes:
27+
- Core dependencies: pydantic, numpy, ruamel.yaml, IPython
28+
- Individual transport extras: `[epicsca]`, `[epicspva]`, `[tango]`, `[graphql]`, `[rest]`
29+
- Convenience groups: `[epics]`, `[all]`, `[dev]`, `[demo]`
30+
- Each transport declares its own dependencies explicitly
31+
32+
## Consequences
33+
34+
### Benefits
35+
36+
- **Minimal Core Installation:** Users can install FastCS core without transport dependencies
37+
- **Explicit Dependency Relationships:** Each transport declares what it needs
38+
- **Flexible Installation:** Users choose exactly what they need: `pip install fastcs[epicspva,rest]`
39+
- **Development Convenience:** `pip install fastcs[dev]` includes everything for development
40+
- **Clear Documentation:** Installation commands are self-documenting
41+
42+
### Installation Patterns
43+
44+
**Minimal (core only):**
45+
```bash
46+
pip install fastcs
47+
```
48+
49+
**Single transport:**
50+
```bash
51+
pip install fastcs[epicspva] # EPICS PVA
52+
pip install fastcs[rest] # REST API
53+
```
54+
55+
**Multiple transports:**
56+
```bash
57+
pip install fastcs[epics,rest] # EPICS CA + PVA + REST
58+
```
59+
60+
**All transports:**
61+
```bash
62+
pip install fastcs[all]
63+
```
64+
65+
**Development:**
66+
```bash
67+
pip install fastcs[dev]
68+
```

0 commit comments

Comments
 (0)