ophyd-async 0.8.0a3__tar.gz → 0.8.0a4__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.0a3 → ophyd_async-0.8.0a4}/.copier-answers.yml +1 -1
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/CONTRIBUTING.md +1 -1
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/workflows/_pypi.yml +1 -1
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/workflows/_release.yml +1 -1
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/PKG-INFO +1 -1
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/_version.py +1 -1
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/__init__.py +2 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_device.py +70 -49
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_mock_signal_backend.py +10 -7
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_mock_signal_utils.py +14 -11
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_signal.py +22 -24
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_soft_signal_backend.py +2 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_utils.py +55 -10
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adcore/_single_trigger.py +2 -1
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/core/_pvi_connector.py +24 -25
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/plan_stubs/_ensure_connected.py +2 -4
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/base_devices/_base_device.py +37 -36
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async.egg-info/PKG-INFO +1 -1
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_device.py +22 -14
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_signal.py +19 -59
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/demo/test_demo.py +5 -3
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/plan_stubs/test_ensure_connected.py +2 -2
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.codecov.yml +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.devcontainer/devcontainer.json +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.git-blame-ignore-revs +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/ISSUE_TEMPLATE/issue.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/actions/install_requirements/action.yml +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/dependabot.yml +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/pages/index.html +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/pages/make_switcher.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/workflows/_check.yml +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/workflows/_dist.yml +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/workflows/_docs.yml +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/workflows/_test.yml +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/workflows/_tox.yml +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/workflows/ci.yml +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.github/workflows/periodic.yml +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.gitignore +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.mailmap +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/.pre-commit-config.yaml +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/Dockerfile +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/LICENSE +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/README.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/_api.rst +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/_templates/custom-module-template.rst +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/conf.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/examples/epics_demo.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/examples/foo_detector.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/examples/tango_demo.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0001-record-architecture-decisions.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0002-switched-to-python-copier-template.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0003-ophyd-async-migration.rst +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0004-repository-structure.rst +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0005-respect-black-line-length.rst +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0006-procedural-device-definitions.rst +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0007-subpackage-structure.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0008-signal-types.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0009-procedural-vs-declarative-devices.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/explanations/decisions/COPYME +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/explanations/decisions.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/explanations/design-goals.rst +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/explanations/event-loop-choice.rst +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/explanations/flyscanning.rst +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/explanations.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/genindex.rst +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/how-to/choose-interfaces-for-devices.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/how-to/compound-devices.rst +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/how-to/contribute.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/how-to/make-a-simple-device.rst +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/how-to/make-a-standard-detector.rst +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/how-to/write-tests-for-devices.rst +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/how-to.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/images/ophyd-async-logo.svg +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/images/ophyd-favicon.svg +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/index.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/reference.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/tutorials/installation.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/tutorials/using-existing-devices.rst +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/docs/tutorials.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/pyproject.toml +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/setup.cfg +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/__main__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_detector.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_device_filler.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_device_save_loader.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_flyer.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_hdf_dataset.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_log.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_protocol.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_providers.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_readable.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_signal_backend.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_status.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_table.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adaravis/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adaravis/_aravis.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adaravis/_aravis_controller.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adaravis/_aravis_io.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adcore/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adcore/_core_io.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adcore/_core_logic.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adcore/_hdf_writer.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adcore/_utils.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adkinetix/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adkinetix/_kinetix.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adkinetix/_kinetix_controller.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adkinetix/_kinetix_io.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adpilatus/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adpilatus/_pilatus.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adpilatus/_pilatus_controller.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adpilatus/_pilatus_io.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adsimdetector/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adsimdetector/_sim.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adsimdetector/_sim_controller.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/advimba/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/advimba/_vimba.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/advimba/_vimba_controller.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/advimba/_vimba_io.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/core/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/core/_aioca.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/core/_epics_connector.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/core/_epics_device.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/core/_p4p.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/core/_signal.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/core/_util.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/demo/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/demo/_mover.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/demo/_sensor.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/demo/mover.db +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/demo/sensor.db +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/eiger/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/eiger/_eiger.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/eiger/_eiger_controller.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/eiger/_eiger_io.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/eiger/_odin_io.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/motor.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/signal.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/core.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/odin/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/_block.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/_control.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/_hdf_panda.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/_table.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/_trigger.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/_utils.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/_writer.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/plan_stubs/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/plan_stubs/_fly.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/plan_stubs/_nd_attributes.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/py.typed +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/demo/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/demo/_pattern_detector/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_detector_writer.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/demo/_sim_motor.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/testing/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/base_devices/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/base_devices/_tango_readable.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/demo/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/demo/_counter.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/demo/_detector.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/demo/_mover.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/demo/_tango/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/demo/_tango/_servers.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/signal/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/signal/_signal.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/signal/_tango_transport.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async.egg-info/SOURCES.txt +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async.egg-info/dependency_links.txt +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async.egg-info/entry_points.txt +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async.egg-info/requires.txt +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/src/ophyd_async.egg-info/top_level.txt +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/system_tests/epics/eiger/README.md +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/system_tests/epics/eiger/start_iocs_and_run_tests.sh +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/system_tests/epics/eiger/test_eiger_system.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/conftest.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_device_collector.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_device_save_loader.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_flyer.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_log.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_mock_signal_backend.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_protocol.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_providers.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_readable.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_soft_signal_backend.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_status.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_subset_enum.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_table.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_utils.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/core/test_watchable_async_status.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/adaravis/test_aravis.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/adcore/test_drivers.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/adcore/test_scans.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/adcore/test_single_trigger.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/adcore/test_writers.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/adkinetix/test_kinetix.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/adpilatus/test_pilatus.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/adsimdetector/test_sim.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/advimba/test_vimba.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/conftest.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/eiger/test_eiger_controller.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/eiger/test_eiger_detector.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/eiger/test_odin_io.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/pvi/test_pvi.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/signal/test_common.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/signal/test_records.db +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/signal/test_signals.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/test_areadetector_subclass_naming.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/epics/test_motor.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/fastcs/panda/db/panda.db +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/fastcs/panda/test_hdf_panda.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/fastcs/panda/test_panda_connect.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/fastcs/panda/test_panda_control.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/fastcs/panda/test_panda_utils.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/fastcs/panda/test_seq_table.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/fastcs/panda/test_trigger.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/fastcs/panda/test_writer.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/plan_stubs/test_fly.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/sim/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/sim/conftest.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/sim/demo/__init__.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/sim/demo/test_sim_motor.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/sim/test_pattern_generator.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/sim/test_sim_detector.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/sim/test_sim_writer.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/sim/test_streaming_plan.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/tango/test_base_device.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/tango/test_tango_signals.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/tango/test_tango_transport.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/test_cli.py +0 -0
- {ophyd_async-0.8.0a3 → ophyd_async-0.8.0a4}/tests/test_data/test_yaml_save.yml +0 -0
|
@@ -24,4 +24,4 @@ It is recommended that developers use a [vscode devcontainer](https://code.visua
|
|
|
24
24
|
|
|
25
25
|
This project was created using the [Diamond Light Source Copier Template](https://github.com/DiamondLightSource/python-copier-template) for Python projects.
|
|
26
26
|
|
|
27
|
-
For more information on common tasks like setting up a developer environment, running the tests, and setting a pre-commit hook, see the template's [How-to guides](https://diamondlightsource.github.io/python-copier-template/2.
|
|
27
|
+
For more information on common tasks like setting up a developer environment, running the tests, and setting a pre-commit hook, see the template's [How-to guides](https://diamondlightsource.github.io/python-copier-template/2.5.0/how-to.html).
|
|
@@ -23,7 +23,7 @@ jobs:
|
|
|
23
23
|
- name: Create GitHub Release
|
|
24
24
|
# We pin to the SHA, not the tag, for security reasons.
|
|
25
25
|
# https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions
|
|
26
|
-
uses: softprops/action-gh-release@
|
|
26
|
+
uses: softprops/action-gh-release@e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b8 # v2.0.9
|
|
27
27
|
with:
|
|
28
28
|
prerelease: ${{ contains(github.ref_name, 'a') || contains(github.ref_name, 'b') || contains(github.ref_name, 'rc') }}
|
|
29
29
|
files: "*"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ophyd-async
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.0a4
|
|
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
|
|
@@ -83,6 +83,7 @@ from ._utils import (
|
|
|
83
83
|
DEFAULT_TIMEOUT,
|
|
84
84
|
CalculatableTimeout,
|
|
85
85
|
Callback,
|
|
86
|
+
LazyMock,
|
|
86
87
|
NotConnected,
|
|
87
88
|
Reference,
|
|
88
89
|
StrictEnum,
|
|
@@ -176,6 +177,7 @@ __all__ = [
|
|
|
176
177
|
"DEFAULT_TIMEOUT",
|
|
177
178
|
"CalculatableTimeout",
|
|
178
179
|
"Callback",
|
|
180
|
+
"LazyMock",
|
|
179
181
|
"CALCULATE_TIMEOUT",
|
|
180
182
|
"NotConnected",
|
|
181
183
|
"Reference",
|
|
@@ -3,17 +3,15 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import sys
|
|
5
5
|
from collections.abc import Coroutine, Iterator, Mapping, MutableMapping
|
|
6
|
+
from functools import cached_property
|
|
6
7
|
from logging import LoggerAdapter, getLogger
|
|
7
8
|
from typing import Any, TypeVar
|
|
8
|
-
from unittest.mock import Mock
|
|
9
9
|
|
|
10
10
|
from bluesky.protocols import HasName
|
|
11
11
|
from bluesky.run_engine import call_in_bluesky_event_loop, in_bluesky_event_loop
|
|
12
12
|
|
|
13
13
|
from ._protocol import Connectable
|
|
14
|
-
from ._utils import DEFAULT_TIMEOUT, NotConnected, wait_for_connection
|
|
15
|
-
|
|
16
|
-
_device_mocks: dict[Device, Mock] = {}
|
|
14
|
+
from ._utils import DEFAULT_TIMEOUT, LazyMock, NotConnected, wait_for_connection
|
|
17
15
|
|
|
18
16
|
|
|
19
17
|
class DeviceConnector:
|
|
@@ -37,25 +35,23 @@ class DeviceConnector:
|
|
|
37
35
|
during ``__init__``.
|
|
38
36
|
"""
|
|
39
37
|
|
|
40
|
-
async def
|
|
41
|
-
|
|
42
|
-
device:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
):
|
|
38
|
+
async def connect_mock(self, device: Device, mock: LazyMock):
|
|
39
|
+
# Connect serially, no errors to gather up as in mock mode
|
|
40
|
+
for name, child_device in device.children():
|
|
41
|
+
await child_device.connect(mock=mock.child(name))
|
|
42
|
+
|
|
43
|
+
async def connect_real(self, device: Device, timeout: float, force_reconnect: bool):
|
|
47
44
|
"""Used during ``Device.connect``.
|
|
48
45
|
|
|
49
46
|
This is called when a previous connect has not been done, or has been
|
|
50
47
|
done in a different mock more. It should connect the Device and all its
|
|
51
48
|
children.
|
|
52
49
|
"""
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)
|
|
50
|
+
# Connect in parallel, gathering up NotConnected errors
|
|
51
|
+
coros = {
|
|
52
|
+
name: child_device.connect(timeout=timeout, force_reconnect=force_reconnect)
|
|
53
|
+
for name, child_device in device.children()
|
|
54
|
+
}
|
|
59
55
|
await wait_for_connection(**coros)
|
|
60
56
|
|
|
61
57
|
|
|
@@ -67,9 +63,8 @@ class Device(HasName, Connectable):
|
|
|
67
63
|
parent: Device | None = None
|
|
68
64
|
# None if connect hasn't started, a Task if it has
|
|
69
65
|
_connect_task: asyncio.Task | None = None
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
_connect_mock_arg: bool | None = None
|
|
66
|
+
# The mock if we have connected in mock mode
|
|
67
|
+
_mock: LazyMock | None = None
|
|
73
68
|
|
|
74
69
|
def __init__(
|
|
75
70
|
self, name: str = "", connector: DeviceConnector | None = None
|
|
@@ -83,10 +78,18 @@ class Device(HasName, Connectable):
|
|
|
83
78
|
"""Return the name of the Device"""
|
|
84
79
|
return self._name
|
|
85
80
|
|
|
81
|
+
@cached_property
|
|
82
|
+
def _child_devices(self) -> dict[str, Device]:
|
|
83
|
+
return {}
|
|
84
|
+
|
|
86
85
|
def children(self) -> Iterator[tuple[str, Device]]:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
yield from self._child_devices.items()
|
|
87
|
+
|
|
88
|
+
@cached_property
|
|
89
|
+
def log(self) -> LoggerAdapter:
|
|
90
|
+
return LoggerAdapter(
|
|
91
|
+
getLogger("ophyd_async.devices"), {"ophyd_async_device_name": self.name}
|
|
92
|
+
)
|
|
90
93
|
|
|
91
94
|
def set_name(self, name: str):
|
|
92
95
|
"""Set ``self.name=name`` and each ``self.child.name=name+"-child"``.
|
|
@@ -97,28 +100,33 @@ class Device(HasName, Connectable):
|
|
|
97
100
|
New name to set
|
|
98
101
|
"""
|
|
99
102
|
self._name = name
|
|
100
|
-
# Ensure
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
)
|
|
103
|
+
# Ensure logger is recreated after a name change
|
|
104
|
+
if "log" in self.__dict__:
|
|
105
|
+
del self.log
|
|
104
106
|
for child_name, child in self.children():
|
|
105
107
|
child_name = f"{self.name}-{child_name.strip('_')}" if self.name else ""
|
|
106
108
|
child.set_name(child_name)
|
|
107
109
|
|
|
108
110
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
111
|
+
# Bear in mind that this function is called *a lot*, so
|
|
112
|
+
# we need to make sure nothing expensive happens in it...
|
|
109
113
|
if name == "parent":
|
|
110
114
|
if self.parent not in (value, None):
|
|
111
115
|
raise TypeError(
|
|
112
116
|
f"Cannot set the parent of {self} to be {value}: "
|
|
113
117
|
f"it is already a child of {self.parent}"
|
|
114
118
|
)
|
|
115
|
-
|
|
119
|
+
# ...hence not doing an isinstance check for attributes we
|
|
120
|
+
# know not to be Devices
|
|
121
|
+
elif name not in _not_device_attrs and isinstance(value, Device):
|
|
116
122
|
value.parent = self
|
|
117
|
-
|
|
123
|
+
self._child_devices[name] = value
|
|
124
|
+
# ...and avoiding the super call as we know it resolves to `object`
|
|
125
|
+
return object.__setattr__(self, name, value)
|
|
118
126
|
|
|
119
127
|
async def connect(
|
|
120
128
|
self,
|
|
121
|
-
mock: bool |
|
|
129
|
+
mock: bool | LazyMock = False,
|
|
122
130
|
timeout: float = DEFAULT_TIMEOUT,
|
|
123
131
|
force_reconnect: bool = False,
|
|
124
132
|
) -> None:
|
|
@@ -133,26 +141,39 @@ class Device(HasName, Connectable):
|
|
|
133
141
|
timeout:
|
|
134
142
|
Time to wait before failing with a TimeoutError.
|
|
135
143
|
"""
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
144
|
+
if mock:
|
|
145
|
+
# Always connect in mock mode serially
|
|
146
|
+
if isinstance(mock, LazyMock):
|
|
147
|
+
# Use the provided mock
|
|
148
|
+
self._mock = mock
|
|
149
|
+
elif not self._mock:
|
|
150
|
+
# Make one
|
|
151
|
+
self._mock = LazyMock()
|
|
152
|
+
await self._connector.connect_mock(self, self._mock)
|
|
153
|
+
else:
|
|
154
|
+
# Try to cache the connect in real mode
|
|
155
|
+
can_use_previous_connect = (
|
|
156
|
+
self._mock is None
|
|
157
|
+
and self._connect_task
|
|
158
|
+
and not (self._connect_task.done() and self._connect_task.exception())
|
|
150
159
|
)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
160
|
+
if force_reconnect or not can_use_previous_connect:
|
|
161
|
+
self._mock = None
|
|
162
|
+
coro = self._connector.connect_real(self, timeout, force_reconnect)
|
|
163
|
+
self._connect_task = asyncio.create_task(coro)
|
|
164
|
+
assert self._connect_task, "Connect task not created, this shouldn't happen"
|
|
165
|
+
# Wait for it to complete
|
|
166
|
+
await self._connect_task
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
_not_device_attrs = {
|
|
170
|
+
"_name",
|
|
171
|
+
"_children",
|
|
172
|
+
"_connector",
|
|
173
|
+
"_timeout",
|
|
174
|
+
"_mock",
|
|
175
|
+
"_connect_task",
|
|
176
|
+
}
|
|
156
177
|
|
|
157
178
|
|
|
158
179
|
DeviceT = TypeVar("DeviceT", bound=Device)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from collections.abc import Callable
|
|
3
3
|
from functools import cached_property
|
|
4
|
-
from unittest.mock import AsyncMock
|
|
4
|
+
from unittest.mock import AsyncMock
|
|
5
5
|
|
|
6
6
|
from bluesky.protocols import Descriptor, Reading
|
|
7
7
|
|
|
8
8
|
from ._signal_backend import SignalBackend, SignalDatatypeT
|
|
9
9
|
from ._soft_signal_backend import SoftSignalBackend
|
|
10
|
-
from ._utils import Callback
|
|
10
|
+
from ._utils import Callback, LazyMock
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class MockSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
@@ -16,7 +16,7 @@ class MockSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
16
16
|
def __init__(
|
|
17
17
|
self,
|
|
18
18
|
initial_backend: SignalBackend[SignalDatatypeT],
|
|
19
|
-
mock:
|
|
19
|
+
mock: LazyMock,
|
|
20
20
|
) -> None:
|
|
21
21
|
if isinstance(initial_backend, MockSignalBackend):
|
|
22
22
|
raise ValueError("Cannot make a MockSignalBackend for a MockSignalBackend")
|
|
@@ -34,11 +34,14 @@ class MockSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
34
34
|
|
|
35
35
|
# use existing Mock if provided
|
|
36
36
|
self.mock = mock
|
|
37
|
-
self.put_mock = AsyncMock(name="put", spec=Callable)
|
|
38
|
-
self.mock.attach_mock(self.put_mock, "put")
|
|
39
|
-
|
|
40
37
|
super().__init__(datatype=self.initial_backend.datatype)
|
|
41
38
|
|
|
39
|
+
@cached_property
|
|
40
|
+
def put_mock(self) -> AsyncMock:
|
|
41
|
+
put_mock = AsyncMock(name="put", spec=Callable)
|
|
42
|
+
self.mock().attach_mock(put_mock, "put")
|
|
43
|
+
return put_mock
|
|
44
|
+
|
|
42
45
|
def set_value(self, value: SignalDatatypeT):
|
|
43
46
|
self.soft_backend.set_value(value)
|
|
44
47
|
|
|
@@ -46,7 +49,7 @@ class MockSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
46
49
|
return f"mock+{self.initial_backend.source(name, read)}"
|
|
47
50
|
|
|
48
51
|
async def connect(self, timeout: float) -> None:
|
|
49
|
-
|
|
52
|
+
raise RuntimeError("It is not possible to connect a MockSignalBackend")
|
|
50
53
|
|
|
51
54
|
@cached_property
|
|
52
55
|
def put_proceeds(self) -> asyncio.Event:
|
|
@@ -2,17 +2,26 @@ from collections.abc import Awaitable, Callable, Iterable
|
|
|
2
2
|
from contextlib import asynccontextmanager, contextmanager
|
|
3
3
|
from unittest.mock import AsyncMock, Mock
|
|
4
4
|
|
|
5
|
-
from ._device import Device
|
|
5
|
+
from ._device import Device
|
|
6
6
|
from ._mock_signal_backend import MockSignalBackend
|
|
7
|
-
from ._signal import Signal,
|
|
7
|
+
from ._signal import Signal, SignalConnector, SignalR
|
|
8
8
|
from ._soft_signal_backend import SignalDatatypeT
|
|
9
|
+
from ._utils import LazyMock
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_mock(device: Device | Signal) -> Mock:
|
|
13
|
+
mock = device._mock # noqa: SLF001
|
|
14
|
+
assert isinstance(mock, LazyMock), f"Device {device} not connected in mock mode"
|
|
15
|
+
return mock()
|
|
9
16
|
|
|
10
17
|
|
|
11
18
|
def _get_mock_signal_backend(signal: Signal) -> MockSignalBackend:
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
connector = signal._connector # noqa: SLF001
|
|
20
|
+
assert isinstance(connector, SignalConnector), f"Expected Signal, got {signal}"
|
|
21
|
+
assert isinstance(
|
|
22
|
+
connector.backend, MockSignalBackend
|
|
14
23
|
), f"Signal {signal} not connected in mock mode"
|
|
15
|
-
return
|
|
24
|
+
return connector.backend
|
|
16
25
|
|
|
17
26
|
|
|
18
27
|
def set_mock_value(signal: Signal[SignalDatatypeT], value: SignalDatatypeT):
|
|
@@ -45,12 +54,6 @@ def get_mock_put(signal: Signal) -> AsyncMock:
|
|
|
45
54
|
return _get_mock_signal_backend(signal).put_mock
|
|
46
55
|
|
|
47
56
|
|
|
48
|
-
def get_mock(device: Device | Signal) -> Mock:
|
|
49
|
-
if isinstance(device, Signal):
|
|
50
|
-
return _get_mock_signal_backend(device).mock
|
|
51
|
-
return _device_mocks[device]
|
|
52
|
-
|
|
53
|
-
|
|
54
57
|
def reset_mock_put_calls(signal: Signal):
|
|
55
58
|
backend = _get_mock_signal_backend(signal)
|
|
56
59
|
backend.put_mock.reset_mock()
|
|
@@ -4,7 +4,6 @@ import asyncio
|
|
|
4
4
|
import functools
|
|
5
5
|
from collections.abc import AsyncGenerator, Awaitable, Callable, Mapping
|
|
6
6
|
from typing import Any, Generic, cast
|
|
7
|
-
from unittest.mock import Mock
|
|
8
7
|
|
|
9
8
|
from bluesky.protocols import (
|
|
10
9
|
Locatable,
|
|
@@ -30,9 +29,14 @@ from ._signal_backend import (
|
|
|
30
29
|
)
|
|
31
30
|
from ._soft_signal_backend import SoftSignalBackend
|
|
32
31
|
from ._status import AsyncStatus
|
|
33
|
-
from ._utils import
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
from ._utils import (
|
|
33
|
+
CALCULATE_TIMEOUT,
|
|
34
|
+
DEFAULT_TIMEOUT,
|
|
35
|
+
CalculatableTimeout,
|
|
36
|
+
Callback,
|
|
37
|
+
LazyMock,
|
|
38
|
+
T,
|
|
39
|
+
)
|
|
36
40
|
|
|
37
41
|
|
|
38
42
|
async def _wait_for(coro: Awaitable[T], timeout: float | None, source: str) -> T:
|
|
@@ -54,26 +58,28 @@ class SignalConnector(DeviceConnector):
|
|
|
54
58
|
def __init__(self, backend: SignalBackend):
|
|
55
59
|
self.backend = self._init_backend = backend
|
|
56
60
|
|
|
57
|
-
async def
|
|
58
|
-
self,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
force_reconnect: bool,
|
|
63
|
-
):
|
|
64
|
-
if mock:
|
|
65
|
-
self.backend = MockSignalBackend(self._init_backend, mock)
|
|
66
|
-
_mock_signal_backends[device] = self.backend
|
|
67
|
-
else:
|
|
68
|
-
self.backend = self._init_backend
|
|
61
|
+
async def connect_mock(self, device: Device, mock: LazyMock):
|
|
62
|
+
self.backend = MockSignalBackend(self._init_backend, mock)
|
|
63
|
+
|
|
64
|
+
async def connect_real(self, device: Device, timeout: float, force_reconnect: bool):
|
|
65
|
+
self.backend = self._init_backend
|
|
69
66
|
device.log.debug(f"Connecting to {self.backend.source(device.name, read=True)}")
|
|
70
67
|
await self.backend.connect(timeout)
|
|
71
68
|
|
|
72
69
|
|
|
70
|
+
class _ChildrenNotAllowed(dict[str, Device]):
|
|
71
|
+
def __setitem__(self, key: str, value: Device) -> None:
|
|
72
|
+
raise AttributeError(
|
|
73
|
+
f"Cannot add Device or Signal child {key}={value} of Signal, "
|
|
74
|
+
"make a subclass of Device instead"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
73
78
|
class Signal(Device, Generic[SignalDatatypeT]):
|
|
74
79
|
"""A Device with the concept of a value, with R, RW, W and X flavours"""
|
|
75
80
|
|
|
76
81
|
_connector: SignalConnector
|
|
82
|
+
_child_devices = _ChildrenNotAllowed() # type: ignore
|
|
77
83
|
|
|
78
84
|
def __init__(
|
|
79
85
|
self,
|
|
@@ -89,14 +95,6 @@ class Signal(Device, Generic[SignalDatatypeT]):
|
|
|
89
95
|
"""Like ca://PV_PREFIX:SIGNAL, or "" if not set"""
|
|
90
96
|
return self._connector.backend.source(self.name, read=True)
|
|
91
97
|
|
|
92
|
-
def __setattr__(self, name: str, value: Any) -> None:
|
|
93
|
-
if name != "parent" and isinstance(value, Device):
|
|
94
|
-
raise AttributeError(
|
|
95
|
-
f"Cannot add Device or Signal {value} as a child of Signal {self}, "
|
|
96
|
-
"make a subclass of Device instead"
|
|
97
|
-
)
|
|
98
|
-
return super().__setattr__(name, value)
|
|
99
|
-
|
|
100
98
|
|
|
101
99
|
class _SignalCache(Generic[SignalDatatypeT]):
|
|
102
100
|
def __init__(self, backend: SignalBackend[SignalDatatypeT], signal: Signal):
|
|
@@ -4,6 +4,7 @@ import time
|
|
|
4
4
|
from abc import abstractmethod
|
|
5
5
|
from collections.abc import Sequence
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
+
from functools import lru_cache
|
|
7
8
|
from typing import Any, Generic, get_origin
|
|
8
9
|
|
|
9
10
|
import numpy as np
|
|
@@ -90,6 +91,7 @@ class TableSoftConverter(SoftConverter[TableT]):
|
|
|
90
91
|
raise TypeError(f"Cannot convert {value} to {self.datatype}")
|
|
91
92
|
|
|
92
93
|
|
|
94
|
+
@lru_cache
|
|
93
95
|
def make_converter(datatype: type[SignalDatatype]) -> SoftConverter:
|
|
94
96
|
enum_cls = get_enum_cls(datatype)
|
|
95
97
|
if datatype == Sequence[str]:
|
|
@@ -14,6 +14,7 @@ from typing import (
|
|
|
14
14
|
get_args,
|
|
15
15
|
get_origin,
|
|
16
16
|
)
|
|
17
|
+
from unittest.mock import Mock
|
|
17
18
|
|
|
18
19
|
import numpy as np
|
|
19
20
|
|
|
@@ -120,20 +121,29 @@ async def wait_for_connection(**coros: Awaitable[None]):
|
|
|
120
121
|
|
|
121
122
|
Expected kwargs should be a mapping of names to coroutine tasks to execute.
|
|
122
123
|
"""
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
exceptions: dict[str, Exception] = {}
|
|
125
|
+
if len(coros) == 1:
|
|
126
|
+
# Single device optimization
|
|
127
|
+
name, coro = coros.popitem()
|
|
128
|
+
try:
|
|
129
|
+
await coro
|
|
130
|
+
except Exception as e:
|
|
131
|
+
exceptions[name] = e
|
|
132
|
+
else:
|
|
133
|
+
# Use gather to connect in parallel
|
|
134
|
+
results = await asyncio.gather(*coros.values(), return_exceptions=True)
|
|
135
|
+
for name, result in zip(coros, results, strict=False):
|
|
136
|
+
if isinstance(result, Exception):
|
|
137
|
+
exceptions[name] = result
|
|
125
138
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if not isinstance(result, NotConnected):
|
|
139
|
+
if exceptions:
|
|
140
|
+
for name, exception in exceptions.items():
|
|
141
|
+
if not isinstance(exception, NotConnected):
|
|
130
142
|
logging.exception(
|
|
131
143
|
f"device `{name}` raised unexpected exception "
|
|
132
|
-
f"{type(
|
|
133
|
-
exc_info=
|
|
144
|
+
f"{type(exception).__name__}",
|
|
145
|
+
exc_info=exception,
|
|
134
146
|
)
|
|
135
|
-
|
|
136
|
-
if exceptions:
|
|
137
147
|
raise NotConnected(exceptions)
|
|
138
148
|
|
|
139
149
|
|
|
@@ -252,3 +262,38 @@ class Reference(Generic[T]):
|
|
|
252
262
|
|
|
253
263
|
def __call__(self) -> T:
|
|
254
264
|
return self._obj
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class LazyMock:
|
|
268
|
+
"""A lazily created Mock to be used when connecting in mock mode.
|
|
269
|
+
|
|
270
|
+
Creating Mocks is reasonably expensive when each Device (and Signal)
|
|
271
|
+
requires its own, and the tree is only used when ``Signal.set()`` is
|
|
272
|
+
called. This class allows a tree of lazily connected Mocks to be
|
|
273
|
+
constructed so that when the leaf is created, so are its parents.
|
|
274
|
+
Any calls to the child are then accessible from the parent mock.
|
|
275
|
+
|
|
276
|
+
>>> parent = LazyMock()
|
|
277
|
+
>>> child = parent.child("child")
|
|
278
|
+
>>> child_mock = child()
|
|
279
|
+
>>> child_mock() # doctest: +ELLIPSIS
|
|
280
|
+
<Mock name='mock.child()' id='...'>
|
|
281
|
+
>>> parent_mock = parent()
|
|
282
|
+
>>> parent_mock.mock_calls
|
|
283
|
+
[call.child()]
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
def __init__(self, name: str = "", parent: LazyMock | None = None) -> None:
|
|
287
|
+
self.parent = parent
|
|
288
|
+
self.name = name
|
|
289
|
+
self._mock: Mock | None = None
|
|
290
|
+
|
|
291
|
+
def child(self, name: str) -> LazyMock:
|
|
292
|
+
return LazyMock(name, self)
|
|
293
|
+
|
|
294
|
+
def __call__(self) -> Mock:
|
|
295
|
+
if self._mock is None:
|
|
296
|
+
self._mock = Mock(spec=object)
|
|
297
|
+
if self.parent is not None:
|
|
298
|
+
self.parent().attach_mock(self._mock, self.name)
|
|
299
|
+
return self._mock
|
|
@@ -19,7 +19,8 @@ class SingleTriggerDetector(StandardReadable, Triggerable):
|
|
|
19
19
|
**plugins: NDPluginBaseIO,
|
|
20
20
|
) -> None:
|
|
21
21
|
self.drv = drv
|
|
22
|
-
|
|
22
|
+
for k, v in plugins.items():
|
|
23
|
+
setattr(self, k, v)
|
|
23
24
|
|
|
24
25
|
self.add_readables(
|
|
25
26
|
[self.drv.array_counter, *read_uncached],
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from unittest.mock import Mock
|
|
4
|
-
|
|
5
3
|
from ophyd_async.core import (
|
|
6
4
|
Device,
|
|
7
5
|
DeviceConnector,
|
|
@@ -11,6 +9,7 @@ from ophyd_async.core import (
|
|
|
11
9
|
SignalRW,
|
|
12
10
|
SignalX,
|
|
13
11
|
)
|
|
12
|
+
from ophyd_async.core._utils import LazyMock
|
|
14
13
|
|
|
15
14
|
from ._epics_connector import fill_backend_with_prefix
|
|
16
15
|
from ._signal import PvaSignalBackend, pvget_with_timeout
|
|
@@ -64,29 +63,29 @@ class PviDeviceConnector(DeviceConnector):
|
|
|
64
63
|
backend.read_pv = read_pv
|
|
65
64
|
backend.write_pv = write_pv
|
|
66
65
|
|
|
67
|
-
async def
|
|
68
|
-
self
|
|
66
|
+
async def connect_mock(self, device: Device, mock: LazyMock):
|
|
67
|
+
self.filler.create_device_vector_entries_to_mock(2)
|
|
68
|
+
# Set the name of the device to name all children
|
|
69
|
+
device.set_name(device.name)
|
|
70
|
+
return await super().connect_mock(device, mock)
|
|
71
|
+
|
|
72
|
+
async def connect_real(
|
|
73
|
+
self, device: Device, timeout: float, force_reconnect: bool
|
|
69
74
|
) -> None:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
# This is a DeviceVector of children
|
|
85
|
-
for i, e in enumerate(entry):
|
|
86
|
-
if e:
|
|
87
|
-
self._fill_child(name, e, i)
|
|
88
|
-
# Check that all the requested children have been filled
|
|
89
|
-
self.filler.check_filled(f"{self.pvi_pv}: {entries}")
|
|
75
|
+
pvi_structure = await pvget_with_timeout(self.pvi_pv, timeout)
|
|
76
|
+
entries: dict[str, Entry | list[Entry | None]] = pvi_structure["value"].todict()
|
|
77
|
+
# Fill based on what PVI gives us
|
|
78
|
+
for name, entry in entries.items():
|
|
79
|
+
if isinstance(entry, dict):
|
|
80
|
+
# This is a child
|
|
81
|
+
self._fill_child(name, entry)
|
|
82
|
+
else:
|
|
83
|
+
# This is a DeviceVector of children
|
|
84
|
+
for i, e in enumerate(entry):
|
|
85
|
+
if e:
|
|
86
|
+
self._fill_child(name, e, i)
|
|
87
|
+
# Check that all the requested children have been filled
|
|
88
|
+
self.filler.check_filled(f"{self.pvi_pv}: {entries}")
|
|
90
89
|
# Set the name of the device to name all children
|
|
91
90
|
device.set_name(device.name)
|
|
92
|
-
return await super().
|
|
91
|
+
return await super().connect_real(device, timeout, force_reconnect)
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
from unittest.mock import Mock
|
|
2
|
-
|
|
3
1
|
import bluesky.plan_stubs as bps
|
|
4
2
|
|
|
5
|
-
from ophyd_async.core import DEFAULT_TIMEOUT, Device, wait_for_connection
|
|
3
|
+
from ophyd_async.core import DEFAULT_TIMEOUT, Device, LazyMock, wait_for_connection
|
|
6
4
|
|
|
7
5
|
|
|
8
6
|
def ensure_connected(
|
|
9
7
|
*devices: Device,
|
|
10
|
-
mock: bool |
|
|
8
|
+
mock: bool | LazyMock = False,
|
|
11
9
|
timeout: float = DEFAULT_TIMEOUT,
|
|
12
10
|
force_reconnect=False,
|
|
13
11
|
):
|