From 150bff750a422b89569acc14aa11f426e6d92cd1 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 3 Dec 2024 08:44:27 +0000 Subject: [PATCH] Use pyright instead of mypy --- .copier-answers.yml | 2 +- pyproject.toml | 11 ++-- src/fastcs_odin/__main__.py | 22 ++------ src/fastcs_odin/odin_adapter_controller.py | 13 +++-- src/fastcs_odin/odin_data.py | 64 +++++++++++----------- src/fastcs_odin/util.py | 18 +++++- tests/test_controllers.py | 5 +- 7 files changed, 74 insertions(+), 61 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 523031f..058748a 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -15,4 +15,4 @@ github_org: DiamondLightSource package_name: fastcs_odin pypi: true repo_name: fastcs-odin -type_checker: mypy +type_checker: pyright diff --git a/pyproject.toml b/pyproject.toml index 4d77958..9d6548c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", @@ -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 @@ -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 """ diff --git a/src/fastcs_odin/__main__.py b/src/fastcs_odin/__main__.py index 3413e8f..b18b648 100644 --- a/src/fastcs_odin/__main__.py +++ b/src/fastcs_odin/__main__.py @@ -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 @@ -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 diff --git a/src/fastcs_odin/odin_adapter_controller.py b/src/fastcs_odin/odin_adapter_controller.py index 9fb837a..534752a 100644 --- a/src/fastcs_odin/odin_adapter_controller.py +++ b/src/fastcs_odin/odin_adapter_controller.py @@ -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 @@ -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( @@ -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: @@ -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 = [] @@ -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) @@ -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 diff --git a/src/fastcs_odin/odin_data.py b/src/fastcs_odin/odin_data.py index da36789..a08a3f0 100644 --- a/src/fastcs_odin/odin_data.py +++ b/src/fastcs_odin/odin_data.py @@ -1,9 +1,8 @@ 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 ( @@ -11,7 +10,7 @@ OdinAdapterController, StatusSummaryUpdater, ) -from fastcs_odin.util import OdinParameter, partition +from fastcs_odin.util import OdinParameter, get_all_sub_controllers, partition class OdinDataController(OdinAdapterController): @@ -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( @@ -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(), @@ -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) diff --git a/src/fastcs_odin/util.py b/src/fastcs_odin/util.py index 310e2f1..59c861c 100644 --- a/src/fastcs_odin/util.py +++ b/src/fastcs_odin/util.py @@ -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 + def is_metadata_object(v: Any) -> bool: return isinstance(v, dict) and "writeable" in v and "type" in v @@ -146,3 +148,17 @@ def partition( falsy.append(parameter) return truthy, falsy + + +def get_all_sub_controllers( + controller: BaseController, +) -> list[BaseController]: + return list(_walk_sub_controllers(controller)) + + +def _walk_sub_controllers( + controller: BaseController, +) -> Iterable[BaseController]: + for sub_controller in controller.get_sub_controllers().values(): + yield sub_controller + yield from _walk_sub_controllers(sub_controller) diff --git a/tests/test_controllers.py b/tests/test_controllers.py index fc78b36..71853c4 100644 --- a/tests/test_controllers.py +++ b/tests/test_controllers.py @@ -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"], @@ -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)