Skip to content

Commit f1533de

Browse files
committed
Add fastcs launcher
1 parent 86e155c commit f1533de

File tree

5 files changed

+111
-36
lines changed

5 files changed

+111
-36
lines changed

src/fastcs/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88

99
from ._version import __version__
1010
from .main import FastCS as FastCS
11+
from .main import launch as launch
1112

1213
__all__ = ["__version__"]

src/fastcs/controller.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
from copy import copy
4+
from typing import Any
45

56
from .attributes import Attribute
67

@@ -55,6 +56,9 @@ class Controller(BaseController):
5556
def __init__(self) -> None:
5657
super().__init__()
5758

59+
def startup(self, config: Any) -> None:
60+
pass
61+
5862
async def initialise(self) -> None:
5963
pass
6064

src/fastcs/main.py

Lines changed: 100 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
from dataclasses import dataclass
2-
from typing import Any
1+
import json
2+
from pathlib import Path
3+
from typing import Annotated, Any, get_type_hints
4+
5+
import typer
6+
from pydantic import create_model
7+
from ruamel.yaml import YAML
38

49
from .backend import Backend
510
from .controller import Controller
@@ -8,44 +13,114 @@
813
from .transport.tango.options import TangoOptions
914

1015

11-
@dataclass
12-
class FastCSOptions:
13-
transport: EpicsOptions | TangoOptions
14-
controller: Any = None
15-
16-
1716
class FastCS:
1817
def __init__(
1918
self,
20-
controller: Controller,
21-
options: FastCSOptions,
19+
controller: type[Controller],
20+
controller_options: Any,
21+
transport_options: EpicsOptions | TangoOptions,
2222
):
23-
self.backend = Backend(controller)
24-
self.transport: TransportAdapter
25-
match options.transport:
23+
self._controller = controller()
24+
self._controller.startup(controller_options)
25+
26+
self._backend = Backend(self._controller)
27+
self._transport: TransportAdapter
28+
match transport_options:
2629
case TangoOptions():
2730
from .transport.tango.adapter import TangoTransport
2831

29-
self.transport = TangoTransport(
30-
self.backend.mapping,
31-
options.transport,
32+
self._transport = TangoTransport(
33+
self._backend.mapping,
34+
transport_options,
3235
)
3336
case EpicsOptions():
3437
from .transport.epics.adapter import EpicsTransport
3538

36-
self.transport = EpicsTransport(
37-
self.backend.mapping,
38-
self.backend.context,
39-
self.backend.dispatcher,
40-
options.transport,
39+
self._transport = EpicsTransport(
40+
self._backend.mapping,
41+
self._backend.context,
42+
self._backend.dispatcher,
43+
transport_options,
4144
)
4245

4346
def create_docs(self) -> None:
44-
self.transport.create_docs()
47+
self._transport.create_docs()
4548

4649
def create_gui(self) -> None:
47-
self.transport.create_gui()
50+
self._transport.create_gui()
4851

4952
def run(self) -> None:
50-
self.backend.run()
51-
self.transport.run()
53+
self._backend.run()
54+
self._transport.run()
55+
56+
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
65+
66+
fastcs_options = create_model(
67+
f"{controller_class.__name__}",
68+
controller=(options_type, ...),
69+
transport=(EpicsOptions | TangoOptions, ...),
70+
)
71+
72+
_launch_typer = typer.Typer()
73+
74+
class _LaunchContext:
75+
def __init__(self, controller_class, fastcs_options):
76+
self.controller_class = controller_class
77+
self.fastcs_options = fastcs_options
78+
79+
@_launch_typer.callback()
80+
def create_context(ctx: typer.Context):
81+
ctx.obj = _LaunchContext(
82+
controller_class,
83+
fastcs_options,
84+
)
85+
86+
@_launch_typer.command(
87+
help=f"Produce json schema for a {controller_class.__name__}"
88+
)
89+
def schema(ctx: typer.Context):
90+
system_schema = ctx.obj.fastcs_options.model_json_schema()
91+
print(json.dumps(system_schema, indent=2))
92+
93+
@_launch_typer.command(help=f"Start up a {controller_class.__name__}")
94+
def run(
95+
ctx: typer.Context,
96+
config: Annotated[
97+
Path,
98+
typer.Argument(
99+
help=f"A yaml file matching the {controller_class.__name__} schema"
100+
),
101+
],
102+
):
103+
"""
104+
Start the controller
105+
"""
106+
controller_class = ctx.obj.controller_class
107+
fastcs_options = ctx.obj.fastcs_options
108+
109+
yaml = YAML(typ="safe")
110+
options_yaml = yaml.load(config)
111+
# To do: Handle a k8s "values.yaml" file
112+
instance_options = fastcs_options.parse_obj(options_yaml)
113+
114+
instance = FastCS(
115+
controller_class,
116+
instance_options.controller,
117+
instance_options.transport,
118+
)
119+
120+
if hasattr(instance_options.transport, "gui"):
121+
instance.create_gui()
122+
if hasattr(instance_options.transport, "docs"):
123+
instance.create_docs()
124+
instance.run()
125+
126+
_launch_typer()

src/fastcs/transport/adapter.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
11
from abc import ABC, abstractmethod
2-
from dataclasses import dataclass
3-
4-
5-
@dataclass
6-
class TransportOptions:
7-
pass
82

93

104
class TransportAdapter(ABC):

tests/ioc.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from fastcs.attributes import AttrR, AttrRW, AttrW
22
from fastcs.controller import Controller, SubController
33
from fastcs.datatypes import Int
4-
from fastcs.main import FastCS, FastCSOptions
4+
from fastcs.main import FastCS
55
from fastcs.transport.epics.options import EpicsIOCOptions, EpicsOptions
66
from fastcs.wrappers import command
77

@@ -10,6 +10,9 @@ class ParentController(Controller):
1010
a: AttrR = AttrR(Int())
1111
b: AttrRW = AttrRW(Int())
1212

13+
def startup(self, config):
14+
self.register_sub_controller("Child", ChildController())
15+
1316

1417
class ChildController(SubController):
1518
c: AttrW = AttrW(Int())
@@ -20,10 +23,8 @@ async def d(self):
2023

2124

2225
def run():
23-
controller = ParentController()
24-
controller.register_sub_controller("Child", ChildController())
25-
options = EpicsOptions(ioc=EpicsIOCOptions(pv_prefix="DEVICE"))
26-
fastcs = FastCS(controller, FastCSOptions(options))
26+
epics_options = EpicsOptions(ioc=EpicsIOCOptions(pv_prefix="DEVICE"))
27+
fastcs = FastCS(ParentController, None, epics_options)
2728
fastcs.run()
2829

2930

0 commit comments

Comments
 (0)