Skip to content

Commit 6426fea

Browse files
committed
Use controller __init__ hinting for launcher
1 parent f1533de commit 6426fea

File tree

3 files changed

+70
-27
lines changed

3 files changed

+70
-27
lines changed

src/fastcs/controller.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,6 @@ class Controller(BaseController):
5656
def __init__(self) -> None:
5757
super().__init__()
5858

59-
def startup(self, config: Any) -> None:
60-
pass
61-
6259
async def initialise(self) -> None:
6360
pass
6461

src/fastcs/main.py

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
from inspect import signature
23
from pathlib import Path
34
from typing import Annotated, Any, get_type_hints
45

@@ -16,14 +17,10 @@
1617
class FastCS:
1718
def __init__(
1819
self,
19-
controller: type[Controller],
20-
controller_options: Any,
20+
controller: Controller,
2121
transport_options: EpicsOptions | TangoOptions,
2222
):
23-
self._controller = controller()
24-
self._controller.startup(controller_options)
25-
26-
self._backend = Backend(self._controller)
23+
self._backend = Backend(controller)
2724
self._transport: TransportAdapter
2825
match transport_options:
2926
case TangoOptions():
@@ -54,20 +51,64 @@ def run(self) -> None:
5451
self._transport.run()
5552

5653

57-
def launch(controller_class: type[Controller]):
58-
try:
59-
options_type = get_type_hints(controller_class.startup)["config"]
60-
except KeyError as e:
61-
raise KeyError(
62-
"Missing type hint for config argument, expected: "
63-
"`def startup(self, config: <type hint>)`"
64-
) from e
54+
class LaunchError(Exception):
55+
pass
6556

66-
fastcs_options = create_model(
67-
f"{controller_class.__name__}",
68-
controller=(options_type, ...),
69-
transport=(EpicsOptions | TangoOptions, ...),
70-
)
57+
58+
def launch(controller_class: type[Controller]) -> None:
59+
"""
60+
Serves as an entry point for starting FastCS applications.
61+
62+
By utilizing type hints in a Controller's __init__ method, this
63+
function provides a command-line interface to describe and gather the
64+
required configuration before instantiating the application.
65+
66+
Args:
67+
controller_class (type[Controller]): The FastCS Controller to instantiate.
68+
It must have a type-hinted __init__ method and no more than 2 arguments.
69+
70+
Raises:
71+
LaunchError: If the class's __init__ is not as expected
72+
73+
Example of the expected Controller implementation:
74+
class MyController(Controller):
75+
def __init__(self, my_arg: MyControllerOptions) -> None:
76+
...
77+
78+
Typical usage:
79+
if __name__ == "__main__":
80+
launch(MyController)
81+
"""
82+
args_len = controller_class.__init__.__code__.co_argcount
83+
sig = signature(controller_class.__init__)
84+
if args_len == 1:
85+
fastcs_options = create_model(
86+
f"{controller_class.__name__}",
87+
transport=(EpicsOptions | TangoOptions, ...),
88+
__config__={"extra": "forbid"},
89+
)
90+
elif args_len == 2:
91+
hints = get_type_hints(controller_class.__init__)
92+
if "self" in hints:
93+
del hints["self"]
94+
if hints:
95+
options_type = list(hints.values())[-1]
96+
else:
97+
raise LaunchError(
98+
f"Expected typehinting in {controller_class.__name__}"
99+
f".__init__ but received {sig}"
100+
)
101+
fastcs_options = create_model(
102+
f"{controller_class.__name__}",
103+
controller=(options_type, ...),
104+
transport=(EpicsOptions | TangoOptions, ...),
105+
__config__={"extra": "forbid"},
106+
)
107+
else:
108+
raise LaunchError(
109+
f"Expected up to 2 arguments for {controller_class.__name__}.__init__ "
110+
f"but received {args_len} {sig}"
111+
)
71112

72113
_launch_typer = typer.Typer()
73114

@@ -109,11 +150,14 @@ def run(
109150
yaml = YAML(typ="safe")
110151
options_yaml = yaml.load(config)
111152
# To do: Handle a k8s "values.yaml" file
112-
instance_options = fastcs_options.parse_obj(options_yaml)
153+
instance_options = fastcs_options.model_validate(options_yaml)
154+
if hasattr(instance_options, "controller"):
155+
controller = controller_class(instance_options.controller)
156+
else:
157+
controller = controller_class()
113158

114159
instance = FastCS(
115-
controller_class,
116-
instance_options.controller,
160+
controller,
117161
instance_options.transport,
118162
)
119163

tests/ioc.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ class ParentController(Controller):
1010
a: AttrR = AttrR(Int())
1111
b: AttrRW = AttrRW(Int())
1212

13-
def startup(self, config):
13+
def __init__(self):
14+
super().__init__()
1415
self.register_sub_controller("Child", ChildController())
1516

1617

@@ -24,7 +25,8 @@ async def d(self):
2425

2526
def run():
2627
epics_options = EpicsOptions(ioc=EpicsIOCOptions(pv_prefix="DEVICE"))
27-
fastcs = FastCS(ParentController, None, epics_options)
28+
controller = ParentController()
29+
fastcs = FastCS(controller, epics_options)
2830
fastcs.run()
2931

3032

0 commit comments

Comments
 (0)