DiamondLightSource / DiamondLightSource/fastcs
Plugin mechanism for `Controller` subclasses via entry points
- Dominant language
- Python
- Stars
- 6
- Forks
- 8
- Avg merge
- 2d 12h
- Merged PRs (30d)
- 3
Description
After #360 we should do this
## Summary
We need a way for external packages (e.g. `fastcs_foo`) to register `Controller` subclasses so that `fastcs` can discover and instantiate them dynamically (e.g. from YAML configuration), without requiring explicit imports of those packages.
This would enable a proper plugin ecosystem for controllers.
---
## Proposed approach
Use Python packaging **entry points** as an extension mechanism.
- Define an entry point group: `fastcs.controllers`
- Each plugin package registers its `Controller` subclasses there
- `fastcs` discovers them at runtime using `importlib.metadata.entry_points`
---
## Example plugin (`fastcs_foo`)
### Implementation
```python
# fastcs_foo/foo_controller.py
from fastcs.controller import Controller
class FooController(Controller):
def run(self):
print(f"Foo running with {self.config}")
```
### Registration
```toml
# pyproject.toml
[project.entry-points."fastcs.controllers"]
FooController = "fastcs_foo.foo_controller:FooController"
```
---
## YAML configuration
Controllers are referenced by:
```
.
```
Example:
```yaml
controllers:
- type: fastcs_foo.FooController
config:
speed: 10
```
---
## Discovery + resolution (in `fastcs`)
```python
from importlib.metadata import entry_points
from fastcs.controller import Controller
def discover_controllers():
registry = {}
for ep in entry_points(group="fastcs.controllers"):
cls = ep.load()
namespace = cls.__module__.split(".", 1)[0]
name = ep.name
registry[(namespace, name)] = cls
return registry
def resolve_controller(type_string: str):
namespace, class_name = type_string.split(".", 1)
registry = discover_controllers()
key = (namespace, class_name)
if key not in registry:
raise ValueError(f"Unknown controller '{type_string}'")
cls = registry[key]
if not issubclass(cls, Controller):
raise TypeError(f"{type_string} is not a Controller subclass")
return cls
```
---
## Benefits
- No explicit imports required
- Clean separation between core and plugins
- Works with standard Python packaging
- Controllers can live in independently installable packages
- YAML remains simple and explicit (`fastcs_foo.FooController`)
---
## Notes / future considerations
- Could validate distribution name vs module root more strictly
- Could support aliases or versioning
- Could cache discovery results for performance
---
## Outcome
This would provide a robust, extensible plugin system for `Controller` implementations, similar to how pytest and other tools manage plugins.
Contributor guide
Assessment
This issue has not been assessed yet.