ophyd-async 0.8.0a2__tar.gz → 0.8.0a3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/workflows/ci.yml +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/PKG-INFO +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/examples/foo_detector.py +1 -1
- ophyd_async-0.8.0a3/docs/explanations/decisions/0009-procedural-vs-declarative-devices.md +140 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/pyproject.toml +2 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/_version.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/__init__.py +7 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_device.py +1 -0
- ophyd_async-0.8.0a3/src/ophyd_async/core/_device_filler.py +269 -0
- ophyd_async-0.8.0a3/src/ophyd_async/core/_readable.py +260 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_utils.py +9 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adaravis/_aravis_io.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adcore/_core_io.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adcore/_single_trigger.py +4 -9
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adkinetix/_kinetix_io.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adpilatus/_pilatus_io.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/advimba/_vimba_io.py +1 -1
- ophyd_async-0.8.0a3/src/ophyd_async/epics/core/__init__.py +26 -0
- {ophyd_async-0.8.0a2/src/ophyd_async/epics/signal → ophyd_async-0.8.0a3/src/ophyd_async/epics/core}/_aioca.py +3 -6
- ophyd_async-0.8.0a3/src/ophyd_async/epics/core/_epics_connector.py +53 -0
- ophyd_async-0.8.0a3/src/ophyd_async/epics/core/_epics_device.py +13 -0
- {ophyd_async-0.8.0a2/src/ophyd_async/epics/signal → ophyd_async-0.8.0a3/src/ophyd_async/epics/core}/_p4p.py +3 -6
- ophyd_async-0.8.0a3/src/ophyd_async/epics/core/_pvi_connector.py +92 -0
- {ophyd_async-0.8.0a2/src/ophyd_async/epics/signal → ophyd_async-0.8.0a3/src/ophyd_async/epics/core}/_signal.py +31 -16
- ophyd_async-0.8.0a2/src/ophyd_async/epics/signal/_common.py → ophyd_async-0.8.0a3/src/ophyd_async/epics/core/_util.py +19 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/demo/_mover.py +4 -5
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/demo/_sensor.py +9 -12
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/eiger/_eiger_io.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/eiger/_odin_io.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/motor.py +4 -5
- ophyd_async-0.8.0a3/src/ophyd_async/epics/signal.py +11 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/fastcs/core.py +2 -2
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/sim/demo/_sim_motor.py +3 -4
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/tango/base_devices/_base_device.py +15 -16
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/tango/demo/_counter.py +6 -16
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/tango/demo/_mover.py +3 -4
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async.egg-info/PKG-INFO +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async.egg-info/SOURCES.txt +10 -7
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/system_tests/epics/eiger/test_eiger_system.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_device_save_loader.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_flyer.py +5 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_mock_signal_backend.py +3 -3
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_readable.py +81 -58
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_signal.py +4 -5
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_soft_signal_backend.py +8 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_subset_enum.py +3 -3
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_utils.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/adaravis/test_aravis.py +3 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/adcore/test_writers.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/adkinetix/test_kinetix.py +3 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/adsimdetector/test_sim.py +7 -3
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/advimba/test_vimba.py +3 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/demo/test_demo.py +5 -2
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/eiger/test_odin_io.py +3 -4
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/pvi/test_pvi.py +33 -4
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/signal/test_common.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/signal/test_signals.py +15 -4
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/test_motor.py +18 -10
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/fastcs/panda/db/panda.db +8 -8
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/fastcs/panda/test_hdf_panda.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/fastcs/panda/test_panda_connect.py +6 -2
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/fastcs/panda/test_panda_control.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/fastcs/panda/test_panda_utils.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/fastcs/panda/test_writer.py +3 -2
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/plan_stubs/test_ensure_connected.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/plan_stubs/test_fly.py +1 -1
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/sim/demo/test_sim_motor.py +2 -2
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/sim/test_sim_detector.py +3 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/tango/test_base_device.py +8 -18
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/tango/test_tango_transport.py +5 -3
- ophyd_async-0.8.0a2/src/ophyd_async/core/_device_filler.py +0 -191
- ophyd_async-0.8.0a2/src/ophyd_async/core/_readable.py +0 -261
- ophyd_async-0.8.0a2/src/ophyd_async/epics/pvi/__init__.py +0 -3
- ophyd_async-0.8.0a2/src/ophyd_async/epics/pvi/_pvi.py +0 -73
- ophyd_async-0.8.0a2/src/ophyd_async/epics/signal/__init__.py +0 -20
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.codecov.yml +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.copier-answers.yml +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.devcontainer/devcontainer.json +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.git-blame-ignore-revs +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/CONTRIBUTING.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/ISSUE_TEMPLATE/issue.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/actions/install_requirements/action.yml +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/dependabot.yml +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/pages/index.html +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/pages/make_switcher.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/workflows/_check.yml +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/workflows/_dist.yml +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/workflows/_docs.yml +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/workflows/_pypi.yml +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/workflows/_release.yml +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/workflows/_test.yml +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/workflows/_tox.yml +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.github/workflows/periodic.yml +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.gitignore +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.mailmap +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/.pre-commit-config.yaml +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/Dockerfile +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/LICENSE +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/README.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/_api.rst +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/_templates/custom-module-template.rst +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/conf.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/examples/epics_demo.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/examples/tango_demo.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/explanations/decisions/0001-record-architecture-decisions.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/explanations/decisions/0002-switched-to-python-copier-template.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/explanations/decisions/0003-ophyd-async-migration.rst +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/explanations/decisions/0004-repository-structure.rst +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/explanations/decisions/0005-respect-black-line-length.rst +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/explanations/decisions/0006-procedural-device-definitions.rst +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/explanations/decisions/0007-subpackage-structure.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/explanations/decisions/0008-signal-types.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/explanations/decisions/COPYME +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/explanations/decisions.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/explanations/design-goals.rst +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/explanations/event-loop-choice.rst +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/explanations/flyscanning.rst +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/explanations.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/genindex.rst +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/how-to/choose-interfaces-for-devices.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/how-to/compound-devices.rst +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/how-to/contribute.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/how-to/make-a-simple-device.rst +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/how-to/make-a-standard-detector.rst +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/how-to/write-tests-for-devices.rst +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/how-to.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/images/ophyd-async-logo.svg +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/images/ophyd-favicon.svg +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/index.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/reference.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/tutorials/installation.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/tutorials/using-existing-devices.rst +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/docs/tutorials.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/setup.cfg +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/__main__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_detector.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_device_save_loader.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_flyer.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_hdf_dataset.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_log.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_mock_signal_backend.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_mock_signal_utils.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_protocol.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_providers.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_signal.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_signal_backend.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_soft_signal_backend.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_status.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/core/_table.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adaravis/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adaravis/_aravis.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adaravis/_aravis_controller.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adcore/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adcore/_core_logic.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adcore/_hdf_writer.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adcore/_utils.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adkinetix/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adkinetix/_kinetix.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adkinetix/_kinetix_controller.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adpilatus/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adpilatus/_pilatus.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adpilatus/_pilatus_controller.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adsimdetector/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adsimdetector/_sim.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/adsimdetector/_sim_controller.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/advimba/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/advimba/_vimba.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/advimba/_vimba_controller.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/demo/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/demo/mover.db +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/demo/sensor.db +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/eiger/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/eiger/_eiger.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/epics/eiger/_eiger_controller.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/fastcs/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/fastcs/odin/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/fastcs/panda/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/fastcs/panda/_block.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/fastcs/panda/_control.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/fastcs/panda/_hdf_panda.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/fastcs/panda/_table.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/fastcs/panda/_trigger.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/fastcs/panda/_utils.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/fastcs/panda/_writer.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/plan_stubs/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/plan_stubs/_ensure_connected.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/plan_stubs/_fly.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/plan_stubs/_nd_attributes.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/py.typed +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/sim/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/sim/demo/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/sim/demo/_pattern_detector/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_detector_writer.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/sim/testing/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/tango/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/tango/base_devices/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/tango/base_devices/_tango_readable.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/tango/demo/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/tango/demo/_detector.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/tango/demo/_tango/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/tango/demo/_tango/_servers.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/tango/signal/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/tango/signal/_signal.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async/tango/signal/_tango_transport.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async.egg-info/dependency_links.txt +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async.egg-info/entry_points.txt +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async.egg-info/requires.txt +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/src/ophyd_async.egg-info/top_level.txt +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/system_tests/epics/eiger/README.md +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/system_tests/epics/eiger/start_iocs_and_run_tests.sh +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/conftest.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_device.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_device_collector.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_log.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_protocol.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_providers.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_status.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_table.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/core/test_watchable_async_status.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/adcore/test_drivers.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/adcore/test_scans.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/adcore/test_single_trigger.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/adpilatus/test_pilatus.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/conftest.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/eiger/test_eiger_controller.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/eiger/test_eiger_detector.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/signal/test_records.db +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/epics/test_areadetector_subclass_naming.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/fastcs/panda/test_seq_table.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/fastcs/panda/test_trigger.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/sim/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/sim/conftest.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/sim/demo/__init__.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/sim/test_pattern_generator.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/sim/test_sim_writer.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/sim/test_streaming_plan.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/tango/test_tango_signals.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/test_cli.py +0 -0
- {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a3}/tests/test_data/test_yaml_save.yml +0 -0
|
@@ -20,7 +20,7 @@ jobs:
|
|
|
20
20
|
if: needs.check.outputs.branch-pr == ''
|
|
21
21
|
strategy:
|
|
22
22
|
matrix:
|
|
23
|
-
runs-on: ["ubuntu-latest"] # can add
|
|
23
|
+
runs-on: ["ubuntu-latest", "windows-latest"] # can add macos-latest
|
|
24
24
|
python-version: ["3.10","3.11"] # 3.12 should be added when p4p is updated
|
|
25
25
|
include:
|
|
26
26
|
# Include one that runs in the dev environment
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ophyd-async
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.0a3
|
|
4
4
|
Summary: Asynchronous Bluesky hardware abstraction code, compatible with control systems like EPICS and Tango
|
|
5
5
|
Author-email: Tom Cobb <tom.cobb@diamond.ac.uk>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# 9. Procedural vs Declarative Devices
|
|
2
|
+
|
|
3
|
+
Date: 01/10/24
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Accepted
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
In [](./0006-procedural-device-definitions.rst) we decided we preferred the procedural approach to devices, because of the issue of applying structure like `DeviceVector`. Since then we have `FastCS` and `Tango` support which use a declarative approach. We need to decide whether we are happy with this situation, or whether we should go all in one way or the other. A suitable test Device would be:
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
class EpicsProceduralDevice(StandardReadable):
|
|
15
|
+
def __init__(self, prefix: str, num_values: int, name="") -> None:
|
|
16
|
+
with self.add_children_as_readables():
|
|
17
|
+
self.value = DeviceVector(
|
|
18
|
+
{
|
|
19
|
+
i: epics_signal_r(float, f"{prefix}Value{i}")
|
|
20
|
+
for i in range(1, num_values + 1)
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
with self.add_children_as_readables(ConfigSignal):
|
|
24
|
+
self.mode = epics_signal_rw(EnergyMode, prefix + "Mode")
|
|
25
|
+
super().__init__(name=name)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
and a Tango/FastCS procedural equivalent would be (if we add support to StandardReadable for Format.HINTED_SIGNAL and Format.CONFIG_SIGNAL annotations):
|
|
29
|
+
```python
|
|
30
|
+
class TangoDeclarativeDevice(StandardReadable, TangoDevice):
|
|
31
|
+
value: Annotated[DeviceVector[SignalR[float]], Format.HINTED_SIGNAL]
|
|
32
|
+
mode: Annotated[SignalRW[EnergyMode], Format.CONFIG_SIGNAL]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
But we could specify the Tango one procedurally (with some slight ugliness around the DeviceVector):
|
|
36
|
+
```python
|
|
37
|
+
class TangoProceduralDevice(StandardReadable):
|
|
38
|
+
def __init__(self, prefix: str, name="") -> None:
|
|
39
|
+
with self.add_children_as_readables():
|
|
40
|
+
self.value = DeviceVector({0: tango_signal_r(float)})
|
|
41
|
+
with self.add_children_as_readables(ConfigSignal):
|
|
42
|
+
self.mode = tango_signal_rw(EnergyMode)
|
|
43
|
+
super().__init__(name=name, connector=TangoConnector(prefix))
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
or the EPICS one could be declarative:
|
|
47
|
+
```python
|
|
48
|
+
class EpicsDeclarativeDevice(StandardReadable, EpicsDevice):
|
|
49
|
+
value: Annotated[
|
|
50
|
+
DeviceVector[SignalR[float]], Format.HINTED_SIGNAL, EpicsSuffix("Value%d", "num_values")
|
|
51
|
+
]
|
|
52
|
+
mode: Annotated[SignalRW[EnergyMode], Format.CONFIG_SIGNAL, EpicsSuffix("Mode")]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Which do we prefer?
|
|
56
|
+
|
|
57
|
+
## Decision
|
|
58
|
+
|
|
59
|
+
We decided that the declarative approach is to be preferred until we need to write formatted strings. At that point we should drop to an `__init__` method and a for loop. This is not a step towards only supporting the declarative approach and there are no plans to drop the procedural approach.
|
|
60
|
+
|
|
61
|
+
The two approaches now look like:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
class Sensor(StandardReadable, EpicsDevice):
|
|
65
|
+
"""A demo sensor that produces a scalar value based on X and Y Movers"""
|
|
66
|
+
|
|
67
|
+
value: A[SignalR[float], PvSuffix("Value"), Format.HINTED_SIGNAL]
|
|
68
|
+
mode: A[SignalRW[EnergyMode], PvSuffix("Mode"), Format.CONFIG_SIGNAL]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class SensorGroup(StandardReadable):
|
|
72
|
+
def __init__(self, prefix: str, name: str = "", sensor_count: int = 3) -> None:
|
|
73
|
+
with self.add_children_as_readables():
|
|
74
|
+
self.sensors = DeviceVector(
|
|
75
|
+
{i: Sensor(f"{prefix}{i}:") for i in range(1, sensor_count + 1)}
|
|
76
|
+
)
|
|
77
|
+
super().__init__(name)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Consequences
|
|
81
|
+
|
|
82
|
+
We need to:
|
|
83
|
+
- Add support for reading annotations and `PvSuffix` in an `ophyd_async.epics.core.EpicsDevice` baseclass
|
|
84
|
+
- Do the `Format.HINTED_SIGNAL` and `Format.CONFIG_SIGNAL` flags in annotations for `StandardReadable`
|
|
85
|
+
- Ensure we can always drop to `__init__`
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## pvi structure changes
|
|
89
|
+
Structure read from `.value` now includes `DeviceVector` support. Requires at least PandABlocks-ioc 0.11.2
|
|
90
|
+
|
|
91
|
+
## Epics `signal` module moves
|
|
92
|
+
`ophyd_async.epics.signal` moves to `ophyd_async.epics.core` with a backwards compat module that emits deprecation warning.
|
|
93
|
+
```python
|
|
94
|
+
# old
|
|
95
|
+
from ophyd_async.epics.signal import epics_signal_rw
|
|
96
|
+
# new
|
|
97
|
+
from ophyd_async.epics.core import epics_signal_rw
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## `StandardReadable` wrappers change to `StandardReadableFormat`
|
|
101
|
+
`StandardReadable` wrappers change to enum members of `StandardReadableFormat` (normally imported as `Format`)
|
|
102
|
+
```python
|
|
103
|
+
# old
|
|
104
|
+
from ophyd_async.core import ConfigSignal, HintedSignal
|
|
105
|
+
class MyDevice(StandardReadable):
|
|
106
|
+
def __init__(self):
|
|
107
|
+
self.add_readables([sig1], ConfigSignal)
|
|
108
|
+
self.add_readables([sig2], HintedSignal)
|
|
109
|
+
self.add_readables([sig3], HintedSignal.uncached)
|
|
110
|
+
# new
|
|
111
|
+
from ophyd_async.core import StandardReadableFormat as Format
|
|
112
|
+
class MyDevice(StandardReadable):
|
|
113
|
+
def __init__(self):
|
|
114
|
+
self.add_readables([sig1], Format.CONFIG_SIGNAL)
|
|
115
|
+
self.add_readables([sig2], Format.HINTED_SIGNAL)
|
|
116
|
+
self.add_readables([sig3], Format.HINTED_UNCACHED_SIGNAL
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Declarative Devices are now available
|
|
120
|
+
```python
|
|
121
|
+
# old
|
|
122
|
+
from ophyd_async.core import ConfigSignal, HintedSignal
|
|
123
|
+
from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
|
|
124
|
+
|
|
125
|
+
class Sensor(StandardReadable):
|
|
126
|
+
def __init__(self, prefix: str, name="") -> None:
|
|
127
|
+
with self.add_children_as_readables(HintedSignal):
|
|
128
|
+
self.value = epics_signal_r(float, prefix + "Value")
|
|
129
|
+
with self.add_children_as_readables(ConfigSignal):
|
|
130
|
+
self.mode = epics_signal_rw(EnergyMode, prefix + "Mode")
|
|
131
|
+
super().__init__(name=name)
|
|
132
|
+
# new
|
|
133
|
+
from typing import Annotated as A
|
|
134
|
+
from ophyd_async.core import StandardReadableFormat as Format
|
|
135
|
+
from ophyd_async.epics.core import EpicsDevice, PvSuffix, epics_signal_r, epics_signal_rw
|
|
136
|
+
|
|
137
|
+
class Sensor(StandardReadable, EpicsDevice):
|
|
138
|
+
value: A[SignalR[float], PvSuffix("Value"), Format.HINTED_SIGNAL]
|
|
139
|
+
mode: A[SignalRW[EnergyMode], PvSuffix("Mode"), Format.CONFIG_SIGNAL]
|
|
140
|
+
```
|
|
@@ -94,7 +94,8 @@ reportMissingImports = false # Ignore missing stubs in imported modules
|
|
|
94
94
|
# Run pytest with all our checkers, and don't spam us with massive tracebacks on error
|
|
95
95
|
addopts = """
|
|
96
96
|
--tb=native -vv --strict-markers --doctest-modules
|
|
97
|
-
--doctest-glob="*.rst" --doctest-glob="*.md"
|
|
97
|
+
--doctest-glob="*.rst" --doctest-glob="*.md"
|
|
98
|
+
--ignore=docs/examples --ignore=src/ophyd_async/epics/signal.py
|
|
98
99
|
"""
|
|
99
100
|
# https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings
|
|
100
101
|
filterwarnings = "error"
|
|
@@ -45,7 +45,12 @@ from ._providers import (
|
|
|
45
45
|
UUIDFilenameProvider,
|
|
46
46
|
YMDPathProvider,
|
|
47
47
|
)
|
|
48
|
-
from ._readable import
|
|
48
|
+
from ._readable import (
|
|
49
|
+
ConfigSignal,
|
|
50
|
+
HintedSignal,
|
|
51
|
+
StandardReadable,
|
|
52
|
+
StandardReadableFormat,
|
|
53
|
+
)
|
|
49
54
|
from ._signal import (
|
|
50
55
|
Signal,
|
|
51
56
|
SignalR,
|
|
@@ -141,6 +146,7 @@ __all__ = [
|
|
|
141
146
|
"ConfigSignal",
|
|
142
147
|
"HintedSignal",
|
|
143
148
|
"StandardReadable",
|
|
149
|
+
"StandardReadableFormat",
|
|
144
150
|
"Signal",
|
|
145
151
|
"SignalR",
|
|
146
152
|
"SignalRW",
|
|
@@ -75,6 +75,7 @@ class Device(HasName, Connectable):
|
|
|
75
75
|
self, name: str = "", connector: DeviceConnector | None = None
|
|
76
76
|
) -> None:
|
|
77
77
|
self._connector = connector or DeviceConnector()
|
|
78
|
+
self._connector.create_children_from_annotations(self)
|
|
78
79
|
self.set_name(name)
|
|
79
80
|
|
|
80
81
|
@property
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from collections.abc import Callable, Iterator, Sequence
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
Generic,
|
|
8
|
+
NewType,
|
|
9
|
+
NoReturn,
|
|
10
|
+
Protocol,
|
|
11
|
+
TypeVar,
|
|
12
|
+
cast,
|
|
13
|
+
get_args,
|
|
14
|
+
get_type_hints,
|
|
15
|
+
runtime_checkable,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from ._device import Device, DeviceConnector, DeviceVector
|
|
19
|
+
from ._signal import Signal, SignalX
|
|
20
|
+
from ._signal_backend import SignalBackend, SignalDatatype
|
|
21
|
+
from ._utils import get_origin_class
|
|
22
|
+
|
|
23
|
+
SignalBackendT = TypeVar("SignalBackendT", bound=SignalBackend)
|
|
24
|
+
DeviceConnectorT = TypeVar("DeviceConnectorT", bound=DeviceConnector)
|
|
25
|
+
# Unique name possibly with trailing understore, the attribute name on the Device
|
|
26
|
+
UniqueName = NewType("UniqueName", str)
|
|
27
|
+
# Logical name without trailing underscore, the name in the control system
|
|
28
|
+
LogicalName = NewType("LogicalName", str)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _get_datatype(annotation: Any) -> type | None:
|
|
32
|
+
"""Return int from SignalRW[int]."""
|
|
33
|
+
args = get_args(annotation)
|
|
34
|
+
if len(args) == 1 and get_origin_class(args[0]):
|
|
35
|
+
return args[0]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _logical(name: UniqueName) -> LogicalName:
|
|
39
|
+
return LogicalName(name.rstrip("_"))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@runtime_checkable
|
|
43
|
+
class DeviceAnnotation(Protocol):
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def __call__(self, parent: Device, child: Device): ...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class DeviceFiller(Generic[SignalBackendT, DeviceConnectorT]):
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
device: Device,
|
|
52
|
+
signal_backend_factory: Callable[[type[SignalDatatype] | None], SignalBackendT],
|
|
53
|
+
device_connector_factory: Callable[[], DeviceConnectorT],
|
|
54
|
+
):
|
|
55
|
+
self._device = device
|
|
56
|
+
self._signal_backend_factory = signal_backend_factory
|
|
57
|
+
self._device_connector_factory = device_connector_factory
|
|
58
|
+
# Annotations stored ready for the creation phase
|
|
59
|
+
self._uncreated_signals: dict[UniqueName, type[Signal]] = {}
|
|
60
|
+
self._uncreated_devices: dict[UniqueName, type[Device]] = {}
|
|
61
|
+
self._extras: dict[UniqueName, Sequence[Any]] = {}
|
|
62
|
+
self._signal_datatype: dict[LogicalName, type | None] = {}
|
|
63
|
+
self._vector_device_type: dict[LogicalName, type[Device] | None] = {}
|
|
64
|
+
# Backends and Connectors stored ready for the connection phase
|
|
65
|
+
self._unfilled_backends: dict[
|
|
66
|
+
LogicalName, tuple[SignalBackendT, type[Signal]]
|
|
67
|
+
] = {}
|
|
68
|
+
self._unfilled_connectors: dict[LogicalName, DeviceConnectorT] = {}
|
|
69
|
+
# Once they are filled they go here in case we reconnect
|
|
70
|
+
self._filled_backends: dict[
|
|
71
|
+
LogicalName, tuple[SignalBackendT, type[Signal]]
|
|
72
|
+
] = {}
|
|
73
|
+
self._filled_connectors: dict[LogicalName, DeviceConnectorT] = {}
|
|
74
|
+
self._scan_for_annotations()
|
|
75
|
+
|
|
76
|
+
def _raise(self, name: str, error: str) -> NoReturn:
|
|
77
|
+
raise TypeError(f"{type(self._device).__name__}.{name}: {error}")
|
|
78
|
+
|
|
79
|
+
def _store_signal_datatype(self, name: UniqueName, annotation: Any):
|
|
80
|
+
origin = get_origin_class(annotation)
|
|
81
|
+
datatype = _get_datatype(annotation)
|
|
82
|
+
if origin == SignalX:
|
|
83
|
+
# SignalX doesn't need datatype
|
|
84
|
+
self._signal_datatype[_logical(name)] = None
|
|
85
|
+
elif origin and issubclass(origin, Signal) and datatype:
|
|
86
|
+
# All other Signals need one
|
|
87
|
+
self._signal_datatype[_logical(name)] = datatype
|
|
88
|
+
else:
|
|
89
|
+
# Not recognized
|
|
90
|
+
self._raise(
|
|
91
|
+
name,
|
|
92
|
+
f"Expected SignalX or SignalR/W/RW[type], got {annotation}",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def _scan_for_annotations(self):
|
|
96
|
+
# Get type hints on the class, not the instance
|
|
97
|
+
# https://github.com/python/cpython/issues/124840
|
|
98
|
+
cls = type(self._device)
|
|
99
|
+
# Get hints without Annotated for determining types
|
|
100
|
+
hints = get_type_hints(cls)
|
|
101
|
+
# Get hints with Annotated for wrapping signals and backends
|
|
102
|
+
extra_hints = get_type_hints(cls, include_extras=True)
|
|
103
|
+
for attr_name, annotation in hints.items():
|
|
104
|
+
name = UniqueName(attr_name)
|
|
105
|
+
origin = get_origin_class(annotation)
|
|
106
|
+
if (
|
|
107
|
+
name == "parent"
|
|
108
|
+
or name.startswith("_")
|
|
109
|
+
or not origin
|
|
110
|
+
or not issubclass(origin, Device)
|
|
111
|
+
):
|
|
112
|
+
# Ignore any child that is not a public Device
|
|
113
|
+
continue
|
|
114
|
+
self._extras[name] = getattr(extra_hints[attr_name], "__metadata__", ())
|
|
115
|
+
if issubclass(origin, Signal):
|
|
116
|
+
self._store_signal_datatype(name, annotation)
|
|
117
|
+
self._uncreated_signals[name] = origin
|
|
118
|
+
elif origin == DeviceVector:
|
|
119
|
+
child_type = _get_datatype(annotation)
|
|
120
|
+
child_origin = get_origin_class(child_type)
|
|
121
|
+
if child_origin is None or not issubclass(child_origin, Device):
|
|
122
|
+
self._raise(
|
|
123
|
+
name,
|
|
124
|
+
f"Expected DeviceVector[SomeDevice], got {annotation}",
|
|
125
|
+
)
|
|
126
|
+
if issubclass(child_origin, Signal):
|
|
127
|
+
self._store_signal_datatype(name, child_type)
|
|
128
|
+
self._vector_device_type[_logical(name)] = child_origin
|
|
129
|
+
setattr(self._device, name, DeviceVector({}))
|
|
130
|
+
else:
|
|
131
|
+
self._uncreated_devices[name] = origin
|
|
132
|
+
|
|
133
|
+
def check_created(self):
|
|
134
|
+
uncreated = sorted(set(self._uncreated_signals).union(self._uncreated_devices))
|
|
135
|
+
if uncreated:
|
|
136
|
+
raise RuntimeError(
|
|
137
|
+
f"{self._device.name}: {uncreated} have not been created yet"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def create_signals_from_annotations(
|
|
141
|
+
self,
|
|
142
|
+
filled=True,
|
|
143
|
+
) -> Iterator[tuple[SignalBackendT, list[Any]]]:
|
|
144
|
+
for name in list(self._uncreated_signals):
|
|
145
|
+
child_type = self._uncreated_signals.pop(name)
|
|
146
|
+
backend = self._signal_backend_factory(
|
|
147
|
+
self._signal_datatype[_logical(name)]
|
|
148
|
+
)
|
|
149
|
+
extras = list(self._extras[name])
|
|
150
|
+
yield backend, extras
|
|
151
|
+
signal = child_type(backend)
|
|
152
|
+
for anno in extras:
|
|
153
|
+
assert isinstance(anno, DeviceAnnotation), anno
|
|
154
|
+
anno(self._device, signal)
|
|
155
|
+
setattr(self._device, name, signal)
|
|
156
|
+
dest = self._filled_backends if filled else self._unfilled_backends
|
|
157
|
+
dest[_logical(name)] = (backend, child_type)
|
|
158
|
+
|
|
159
|
+
def create_devices_from_annotations(
|
|
160
|
+
self,
|
|
161
|
+
filled=True,
|
|
162
|
+
) -> Iterator[tuple[DeviceConnectorT, list[Any]]]:
|
|
163
|
+
for name in list(self._uncreated_devices):
|
|
164
|
+
child_type = self._uncreated_devices.pop(name)
|
|
165
|
+
connector = self._device_connector_factory()
|
|
166
|
+
extras = list(self._extras[name])
|
|
167
|
+
yield connector, extras
|
|
168
|
+
device = child_type(connector=connector)
|
|
169
|
+
for anno in extras:
|
|
170
|
+
assert isinstance(anno, DeviceAnnotation), anno
|
|
171
|
+
anno(self._device, device)
|
|
172
|
+
setattr(self._device, name, device)
|
|
173
|
+
dest = self._filled_connectors if filled else self._unfilled_connectors
|
|
174
|
+
dest[_logical(name)] = connector
|
|
175
|
+
|
|
176
|
+
def create_device_vector_entries_to_mock(self, num: int):
|
|
177
|
+
for name, cls in self._vector_device_type.items():
|
|
178
|
+
assert cls, "Shouldn't happen"
|
|
179
|
+
for i in range(1, num + 1):
|
|
180
|
+
if issubclass(cls, Signal):
|
|
181
|
+
self.fill_child_signal(name, cls, i)
|
|
182
|
+
elif issubclass(cls, Device):
|
|
183
|
+
self.fill_child_device(name, cls, i)
|
|
184
|
+
else:
|
|
185
|
+
self._raise(name, f"Can't make {cls}")
|
|
186
|
+
|
|
187
|
+
def check_filled(self, source: str):
|
|
188
|
+
unfilled = sorted(set(self._unfilled_connectors).union(self._unfilled_backends))
|
|
189
|
+
if unfilled:
|
|
190
|
+
raise RuntimeError(
|
|
191
|
+
f"{self._device.name}: cannot provision {unfilled} from {source}"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def _ensure_device_vector(self, name: LogicalName) -> DeviceVector:
|
|
195
|
+
if not hasattr(self._device, name):
|
|
196
|
+
# We have no type hints, so use whatever we are told
|
|
197
|
+
self._vector_device_type[name] = None
|
|
198
|
+
setattr(self._device, name, DeviceVector({}))
|
|
199
|
+
vector = getattr(self._device, name)
|
|
200
|
+
if not isinstance(vector, DeviceVector):
|
|
201
|
+
self._raise(name, f"Expected DeviceVector, got {vector}")
|
|
202
|
+
return vector
|
|
203
|
+
|
|
204
|
+
def fill_child_signal(
|
|
205
|
+
self,
|
|
206
|
+
name: str,
|
|
207
|
+
signal_type: type[Signal],
|
|
208
|
+
vector_index: int | None = None,
|
|
209
|
+
) -> SignalBackendT:
|
|
210
|
+
name = cast(LogicalName, name)
|
|
211
|
+
if name in self._unfilled_backends:
|
|
212
|
+
# We made it above
|
|
213
|
+
backend, expected_signal_type = self._unfilled_backends.pop(name)
|
|
214
|
+
self._filled_backends[name] = backend, expected_signal_type
|
|
215
|
+
elif name in self._filled_backends:
|
|
216
|
+
# We made it and filled it so return for validation
|
|
217
|
+
backend, expected_signal_type = self._filled_backends[name]
|
|
218
|
+
elif vector_index:
|
|
219
|
+
# We need to add a new entry to a DeviceVector
|
|
220
|
+
vector = self._ensure_device_vector(name)
|
|
221
|
+
backend = self._signal_backend_factory(self._signal_datatype.get(name))
|
|
222
|
+
expected_signal_type = self._vector_device_type[name] or signal_type
|
|
223
|
+
vector[vector_index] = signal_type(backend)
|
|
224
|
+
elif child := getattr(self._device, name, None):
|
|
225
|
+
# There is an existing child, so raise
|
|
226
|
+
self._raise(name, f"Cannot make child as it would shadow {child}")
|
|
227
|
+
else:
|
|
228
|
+
# We need to add a new child to the top level Device
|
|
229
|
+
backend = self._signal_backend_factory(None)
|
|
230
|
+
expected_signal_type = signal_type
|
|
231
|
+
setattr(self._device, name, signal_type(backend))
|
|
232
|
+
if signal_type is not expected_signal_type:
|
|
233
|
+
self._raise(
|
|
234
|
+
name,
|
|
235
|
+
f"is a {signal_type.__name__} not a {expected_signal_type.__name__}",
|
|
236
|
+
)
|
|
237
|
+
return backend
|
|
238
|
+
|
|
239
|
+
def fill_child_device(
|
|
240
|
+
self,
|
|
241
|
+
name: str,
|
|
242
|
+
device_type: type[Device] = Device,
|
|
243
|
+
vector_index: int | None = None,
|
|
244
|
+
) -> DeviceConnectorT:
|
|
245
|
+
name = cast(LogicalName, name)
|
|
246
|
+
if name in self._unfilled_connectors:
|
|
247
|
+
# We made it above
|
|
248
|
+
connector = self._unfilled_connectors.pop(name)
|
|
249
|
+
self._filled_connectors[name] = connector
|
|
250
|
+
elif name in self._filled_backends:
|
|
251
|
+
# We made it and filled it so return for validation
|
|
252
|
+
connector = self._filled_connectors[name]
|
|
253
|
+
elif vector_index:
|
|
254
|
+
# We need to add a new entry to a DeviceVector
|
|
255
|
+
vector = self._ensure_device_vector(name)
|
|
256
|
+
vector_device_type = self._vector_device_type[name] or device_type
|
|
257
|
+
assert issubclass(
|
|
258
|
+
vector_device_type, Device
|
|
259
|
+
), f"{vector_device_type} is not a Device"
|
|
260
|
+
connector = self._device_connector_factory()
|
|
261
|
+
vector[vector_index] = vector_device_type(connector=connector)
|
|
262
|
+
elif child := getattr(self._device, name, None):
|
|
263
|
+
# There is an existing child, so raise
|
|
264
|
+
self._raise(name, f"Cannot make child as it would shadow {child}")
|
|
265
|
+
else:
|
|
266
|
+
# We need to add a new child to the top level Device
|
|
267
|
+
connector = self._device_connector_factory()
|
|
268
|
+
setattr(self._device, name, device_type(connector=connector))
|
|
269
|
+
return connector
|