Skip to content

Commit

Permalink
Update for new FastCS attributes API
Browse files Browse the repository at this point in the history
Use pyright instead of mypy
  • Loading branch information
jsouter committed Dec 6, 2024
1 parent f76c22f commit 86bd459
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 69 deletions.
2 changes: 1 addition & 1 deletion .copier-answers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ github_org: DiamondLightSource
package_name: fastcs_odin
pypi: true
repo_name: fastcs-odin
type_checker: mypy
type_checker: pyright
11 changes: 6 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ authors = [
[project.optional-dependencies]
dev = [
"copier",
"mypy",
"myst-parser",
"pipdeptree",
"pre-commit",
"pydata-sphinx-theme>=0.12",
"pyright",
"pytest",
"pytest-asyncio",
"pytest-cov",
Expand All @@ -58,8 +58,9 @@ GitHub = "https://github.com/DiamondLightSource/fastcs-odin"
[tool.setuptools_scm]
version_file = "src/fastcs_odin/_version.py"

[tool.mypy]
ignore_missing_imports = true # Ignore missing stubs in imported modules
[tool.pyright]
typeCheckingMode = "standard"
reportMissingImports = false # Ignore missing stubs in imported modules

[tool.pytest.ini_options]
# Run pytest with all our checkers, and don't spam us with massive tracebacks on error
Expand Down Expand Up @@ -92,12 +93,12 @@ passenv = *
allowlist_externals =
pytest
pre-commit
mypy
pyright
sphinx-build
sphinx-autobuild
commands =
pre-commit: pre-commit run --all-files --show-diff-on-failure {posargs}
type-checking: mypy src tests {posargs}
type-checking: pyright src tests {posargs}
tests: pytest --cov=fastcs_odin --cov-report term --cov-report xml:cov.xml {posargs}
docs: sphinx-{posargs:build -EW --keep-going} -T docs build/html
"""
Expand Down
22 changes: 6 additions & 16 deletions src/fastcs_odin/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from typing import Optional

import typer
from fastcs.backends.asyncio_backend import AsyncioBackend
from fastcs.backends.epics.gui import EpicsGUIOptions
from fastcs.connections.ip_connection import IPConnectionSettings
from fastcs.launch import FastCS
from fastcs.transport.epics.options import EpicsGUIOptions, EpicsOptions

from fastcs_odin.odin_controller import OdinController

Expand Down Expand Up @@ -43,25 +43,15 @@ def main(

@app.command()
def ioc(pv_prefix: str = typer.Argument(), ip: str = OdinIp, port: int = OdinPort):
from fastcs.backends.epics.backend import EpicsBackend
# from fastcs.backends.epics.backend import EpicsBackend

controller = OdinController(IPConnectionSettings(ip, port))

backend = EpicsBackend(controller, pv_prefix)
backend.create_gui(
options=EpicsGUIOptions(
options = EpicsOptions(
gui=EpicsGUIOptions(
output_path=Path.cwd() / "odin.bob", title=f"Odin - {pv_prefix}"
)
)
backend.run()


@app.command()
def asyncio(ip: str = OdinIp, port: int = OdinPort):
controller = OdinController(IPConnectionSettings(ip, port))

backend = AsyncioBackend(controller)
backend.run()
FastCS(controller, options)


# test with: python -m fastcs_odin
Expand Down
16 changes: 8 additions & 8 deletions src/fastcs_odin/odin_adapter_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import re
from collections.abc import Callable, Iterable, Sequence
from dataclasses import dataclass
from typing import Any, TypeVar
from typing import Any, Generic, TypeVar

from fastcs.attributes import AttrR, AttrRW, AttrW, Handler, Sender, Updater
from fastcs.controller import BaseController, SubController
Expand All @@ -23,7 +23,7 @@ class AdapterResponseError(Exception): ...
@dataclass
class ParamTreeHandler(Handler):
path: str
update_period: float = 0.2
update_period: float | None = 0.2
allowed_values: dict[int, str] | None = None

async def put(
Expand Down Expand Up @@ -63,7 +63,7 @@ async def update(


@dataclass
class StatusSummaryUpdater(Updater):
class StatusSummaryUpdater(Updater, Generic[T]):
"""Updater to accumulate underlying attributes into a high-level summary.
Args:
Expand All @@ -80,7 +80,7 @@ class StatusSummaryUpdater(Updater):
path_filter: list[str | tuple[str] | re.Pattern]
attribute_name: str
accumulator: Callable[[Iterable[T]], T]
update_period: float = 0.2
update_period: float | None = 0.2

async def update(self, controller: "OdinAdapterController", attr: AttrR):
values = []
Expand All @@ -101,7 +101,7 @@ class ConfigFanSender(Sender):

attributes: list[AttrW]

async def put(self, _controller: "OdinAdapterController", attr: AttrW, value: Any):
async def put(self, _: "OdinAdapterController", attr: AttrW, value: Any): # pyright: ignore[reportIncompatibleMethodOverride]
for attribute in self.attributes:
await attribute.process(value)

Expand All @@ -111,10 +111,11 @@ async def put(self, _controller: "OdinAdapterController", attr: AttrW, value: An

def _filter_sub_controllers(
controller: BaseController, path_filter: Sequence[str | tuple[str] | re.Pattern]
) -> Iterable[SubController]:
) -> Iterable[BaseController]:
sub_controller_map = controller.get_sub_controllers()

if len(path_filter) == 1:
assert isinstance(path_filter[0], str)
yield sub_controller_map[path_filter[0]]
return

Expand Down Expand Up @@ -212,5 +213,4 @@ def _create_attributes(self):
),
group=group,
)

setattr(self, parameter.name.replace(".", ""), attr)
self.attributes[parameter.name.replace(".", "")] = attr
64 changes: 33 additions & 31 deletions src/fastcs_odin/odin_data.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import logging
import re
from collections.abc import Iterable, Sequence
from collections.abc import Sequence

from fastcs.attributes import AttrR, AttrW
from fastcs.controller import BaseController, SubController
from fastcs.datatypes import Bool, Int

from fastcs_odin.odin_adapter_controller import (
ConfigFanSender,
OdinAdapterController,
StatusSummaryUpdater,
)
from fastcs_odin.util import OdinParameter, partition
from fastcs_odin.util import OdinParameter, get_all_sub_controllers, partition


class OdinDataController(OdinAdapterController):
Expand Down Expand Up @@ -68,21 +67,31 @@ def _create_config_fan_attributes(self):
"""Search for config attributes in sub controllers to create fan out PVs."""
parameter_attribute_map: dict[str, tuple[OdinParameter, list[AttrW]]] = {}
for sub_controller in get_all_sub_controllers(self):
for parameter in sub_controller.parameters:
mode, key = parameter.uri[0], parameter.uri[-1]
if mode == "config" and key not in self._unique_config:
try:
attr = getattr(sub_controller, parameter.name)
except AttributeError:
logging.warning(
f"Controller has parameter {parameter}, "
f"but no corresponding attribute {parameter.name}"
)

if parameter.name not in parameter_attribute_map:
parameter_attribute_map[parameter.name] = (parameter, [attr])
else:
parameter_attribute_map[parameter.name][1].append(attr)
match sub_controller:
case OdinAdapterController():
for parameter in sub_controller.parameters:
mode, key = parameter.uri[0], parameter.uri[-1]
if mode == "config" and key not in self._unique_config:
try:
attr = getattr(sub_controller, parameter.name)
if parameter.name not in parameter_attribute_map:
parameter_attribute_map[parameter.name] = (
parameter,
[attr],
)
else:
parameter_attribute_map[parameter.name][1].append(
attr
)
except AttributeError:
logging.warning(
f"Controller has parameter {parameter}, "
f"but no corresponding attribute {parameter.name}"
)
case _:
logging.warning(
f"Subcontroller {sub_controller} not an OdinAdapterController"
)

for parameter, sub_attributes in parameter_attribute_map.values():
setattr(
Expand Down Expand Up @@ -170,7 +179,12 @@ def __parameter_in_plugin(
class FrameProcessorAdapterController(OdinDataAdapterController):
frames_written: AttrR = AttrR(
Int(),
handler=StatusSummaryUpdater([re.compile("FP*"), "HDF"], "frames_written", sum),
handler=StatusSummaryUpdater(
[re.compile("FP*"), "HDF"],
"frames_written",
sum, # type: ignore
# sum does not fit accumulator type signature, casts bools to ints
),
)
writing: AttrR = AttrR(
Bool(),
Expand Down Expand Up @@ -219,15 +233,3 @@ class FrameProcessorDatasetController(OdinAdapterController):
def _process_parameters(self):
for parameter in self.parameters:
parameter.set_path(parameter.uri[3:])


def get_all_sub_controllers(
controller: "OdinAdapterController",
) -> list["OdinAdapterController"]:
return list(_walk_sub_controllers(controller))


def _walk_sub_controllers(controller: BaseController) -> Iterable[SubController]:
for sub_controller in controller.get_sub_controllers().values():
yield sub_controller
yield from _walk_sub_controllers(sub_controller)
18 changes: 17 additions & 1 deletion src/fastcs_odin/util.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from collections.abc import Callable, Iterator, Mapping
from collections.abc import Callable, Iterable, Iterator, Mapping
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, TypeVar

from fastcs.controller import BaseController, SubController


def is_metadata_object(v: Any) -> bool:
return isinstance(v, dict) and "writeable" in v and "type" in v
Expand Down Expand Up @@ -146,3 +148,17 @@ def partition(
falsy.append(parameter)

return truthy, falsy


def get_all_sub_controllers(
controller: BaseController,
) -> list[SubController]:
return list(_walk_sub_controllers(controller))


def _walk_sub_controllers(
controller: BaseController,
) -> Iterable[SubController]:
for sub_controller in controller.get_sub_controllers().values():
yield sub_controller
yield from _walk_sub_controllers(sub_controller)
17 changes: 10 additions & 7 deletions tests/test_controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ def test_create_attributes():

controller._create_attributes()

match controller:
case OdinAdapterController(
read_int=AttrR(datatype=Int()),
write_bool=AttrRW(datatype=Bool()),
group_float=AttrR(datatype=Float(), group="Group"),
):
match controller.attributes:
case {
"read_int": AttrR(datatype=Int()),
"write_bool": AttrRW(datatype=Bool()),
"group_float": AttrR(datatype=Float(), group="Group"),
}:
pass
case _:
pytest.fail("Controller Attributes not as expected")
Expand Down Expand Up @@ -152,6 +152,7 @@ async def test_fp_create_plugin_sub_controllers():
}:
sub_controllers = controllers["HDF"].get_sub_controllers()
assert "DS" in sub_controllers
assert isinstance(sub_controllers["DS"], OdinAdapterController)
assert sub_controllers["DS"].parameters == [
OdinParameter(
uri=["status", "hdf", "dataset", "compressed_size", "compression"],
Expand Down Expand Up @@ -237,7 +238,9 @@ async def test_status_summary_updater(mocker: MockerFixture):
hdf_controller.frames_written.get.return_value = 50

handler = StatusSummaryUpdater(
["OD", ("FP",), re.compile("FP*"), "HDF"], "frames_written", sum
["OD", ("FP",), re.compile("FP*"), "HDF"],
"frames_written",
sum, # type: ignore
)
hdf_controller.frames_written.get.return_value = 50
await handler.update(controller, attr)
Expand Down

0 comments on commit 86bd459

Please sign in to comment.