Skip to content

Commit b7ac911

Browse files
authored
Fix Transport imports (#227)
Make import of all transports conditional Build Union of Transports that get imported Error if no imports are available for now
1 parent 9879f4e commit b7ac911

File tree

5 files changed

+66
-37
lines changed

5 files changed

+66
-37
lines changed

src/fastcs/launch.py

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
from collections.abc import Callable, Coroutine, Sequence
77
from functools import partial
88
from pathlib import Path
9-
from typing import Annotated, Any, Optional, TypeAlias, get_type_hints
9+
from typing import Annotated, Any, Optional, get_type_hints
1010

1111
import typer
1212
from IPython.terminal.embed import InteractiveShellEmbed
13-
from pydantic import BaseModel, create_model
13+
from pydantic import BaseModel, ValidationError, create_model
1414
from ruamel.yaml import YAML
1515

1616
from fastcs import __version__
@@ -26,11 +26,6 @@
2626
)
2727
from fastcs.logging import logger as _fastcs_logger
2828
from fastcs.tracer import Tracer
29-
from fastcs.transport.epics.ca.transport import EpicsCATransport
30-
from fastcs.transport.epics.pva.transport import EpicsPVATransport
31-
from fastcs.transport.graphql.transport import GraphQLTransport
32-
from fastcs.transport.rest.transport import RestTransport
33-
from fastcs.transport.tango.transport import TangoTransport
3429

3530
from .attributes import ONCE, AttrR, AttrW
3631
from .controller import BaseController, Controller
@@ -41,15 +36,6 @@
4136
from .transport import Transport
4237
from .util import validate_hinted_attributes
4338

44-
# Define a type alias for transport options
45-
TransportList: TypeAlias = list[
46-
EpicsPVATransport
47-
| EpicsCATransport
48-
| TangoTransport
49-
| RestTransport
50-
| GraphQLTransport
51-
]
52-
5339
tracer = Tracer(name=__name__)
5440
logger = _fastcs_logger.bind(logger_name=__name__)
5541

@@ -440,8 +426,19 @@ def run(
440426

441427
yaml = YAML(typ="safe")
442428
options_yaml = yaml.load(config)
443-
# To do: Handle a k8s "values.yaml" file
444-
instance_options = fastcs_options.model_validate(options_yaml)
429+
430+
try:
431+
instance_options = fastcs_options.model_validate(options_yaml)
432+
except ValidationError as e:
433+
if any("transport" in error["loc"] for error in json.loads(e.json())):
434+
raise LaunchError(
435+
"Failed to validate transports. "
436+
"Are the correct fastcs extras installed? "
437+
f"Available transports:\n{Transport.subclasses}",
438+
) from e
439+
440+
raise LaunchError("Failed to validate config") from e
441+
445442
if hasattr(instance_options, "controller"):
446443
controller = controller_class(instance_options.controller)
447444
else:
@@ -466,7 +463,7 @@ def _extract_options_model(controller_class: type[Controller]) -> type[BaseModel
466463
if len(args) == 1:
467464
fastcs_options = create_model(
468465
f"{controller_class.__name__}",
469-
transport=(TransportList, ...),
466+
transport=(list[Transport.union()], ...),
470467
__config__={"extra": "forbid"},
471468
)
472469
elif len(args) == 2:
@@ -483,7 +480,7 @@ def _extract_options_model(controller_class: type[Controller]) -> type[BaseModel
483480
fastcs_options = create_model(
484481
f"{controller_class.__name__}",
485482
controller=(options_type, ...),
486-
transport=(TransportList, ...),
483+
transport=(list[Transport.union()], ...),
487484
__config__={"extra": "forbid"},
488485
)
489486
else:

src/fastcs/transport/__init__.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
1-
from .epics.ca.transport import EpicsCATransport as EpicsCATransport
2-
from .epics.options import EpicsDocsOptions as EpicsDocsOptions
3-
from .epics.options import EpicsGUIOptions as EpicsGUIOptions
4-
from .epics.options import EpicsIOCOptions as EpicsIOCOptions
5-
from .epics.pva.transport import EpicsPVATransport as EpicsPVATransport
6-
from .graphql.transport import GraphQLTransport as GraphQLTransport
7-
from .rest.transport import RestTransport as RestTransport
8-
from .tango.options import TangoDSROptions as TangoDSROptions
9-
from .tango.transport import TangoTransport as TangoTransport
101
from .transport import Transport as Transport
2+
3+
try:
4+
from .epics.ca.transport import EpicsCATransport as EpicsCATransport
5+
from .epics.options import EpicsDocsOptions as EpicsDocsOptions
6+
from .epics.options import EpicsGUIOptions as EpicsGUIOptions
7+
from .epics.options import EpicsIOCOptions as EpicsIOCOptions
8+
except ImportError:
9+
pass
10+
11+
try:
12+
from .epics.pva.transport import EpicsPVATransport as EpicsPVATransport
13+
except ImportError:
14+
pass
15+
16+
try:
17+
from .graphql.transport import GraphQLTransport as GraphQLTransport
18+
except ImportError:
19+
pass
20+
21+
try:
22+
from .rest.transport import RestTransport as RestTransport
23+
except ImportError:
24+
pass
25+
26+
try:
27+
from .tango.options import TangoDSROptions as TangoDSROptions
28+
from .tango.transport import TangoTransport as TangoTransport
29+
except ImportError:
30+
pass

src/fastcs/transport/transport.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import asyncio
22
from abc import abstractmethod
33
from dataclasses import dataclass
4-
from typing import Any
4+
from typing import Any, ClassVar, Union
55

66
from fastcs.controller_api import ControllerAPI
77

@@ -11,6 +11,18 @@ class Transport:
1111
"""A base class for transport's implementation
1212
so it can be used in FastCS."""
1313

14+
subclasses: ClassVar[list[type["Transport"]]] = []
15+
16+
def __init_subclass__(cls):
17+
cls.subclasses.append(cls)
18+
19+
@classmethod
20+
def union(cls):
21+
if not cls.subclasses:
22+
raise RuntimeError("No Transports found")
23+
24+
return Union[tuple(cls.subclasses)] # noqa: UP007
25+
1426
@abstractmethod
1527
async def serve(self) -> None:
1628
pass

tests/data/schema.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,19 +208,19 @@
208208
"items": {
209209
"anyOf": [
210210
{
211-
"$ref": "#/$defs/EpicsPVATransport"
211+
"$ref": "#/$defs/EpicsCATransport"
212212
},
213213
{
214-
"$ref": "#/$defs/EpicsCATransport"
214+
"$ref": "#/$defs/EpicsPVATransport"
215215
},
216216
{
217-
"$ref": "#/$defs/TangoTransport"
217+
"$ref": "#/$defs/GraphQLTransport"
218218
},
219219
{
220220
"$ref": "#/$defs/RestTransport"
221221
},
222222
{
223-
"$ref": "#/$defs/GraphQLTransport"
223+
"$ref": "#/$defs/TangoTransport"
224224
}
225225
]
226226
},

tests/test_launch.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
from fastcs.exceptions import FastCSError, LaunchError
2020
from fastcs.launch import (
2121
FastCS,
22-
TransportList,
2322
_launch,
2423
build_controller_api,
2524
get_controller_schema,
2625
launch,
2726
)
27+
from fastcs.transport.transport import Transport
2828
from fastcs.wrappers import command, scan
2929

3030

@@ -61,7 +61,7 @@ def __init__(self, arg: SomeConfig, too_many):
6161
def test_single_arg_schema():
6262
target_model = create_model(
6363
"SingleArg",
64-
transport=(TransportList, ...),
64+
transport=(list[Transport.union()], ...),
6565
__config__={"extra": "forbid"},
6666
)
6767
target_dict = target_model.model_json_schema()
@@ -78,7 +78,7 @@ def test_is_hinted_schema(data):
7878
target_model = create_model(
7979
"IsHinted",
8080
controller=(SomeConfig, ...),
81-
transport=(TransportList, ...),
81+
transport=(list[Transport.union()], ...),
8282
__config__={"extra": "forbid"},
8383
)
8484
target_dict = target_model.model_json_schema()

0 commit comments

Comments
 (0)