Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,27 @@
("py:class", "'object'"),
("py:class", "'id'"),
("py:class", "typing_extensions.Literal"),
# Doesn't work even with asyncio intersphinx mapping
("py:class", "asyncio.events.AbstractEventLoop"),
("py:class", "asyncio.streams.StreamReader"),
("py:class", "asyncio.streams.StreamWriter"),
# Annoying error:
# docstring of collections.abc.Callable:1: WARNING:
# 'any' reference target not found: self [ref.any]
("any", "self"),
# p4p doesn't have intersphinx mapping
("py:class", "p4p.server.StaticProvider"),
("py:class", "p4p.nt.scalar.NTScalar"),
("py:class", "p4p.nt.enum.NTEnum"),
("py:class", "p4p.nt.ndarray.NTNDArray"),
("py:class", "p4p.nt.NTTable"),
# Problems in FastCS itself
("py:class", "fastcs.transport.epics.pva.pvi_tree._PviSignalInfo"),
# TypeVar without docstrings still give warnings
("py:class", "fastcs.datatypes.T_Numerical"),
("py:class", "strawberry.schema.schema.Schema"),
]
nitpick_ignore_regex = [
("py:class", "fastcs.*.T"),
]
nitpick_ignore_regex = [("py:class", "fastcs.*.T")]

# Both the class’ and the __init__ method’s docstring are concatenated and
# inserted into the main body of the autoclass directive
Expand Down Expand Up @@ -112,7 +129,9 @@

# This means you can link things like `str` and `asyncio` to the relevant
# docs in the python documentation.
intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)}
intersphinx_mapping = {
"python": ("https://docs.python.org/3/", None),
}

# A dictionary of graphviz graph attributes for inheritance diagrams.
inheritance_graph_attrs = {"rankdir": "TB"}
Expand Down
10 changes: 6 additions & 4 deletions src/fastcs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
Version number as calculated by https://github.com/pypa/setuptools_scm
"""

from ._version import __version__
from . import attributes as attributes
from . import controller as controller
from . import cs_methods as cs_methods
from . import datatypes as datatypes
from . import transport as transport
from ._version import __version__ as __version__
from .launch import FastCS as FastCS
from .launch import launch as launch

__all__ = ["__version__"]
14 changes: 11 additions & 3 deletions src/fastcs/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from enum import Enum
from typing import Any, Generic, Protocol, runtime_checkable

import fastcs

from .datatypes import ATTRIBUTE_TYPES, AttrCallback, DataType, T


Expand All @@ -20,7 +22,9 @@ class AttrMode(Enum):
class Sender(Protocol):
"""Protocol for setting the value of an ``Attribute``."""

async def put(self, controller: Any, attr: AttrW, value: Any) -> None:
async def put(
self, controller: fastcs.controller.BaseController, attr: AttrW, value: Any
) -> None:
pass


Expand All @@ -31,7 +35,9 @@ class Updater(Protocol):
# If update period is None then the attribute will not be updated as a task.
update_period: float | None = None

async def update(self, controller: Any, attr: AttrR) -> None:
async def update(
self, controller: fastcs.controller.BaseController, attr: AttrR
) -> None:
pass


Expand All @@ -45,7 +51,9 @@ class Handler(Sender, Updater, Protocol):
class SimpleHandler(Handler):
"""Handler for internal parameters"""

async def put(self, controller: Any, attr: AttrW, value: Any):
async def put(
self, controller: fastcs.controller.BaseController, attr: AttrW, value: Any
):
await attr.update_display_without_process(value)

if isinstance(attr, AttrRW):
Expand Down
3 changes: 3 additions & 0 deletions src/fastcs/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@


class Backend:
"""For keeping track of tasks during FastCS serving."""

def __init__(
self,
controller: Controller,
Expand Down Expand Up @@ -163,6 +165,7 @@ async def scan_coro() -> None:


def build_controller_api(controller: Controller) -> ControllerAPI:
"""Build a `ControllerAPI` for a `BaseController` and its sub controllers"""
return _build_controller_api(controller, [])


Expand Down
6 changes: 6 additions & 0 deletions src/fastcs/connections/ip_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@


class DisconnectedError(Exception):
"""Raised if the ip connection is disconnected."""

pass


Expand All @@ -14,6 +16,8 @@ class IPConnectionSettings:

@dataclass
class StreamConnection:
"""For reading and writing to a stream."""

reader: asyncio.StreamReader
writer: asyncio.StreamWriter

Expand Down Expand Up @@ -41,6 +45,8 @@ async def close(self):


class IPConnection:
"""For connecting to an ip using a `StreamConnection`."""

def __init__(self):
self.__connection = None

Expand Down
4 changes: 4 additions & 0 deletions src/fastcs/connections/serial_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@


class NotOpenedError(Exception):
"""If the serial stream is not opened."""

pass


Expand All @@ -15,6 +17,8 @@ class SerialConnectionSettings:


class SerialConnection:
"""A serial connection."""

def __init__(self):
self.stream = None
self._lock = asyncio.Lock()
Expand Down
4 changes: 3 additions & 1 deletion src/fastcs/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@


class BaseController:
"""Base class for controller."""

#: Attributes passed from the device at runtime.
attributes: dict[str, Attribute]

Expand All @@ -29,7 +31,7 @@ def __init__(

@property
def path(self) -> list[str]:
"""Path prefix of attributes, recursively including parent ``Controller``s."""
"""Path prefix of attributes, recursively including parent Controllers."""
return self._path

def set_path(self, path: list[str]):
Expand Down
6 changes: 3 additions & 3 deletions src/fastcs/controller_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ class ControllerAPI:
description: str | None = None

def walk_api(self) -> Iterator["ControllerAPI"]:
"""Walk through all the nested `ControllerAPIs` of this `ControllerAPI`
"""Walk through all the nested `ControllerAPI` s of this `ControllerAPI`.

yields: `ControllerAPI`s from a depth-first traversal of the tree, including
self.
Yields the `ControllerAPI` s from a depth-first traversal of the tree,
including self.

"""
yield self
Expand Down
8 changes: 6 additions & 2 deletions src/fastcs/cs_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ async def __call__(self):


class Put(Method[BaseController]):
"""Why don't know what this is for."""

def __init__(self, fn: PutCallback):
super().__init__(fn)

Expand All @@ -142,7 +144,7 @@ class UnboundCommand(Method[Controller_T]):
This generic class stores an unbound `Controller` method - effectively a function
that takes an instance of a specific `Controller` type (`Controller_T`). Instances
of this class can be added at `Controller` definition, either manually or with use
of the `@command` wrapper, to register the method to be included in the API of the
of the `command` wrapper, to register the method to be included in the API of the
`Controller`. When the `Controller` is instantiated, these instances will be bound
to the instance, creating a `Command` instance.
"""
Expand Down Expand Up @@ -171,7 +173,7 @@ class UnboundScan(Method[Controller_T]):
This generic class stores an unbound `Controller` method - effectively a function
that takes an instance of a specific `Controller` type (`Controller_T`). Instances
of this class can be added at `Controller` definition, either manually or with use
of the `@scan` wrapper, to register the method to be included in the API of the
of the `scan` wrapper, to register the method to be included in the API of the
`Controller`. When the `Controller` is instantiated, these instances will be bound
to the instance, creating a `Scan` instance.
"""
Expand Down Expand Up @@ -199,6 +201,8 @@ def __call__(self):


class UnboundPut(Method[Controller_T]):
"""Unbound version of `Put`."""

def __init__(self, fn: UnboundPutCallback[Controller_T]) -> None:
super().__init__(fn)

Expand Down
6 changes: 4 additions & 2 deletions src/fastcs/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
class FastCSException(Exception):
pass
"""Base class for general problems in the running of a FastCS transport."""


class LaunchError(FastCSException):
pass
"""For when there is an error in launching FastCS with the given
transports and controller.
"""
3 changes: 3 additions & 0 deletions src/fastcs/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@


class FastCS:
"""For launching a controller with given transport(s)."""

def __init__(
self,
controller: Controller,
Expand Down Expand Up @@ -249,6 +251,7 @@ def _extract_options_model(controller_class: type[Controller]) -> type[BaseModel


def get_controller_schema(target: type[Controller]) -> dict[str, Any]:
"""Gets schema for a give controller for serialisation."""
options_model = _extract_options_model(target)
target_schema = options_model.model_json_schema()
return target_schema
3 changes: 3 additions & 0 deletions src/fastcs/transport/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@


class TransportAdapter(ABC):
"""A base class for adapting a transport's implementation to
so it can be used in FastCS."""

@property
@abstractmethod
def options(self) -> Any:
Expand Down
2 changes: 2 additions & 0 deletions src/fastcs/transport/epics/ca/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@


class EpicsCATransport(TransportAdapter):
"""Channel access transport."""

def __init__(
self,
controller_api: ControllerAPI,
Expand Down
5 changes: 5 additions & 0 deletions src/fastcs/transport/epics/ca/ioc.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@


class EpicsCAIOC:
"""A softioc which handles a controller.

Avoid running directly, instead use `fastcs.launch.FastCS`.
"""

def __init__(
self,
pv_prefix: str,
Expand Down
2 changes: 2 additions & 0 deletions src/fastcs/transport/epics/ca/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

@dataclass
class EpicsCAOptions:
"""Options for the EPICS CA transport."""

docs: EpicsDocsOptions = field(default_factory=EpicsDocsOptions)
gui: EpicsGUIOptions = field(default_factory=EpicsGUIOptions)
ioc: EpicsIOCOptions = field(default_factory=EpicsIOCOptions)
8 changes: 8 additions & 0 deletions src/fastcs/transport/epics/ca/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,15 @@
def record_metadata_from_attribute(
attribute: Attribute[T],
) -> dict[str, str | None]:
"""Converts attributes on the `Attribute` to the
field name/value in the record metadata."""
return {"DESC": attribute.description}


def record_metadata_from_datatype(datatype: DataType[T]) -> dict[str, str]:
"""Converts attributes on the `DataType` to the
field name/value in the record metadata."""

arguments = {
DATATYPE_FIELD_TO_RECORD_FIELD[field]: value
for field, value in asdict(datatype).items()
Expand Down Expand Up @@ -78,6 +83,7 @@ def record_metadata_from_datatype(datatype: DataType[T]) -> dict[str, str]:


def cast_from_epics_type(datatype: DataType[T], value: object) -> T:
"""Casts from an EPICS datatype to a FastCS datatype."""
match datatype:
case Enum():
return datatype.validate(datatype.members[value])
Expand All @@ -88,6 +94,7 @@ def cast_from_epics_type(datatype: DataType[T], value: object) -> T:


def cast_to_epics_type(datatype: DataType[T], value: T) -> object:
"""Casts from an attribute's datatype to an EPICS datatype."""
match datatype:
case Enum():
return datatype.index_of(datatype.validate(value))
Expand All @@ -100,6 +107,7 @@ def cast_to_epics_type(datatype: DataType[T], value: T) -> object:
def builder_callable_from_attribute(
attribute: AttrR | AttrW | AttrRW, make_in_record: bool
):
"""Returns a callable to make the softioc record from an attribute instance."""
match attribute.datatype:
case Bool():
return builder.boolIn if make_in_record else builder.boolOut
Expand Down
2 changes: 2 additions & 0 deletions src/fastcs/transport/epics/docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@


class EpicsDocs:
"""For creating docs in the EPICS transports."""

def __init__(self, controller_apis: ControllerAPI) -> None:
self._controller_apis = controller_apis

Expand Down
2 changes: 2 additions & 0 deletions src/fastcs/transport/epics/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@


class EpicsGUI:
"""For creating gui in the EPICS transports."""

def __init__(self, controller_api: ControllerAPI, pv_prefix: str) -> None:
self._controller_api = controller_api
self._pv_prefix = pv_prefix
Expand Down
8 changes: 8 additions & 0 deletions src/fastcs/transport/epics/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,30 @@

@dataclass
class EpicsDocsOptions:
"""Docs options for EPICS."""

path: Path = Path(".")
depth: int | None = None


class EpicsGUIFormat(Enum):
"""The format of an EPICS GUI."""

bob = ".bob"
edl = ".edl"


@dataclass
class EpicsGUIOptions:
"""Epics GUI options for use in both CA and PVA transports."""

output_path: Path = Path(".") / "output.bob"
file_format: EpicsGUIFormat = EpicsGUIFormat.bob
title: str = "Simple Device"


@dataclass
class EpicsIOCOptions:
"""Epics IOC options for use in both CA and PVA transports."""

pv_prefix: str = "MY-DEVICE-PREFIX"
2 changes: 2 additions & 0 deletions src/fastcs/transport/epics/pva/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@


class EpicsPVATransport(TransportAdapter):
"""PV access transport."""

def __init__(
self,
controller_api: ControllerAPI,
Expand Down
Loading
Loading