ophyd-async 0.10.0a3__tar.gz → 0.10.1__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.
Files changed (298) hide show
  1. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/PKG-INFO +2 -2
  2. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/pyproject.toml +1 -2
  3. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/_version.py +2 -2
  4. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/__init__.py +4 -0
  5. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_derived_signal.py +39 -11
  6. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_derived_signal_backend.py +7 -5
  7. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_detector.py +4 -4
  8. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_hdf_dataset.py +1 -1
  9. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_providers.py +1 -1
  10. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_signal.py +27 -6
  11. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_signal_backend.py +19 -9
  12. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_utils.py +31 -14
  13. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adcore/_core_detector.py +2 -2
  14. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adcore/_core_io.py +3 -3
  15. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adcore/_core_logic.py +34 -10
  16. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adcore/_hdf_writer.py +15 -0
  17. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adcore/_utils.py +11 -2
  18. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adpilatus/_pilatus.py +1 -1
  19. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adpilatus/_pilatus_controller.py +4 -11
  20. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/core/_aioca.py +2 -2
  21. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/core/_util.py +21 -13
  22. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/eiger/_odin_io.py +12 -13
  23. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/motor.py +3 -2
  24. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/fastcs/eiger/_eiger.py +11 -2
  25. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/fastcs/eiger/_eiger_controller.py +7 -3
  26. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/fastcs/eiger/_eiger_io.py +1 -0
  27. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/sim/_motor.py +2 -2
  28. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/core/_base_device.py +2 -1
  29. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/core/_converters.py +2 -6
  30. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/demo/_tango/_servers.py +0 -1
  31. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async.egg-info/PKG-INFO +2 -2
  32. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async.egg-info/requires.txt +1 -1
  33. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_multi_derived_signal.py +61 -2
  34. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_observe.py +56 -0
  35. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_signal.py +6 -0
  36. ophyd_async-0.10.1/tests/core/test_single_derived_signal.py +252 -0
  37. ophyd_async-0.10.1/tests/epics/adandor/test_andor.py +37 -0
  38. ophyd_async-0.10.1/tests/epics/adaravis/test_aravis.py +46 -0
  39. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/adcore/test_drivers.py +54 -2
  40. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/adcore/test_writers.py +16 -0
  41. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/adpilatus/test_pilatus.py +38 -49
  42. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/conftest.py +1 -0
  43. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/eiger/test_odin_io.py +22 -13
  44. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/signal/test_common.py +39 -1
  45. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/test_motor.py +15 -3
  46. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/fastcs/eiger/test_eiger_controller.py +7 -1
  47. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/fastcs/eiger/test_eiger_detector.py +20 -0
  48. ophyd_async-0.10.0a3/tests/core/test_single_derived_signal.py +0 -135
  49. ophyd_async-0.10.0a3/tests/epics/adandor/test_andor.py +0 -46
  50. ophyd_async-0.10.0a3/tests/epics/adaravis/test_aravis.py +0 -44
  51. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.codecov.yml +0 -0
  52. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.copier-answers.yml +0 -0
  53. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.devcontainer/devcontainer.json +0 -0
  54. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.git-blame-ignore-revs +0 -0
  55. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/CONTRIBUTING.md +0 -0
  56. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  57. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/ISSUE_TEMPLATE/issue.md +0 -0
  58. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +0 -0
  59. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/actions/install_requirements/action.yml +0 -0
  60. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/codeql/codeql-config.yml +0 -0
  61. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/dependabot.yml +0 -0
  62. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/pages/index.html +0 -0
  63. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/pages/make_switcher.py +0 -0
  64. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/workflows/_check.yml +0 -0
  65. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/workflows/_codeql.yml +0 -0
  66. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/workflows/_dist.yml +0 -0
  67. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/workflows/_docs.yml +0 -0
  68. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/workflows/_import_with_no_extras.yml +0 -0
  69. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/workflows/_pypi.yml +0 -0
  70. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/workflows/_release.yml +0 -0
  71. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/workflows/_test.yml +0 -0
  72. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/workflows/_tox.yml +0 -0
  73. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/workflows/ci.yml +0 -0
  74. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.github/workflows/periodic.yml +0 -0
  75. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.gitignore +0 -0
  76. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/.pre-commit-config.yaml +0 -0
  77. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/Dockerfile +0 -0
  78. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/LICENSE +0 -0
  79. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/README.md +0 -0
  80. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/_static/custom.css +0 -0
  81. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/conf.py +0 -0
  82. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/decisions/0001-record-architecture-decisions.md +0 -0
  83. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/decisions/0002-switched-to-python-copier-template.md +0 -0
  84. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/decisions/0003-ophyd-async-migration.rst +0 -0
  85. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/decisions/0004-repository-structure.rst +0 -0
  86. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/decisions/0005-respect-black-line-length.rst +0 -0
  87. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/decisions/0006-procedural-device-definitions.rst +0 -0
  88. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/decisions/0007-subpackage-structure.md +0 -0
  89. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/decisions/0008-signal-types.md +0 -0
  90. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/decisions/0009-procedural-vs-declarative-devices.md +0 -0
  91. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/decisions/0010-docstring-format.md +0 -0
  92. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/decisions/COPYME +0 -0
  93. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/decisions.md +0 -0
  94. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/declarative-vs-procedural.md +0 -0
  95. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/design-goals.md +0 -0
  96. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/device-connection-strategies.md +0 -0
  97. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/devices-signals-backends.md +0 -0
  98. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/flyscanning.md +0 -0
  99. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations/where-device-logic.md +0 -0
  100. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/explanations.md +0 -0
  101. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/genindex.rst +0 -0
  102. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/how-to/choose-right-baseclass.md +0 -0
  103. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/how-to/contribute.md +0 -0
  104. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/how-to/derive-one-signal-from-others.md +0 -0
  105. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/how-to/implement-ad-detector.md +0 -0
  106. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/how-to/interact-with-signals.md +0 -0
  107. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/how-to/put-device-back.md +0 -0
  108. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/how-to/store-and-retrieve.md +0 -0
  109. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/how-to.md +0 -0
  110. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/images/flyscan_collection_windows_and_frames.svg +0 -0
  111. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/images/ophyd-async-logo.svg +0 -0
  112. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/images/ophyd-favicon.svg +0 -0
  113. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/images/set_and_wait_for_other_value.excalidraw.svg +0 -0
  114. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/index.md +0 -0
  115. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/reference.md +0 -0
  116. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/tutorials/implementing-detectors.md +0 -0
  117. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/tutorials/implementing-devices.md +0 -0
  118. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/tutorials/installation.md +0 -0
  119. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/tutorials/using-devices.md +0 -0
  120. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/tutorials/writing-tests-for-devices.md +0 -0
  121. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/docs/tutorials.md +0 -0
  122. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/setup.cfg +0 -0
  123. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/__init__.py +0 -0
  124. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/__main__.py +0 -0
  125. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/_docs_parser.py +0 -0
  126. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_device.py +0 -0
  127. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_device_filler.py +0 -0
  128. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_flyer.py +0 -0
  129. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_log.py +0 -0
  130. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_mock_signal_backend.py +0 -0
  131. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_protocol.py +0 -0
  132. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_readable.py +0 -0
  133. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_settings.py +0 -0
  134. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_soft_signal_backend.py +0 -0
  135. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_status.py +0 -0
  136. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_table.py +0 -0
  137. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/core/_yaml_settings.py +0 -0
  138. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/__init__.py +0 -0
  139. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adandor/__init__.py +0 -0
  140. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adandor/_andor.py +0 -0
  141. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adandor/_andor_controller.py +0 -0
  142. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adandor/_andor_io.py +0 -0
  143. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adaravis/__init__.py +0 -0
  144. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adaravis/_aravis.py +0 -0
  145. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adaravis/_aravis_controller.py +0 -0
  146. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adaravis/_aravis_io.py +0 -0
  147. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adcore/__init__.py +0 -0
  148. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adcore/_core_writer.py +0 -0
  149. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adcore/_jpeg_writer.py +0 -0
  150. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adcore/_single_trigger.py +0 -0
  151. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adcore/_tiff_writer.py +0 -0
  152. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adkinetix/__init__.py +0 -0
  153. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adkinetix/_kinetix.py +0 -0
  154. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adkinetix/_kinetix_controller.py +0 -0
  155. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adkinetix/_kinetix_io.py +0 -0
  156. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adpilatus/__init__.py +0 -0
  157. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adpilatus/_pilatus_io.py +0 -0
  158. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adsimdetector/__init__.py +0 -0
  159. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adsimdetector/_sim.py +0 -0
  160. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adsimdetector/_sim_controller.py +0 -0
  161. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/adsimdetector/_sim_io.py +0 -0
  162. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/advimba/__init__.py +0 -0
  163. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/advimba/_vimba.py +0 -0
  164. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/advimba/_vimba_controller.py +0 -0
  165. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/advimba/_vimba_io.py +0 -0
  166. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/core/__init__.py +0 -0
  167. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/core/_epics_connector.py +0 -0
  168. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/core/_epics_device.py +0 -0
  169. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/core/_p4p.py +0 -0
  170. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/core/_pvi_connector.py +0 -0
  171. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/core/_signal.py +0 -0
  172. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/demo/__init__.py +0 -0
  173. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/demo/__main__.py +0 -0
  174. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/demo/_ioc.py +0 -0
  175. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/demo/_motor.py +0 -0
  176. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/demo/_point_detector.py +0 -0
  177. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/demo/_point_detector_channel.py +0 -0
  178. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/demo/_stage.py +0 -0
  179. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/demo/motor.db +0 -0
  180. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/demo/point_detector.db +0 -0
  181. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/demo/point_detector_channel.db +0 -0
  182. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/eiger/__init__.py +0 -0
  183. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/signal.py +0 -0
  184. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/testing/__init__.py +0 -0
  185. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/testing/_example_ioc.py +0 -0
  186. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/testing/_utils.py +0 -0
  187. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/testing/test_records.db +0 -0
  188. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/epics/testing/test_records_pva.db +0 -0
  189. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/fastcs/__init__.py +0 -0
  190. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/fastcs/core.py +0 -0
  191. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/fastcs/eiger/__init__.py +0 -0
  192. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/fastcs/odin/__init__.py +0 -0
  193. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/fastcs/panda/__init__.py +0 -0
  194. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/fastcs/panda/_block.py +0 -0
  195. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/fastcs/panda/_control.py +0 -0
  196. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/fastcs/panda/_hdf_panda.py +0 -0
  197. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/fastcs/panda/_table.py +0 -0
  198. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/fastcs/panda/_trigger.py +0 -0
  199. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/fastcs/panda/_writer.py +0 -0
  200. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/plan_stubs/__init__.py +0 -0
  201. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/plan_stubs/_ensure_connected.py +0 -0
  202. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/plan_stubs/_fly.py +0 -0
  203. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/plan_stubs/_nd_attributes.py +0 -0
  204. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/plan_stubs/_panda.py +0 -0
  205. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/plan_stubs/_settings.py +0 -0
  206. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/plan_stubs/_utils.py +0 -0
  207. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/plan_stubs/_wait_for_awaitable.py +0 -0
  208. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/py.typed +0 -0
  209. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/sim/__init__.py +0 -0
  210. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/sim/__main__.py +0 -0
  211. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/sim/_blob_detector.py +0 -0
  212. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/sim/_blob_detector_controller.py +0 -0
  213. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/sim/_blob_detector_writer.py +0 -0
  214. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/sim/_mirror_horizontal.py +0 -0
  215. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/sim/_mirror_vertical.py +0 -0
  216. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/sim/_pattern_generator.py +0 -0
  217. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/sim/_point_detector.py +0 -0
  218. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/sim/_stage.py +0 -0
  219. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/__init__.py +0 -0
  220. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/core/__init__.py +0 -0
  221. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/core/_signal.py +8 -8
  222. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/core/_tango_readable.py +0 -0
  223. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/core/_tango_transport.py +12 -12
  224. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/core/_utils.py +0 -0
  225. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/demo/__init__.py +0 -0
  226. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/demo/_counter.py +0 -0
  227. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/demo/_detector.py +0 -0
  228. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/demo/_mover.py +0 -0
  229. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/demo/_tango/__init__.py +0 -0
  230. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/testing/__init__.py +0 -0
  231. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/tango/testing/_one_of_everything.py +2 -2
  232. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/testing/__init__.py +0 -0
  233. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/testing/__pytest_assert_rewrite.py +0 -0
  234. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/testing/_assert.py +0 -0
  235. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/testing/_mock_signal_utils.py +0 -0
  236. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/testing/_one_of_everything.py +0 -0
  237. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/testing/_single_derived.py +0 -0
  238. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/testing/_utils.py +0 -0
  239. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async/testing/_wait_for_pending.py +0 -0
  240. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async.egg-info/SOURCES.txt +0 -0
  241. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async.egg-info/dependency_links.txt +0 -0
  242. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/src/ophyd_async.egg-info/top_level.txt +0 -0
  243. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/system_tests/epics/eiger/README.md +0 -0
  244. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/system_tests/epics/eiger/start_iocs_and_run_tests.sh +0 -0
  245. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/system_tests/epics/eiger/test_eiger_system.py +0 -0
  246. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/conftest.py +0 -0
  247. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_auto_init_devices.py +0 -0
  248. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_detector.py +0 -0
  249. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_device.py +0 -0
  250. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_flyer.py +0 -0
  251. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_log.py +0 -0
  252. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_mock_signal_backend.py +0 -0
  253. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_protocol.py +0 -0
  254. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_providers.py +0 -0
  255. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_readable.py +0 -0
  256. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_soft_signal_backend.py +0 -0
  257. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_status.py +0 -0
  258. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_subset_enum.py +0 -0
  259. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_table.py +0 -0
  260. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_utils.py +0 -0
  261. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/core/test_watchable_async_status.py +0 -0
  262. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/adcore/test_cont_acq_detector.py +0 -0
  263. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/adcore/test_detectors.py +0 -0
  264. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/adcore/test_scans.py +0 -0
  265. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/adcore/test_single_trigger.py +0 -0
  266. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/adkinetix/test_kinetix.py +0 -0
  267. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/adsimdetector/test_sim.py +0 -0
  268. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/advimba/test_vimba.py +0 -0
  269. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/demo/test_epics_demo.py +0 -0
  270. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/pvi/test_pvi.py +0 -0
  271. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/signal/test_signals.py +0 -0
  272. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/signal/test_yaml_save_ca.yaml +0 -0
  273. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/signal/test_yaml_save_pva.yaml +0 -0
  274. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/epics/test_areadetector_subclass_naming.py +0 -0
  275. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/fastcs/panda/db/panda.db +0 -0
  276. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/fastcs/panda/test_hdf_panda.py +0 -0
  277. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/fastcs/panda/test_panda_connect.py +0 -0
  278. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/fastcs/panda/test_panda_control.py +0 -0
  279. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/fastcs/panda/test_panda_utils.py +0 -0
  280. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/fastcs/panda/test_seq_table.py +0 -0
  281. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/fastcs/panda/test_trigger.py +0 -0
  282. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/fastcs/panda/test_writer.py +0 -0
  283. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/plan_stubs/test_ensure_connected.py +0 -0
  284. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/plan_stubs/test_fly.py +0 -0
  285. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/plan_stubs/test_settings.py +0 -0
  286. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/plan_stubs/test_setup.py +0 -0
  287. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/sim/__init__.py +0 -0
  288. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/sim/test_sim_blob_detector.py +0 -0
  289. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/sim/test_sim_motor.py +0 -0
  290. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/tango/conftest.py +1 -1
  291. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/tango/context_subprocess.py +0 -0
  292. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/tango/test_base_device.py +10 -10
  293. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/tango/test_tango_signals.py +0 -0
  294. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/tango/test_tango_transport.py +9 -9
  295. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/test_cli.py +0 -0
  296. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/test_data/test_yaml_config_save.yaml +0 -0
  297. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/test_data/test_yaml_save.yaml +0 -0
  298. {ophyd_async-0.10.0a3 → ophyd_async-0.10.1}/tests/test_tutorials.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ophyd-async
3
- Version: 0.10.0a3
3
+ Version: 0.10.1
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
@@ -43,7 +43,7 @@ Description-Content-Type: text/markdown
43
43
  License-File: LICENSE
44
44
  Requires-Dist: numpy
45
45
  Requires-Dist: bluesky>=1.13.1rc2
46
- Requires-Dist: event-model>=1.22.1
46
+ Requires-Dist: event-model>=1.23
47
47
  Requires-Dist: pyyaml
48
48
  Requires-Dist: colorlog
49
49
  Requires-Dist: pydantic>=2.0
@@ -15,7 +15,7 @@ description = "Asynchronous Bluesky hardware abstraction code, compatible with c
15
15
  dependencies = [
16
16
  "numpy",
17
17
  "bluesky>=1.13.1rc2",
18
- "event-model>=1.22.1",
18
+ "event-model>=1.23",
19
19
  "pyyaml",
20
20
  "colorlog",
21
21
  "pydantic>=2.0",
@@ -134,7 +134,6 @@ commands =
134
134
  """
135
135
 
136
136
  [tool.ruff]
137
- src = ["src", "tests", "system_tests"]
138
137
  line-length = 88
139
138
  lint.select = [
140
139
  "B", # flake8-bugbear - https://docs.astral.sh/ruff/rules/#flake8-bugbear-b
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.10.0a3'
21
- __version_tuple__ = version_tuple = (0, 10, 0, 'a3')
20
+ __version__ = version = '0.10.1'
21
+ __version_tuple__ = version_tuple = (0, 10, 1)
@@ -75,11 +75,13 @@ from ._utils import (
75
75
  DEFAULT_TIMEOUT,
76
76
  CalculatableTimeout,
77
77
  Callback,
78
+ EnumTypes,
78
79
  LazyMock,
79
80
  NotConnected,
80
81
  Reference,
81
82
  StrictEnum,
82
83
  SubsetEnum,
84
+ SupersetEnum,
83
85
  WatcherUpdate,
84
86
  gather_dict,
85
87
  get_dtype,
@@ -122,6 +124,8 @@ __all__ = [
122
124
  "Array1D",
123
125
  "StrictEnum",
124
126
  "SubsetEnum",
127
+ "SupersetEnum",
128
+ "EnumTypes",
125
129
  "Table",
126
130
  "SignalMetadata",
127
131
  # Soft signal
@@ -1,5 +1,7 @@
1
1
  from collections.abc import Awaitable, Callable
2
- from typing import Any, Generic, get_type_hints, is_typeddict
2
+ from typing import Any, Generic, get_args, get_origin, get_type_hints, is_typeddict
3
+
4
+ from bluesky.protocols import Locatable
3
5
 
4
6
  from ._derived_signal_backend import (
5
7
  DerivedSignalBackend,
@@ -8,7 +10,7 @@ from ._derived_signal_backend import (
8
10
  TransformT,
9
11
  )
10
12
  from ._device import Device
11
- from ._signal import SignalR, SignalRW, SignalT, SignalW
13
+ from ._signal import Signal, SignalR, SignalRW, SignalT, SignalW
12
14
  from ._signal_backend import SignalDatatypeT
13
15
 
14
16
 
@@ -35,15 +37,27 @@ class DerivedSignalFactory(Generic[TransformT]):
35
37
  self._set_derived = set_derived
36
38
  # Check the raw and transform devices match the input arguments of the Transform
37
39
  if transform_cls is not Transform:
38
- expected = list(transform_cls.model_fields) + [
39
- x
40
- for x in get_type_hints(transform_cls.raw_to_derived)
41
- if x not in ["self", "return"]
42
- ]
43
- if set(expected) != set(raw_and_transform_devices):
40
+ # Populate expected parameters and types
41
+ expected = {
42
+ **{k: f.annotation for k, f in transform_cls.model_fields.items()},
43
+ **{
44
+ k: v
45
+ for k, v in get_type_hints(transform_cls.raw_to_derived).items()
46
+ if k not in {"self", "return"}
47
+ },
48
+ }
49
+
50
+ # Populate received parameters and types
51
+ # Use Signal datatype, or Locatable datatype, or set type as None
52
+ received = {
53
+ k: v.datatype if isinstance(v, Signal) else get_locatable_type(v)
54
+ for k, v in raw_and_transform_devices.items()
55
+ }
56
+
57
+ if expected != received:
44
58
  msg = (
45
- f"Expected devices to be passed as keyword arguments {expected}, "
46
- f"got {list(raw_and_transform_devices)}"
59
+ f"Expected devices to be passed as keyword arguments "
60
+ f"{expected}, got {received}"
47
61
  )
48
62
  raise TypeError(msg)
49
63
  self._set_derived_takes_dict = (
@@ -234,7 +248,7 @@ def derived_signal_rw(
234
248
  if raw_to_derived_datatype != set_derived_datatype:
235
249
  msg = (
236
250
  f"{raw_to_derived} has datatype {raw_to_derived_datatype} "
237
- f"!= {set_derived_datatype} dataype {set_derived_datatype}"
251
+ f"!= {set_derived_datatype} datatype {set_derived_datatype}"
238
252
  )
239
253
  raise TypeError(msg)
240
254
 
@@ -269,3 +283,17 @@ def derived_signal_w(
269
283
  units=derived_units,
270
284
  precision=derived_precision,
271
285
  )
286
+
287
+
288
+ def get_locatable_type(obj: object) -> type | None:
289
+ """Extract datatype from Locatable parent class.
290
+
291
+ :param obj: Object with possible Locatable inheritance
292
+ :return: Type hint associated with Locatable, or None if not found.
293
+ """
294
+ for base in getattr(obj.__class__, "__orig_bases__", []):
295
+ if get_origin(base) is Locatable:
296
+ args = get_args(base)
297
+ if args:
298
+ return args[0]
299
+ return None
@@ -61,7 +61,7 @@ class Transform(BaseModel, Generic[RawT, DerivedT]):
61
61
  TransformT = TypeVar("TransformT", bound=Transform)
62
62
 
63
63
 
64
- def filter_by_type(raw_devices: Mapping[str, Any], type_: type[T]) -> dict[str, T]:
64
+ def validate_by_type(raw_devices: Mapping[str, Any], type_: type[T]) -> dict[str, T]:
65
65
  filtered_devices: dict[str, T] = {}
66
66
  for name, device in raw_devices.items():
67
67
  if not isinstance(device, type_):
@@ -91,21 +91,23 @@ class SignalTransformer(Generic[TransformT]):
91
91
 
92
92
  @cached_property
93
93
  def raw_locatables(self) -> dict[str, AsyncLocatable]:
94
- return filter_by_type(self._raw_devices, AsyncLocatable)
94
+ return validate_by_type(self._raw_devices, AsyncLocatable)
95
95
 
96
96
  @cached_property
97
97
  def transform_readables(self) -> dict[str, AsyncReadable]:
98
- return filter_by_type(self._transform_devices, AsyncReadable)
98
+ return validate_by_type(self._transform_devices, AsyncReadable)
99
99
 
100
100
  @cached_property
101
101
  def raw_and_transform_readables(self) -> dict[str, AsyncReadable]:
102
- return filter_by_type(
102
+ return validate_by_type(
103
103
  self._raw_devices | self._transform_devices, AsyncReadable
104
104
  )
105
105
 
106
106
  @cached_property
107
107
  def raw_and_transform_subscribables(self) -> dict[str, Subscribable]:
108
- return filter_by_type(self._raw_devices | self._transform_devices, Subscribable)
108
+ return validate_by_type(
109
+ self._raw_devices | self._transform_devices, Subscribable
110
+ )
109
111
 
110
112
  def _complete_cached_reading(self) -> dict[str, Reading] | None:
111
113
  if self._cached_readings and len(self._cached_readings) == len(
@@ -328,10 +328,10 @@ class StandardDetector(
328
328
  if isinstance(value.number_of_events, list)
329
329
  else [value.number_of_events]
330
330
  )
331
- self._describe, _ = await asyncio.gather(
332
- self._writer.open(self.name, value.exposures_per_event),
333
- self._controller.prepare(value),
334
- )
331
+
332
+ await self._controller.prepare(value)
333
+ self._describe = await self._writer.open(self.name, value.exposures_per_event)
334
+
335
335
  self._initial_frame = await self._writer.get_indices_written()
336
336
  if value.trigger != DetectorTrigger.INTERNAL:
337
337
  await self._controller.arm()
@@ -23,7 +23,7 @@ class HDFDatasetDescription(BaseModel):
23
23
  """The dataset name within the HDF file,
24
24
  e.g. /entry/data/data or /entry/instrument/NDAttributes/sum"""
25
25
 
26
- shape: tuple[int, ...] = Field(default_factory=tuple)
26
+ shape: tuple[int | None, ...] = Field(default_factory=tuple)
27
27
  """The shape of a single event's data in the HDF file,
28
28
  e.g. (1, 768, 1024) for arrays or () for scalars"""
29
29
 
@@ -228,5 +228,5 @@ class DatasetDescriber(Protocol):
228
228
  """Return the numpy datatype for this dataset."""
229
229
 
230
230
  @abstractmethod
231
- async def shape(self) -> tuple[int, ...]:
231
+ async def shape(self) -> tuple[int | None, ...]:
232
232
  """Get the shape of the data collection."""
@@ -100,6 +100,11 @@ class Signal(Device, Generic[SignalDatatypeT]):
100
100
  """
101
101
  return self._connector.backend.source(self.name, read=True)
102
102
 
103
+ @property
104
+ def datatype(self) -> type[SignalDatatypeT] | None:
105
+ """Returns the datatype of the signal."""
106
+ return self._connector.backend.datatype
107
+
103
108
 
104
109
  SignalT = TypeVar("SignalT", bound=Signal)
105
110
 
@@ -158,7 +163,12 @@ class _SignalCache(Generic[SignalDatatypeT]):
158
163
  self._notify(function, want_value)
159
164
 
160
165
  def unsubscribe(self, function: Callback) -> bool:
161
- self._listeners.pop(function)
166
+ _listener = self._listeners.pop(function, None)
167
+ if not _listener:
168
+ self._signal.log.warning(
169
+ f"Unsubscribe failed: subscriber {function} was not found "
170
+ f" in listeners list: {list(self._listeners)}"
171
+ )
162
172
  return self._staged or bool(self._listeners)
163
173
 
164
174
  def set_staged(self, staged: bool) -> bool:
@@ -431,8 +441,8 @@ async def observe_signals_value(
431
441
  Call subscribe_value on all the signals at the start, and clear_sub on
432
442
  it at the end.
433
443
  :param timeout:
434
- If given, how long to wait for each updated value in seconds. If an
435
- update is not produced in this time then raise asyncio.TimeoutError.
444
+ If given, how long to wait for ANY updated value from shared queue in seconds.
445
+ If an update is not produced in this time then raise asyncio.TimeoutError.
436
446
  :param done_status:
437
447
  If this status is complete, stop observing and make the iterator return.
438
448
  If it raises an exception then this exception will be raised by the
@@ -454,8 +464,10 @@ async def observe_signals_value(
454
464
  q: asyncio.Queue[tuple[SignalR[SignalDatatypeT], SignalDatatypeT] | Status] = (
455
465
  asyncio.Queue()
456
466
  )
457
-
467
+ # dict to store signal subscription to remove it later
458
468
  cbs: dict[SignalR, Callback] = {}
469
+
470
+ # subscribe signal to update queue and fill cbs dict
459
471
  for signal in signals:
460
472
 
461
473
  def queue_value(value: SignalDatatypeT, signal=signal):
@@ -468,6 +480,7 @@ async def observe_signals_value(
468
480
  done_status.add_callback(q.put_nowait)
469
481
  overall_deadline = time.monotonic() + done_timeout if done_timeout else None
470
482
  try:
483
+ last_item = ()
471
484
  while True:
472
485
  if overall_deadline and time.monotonic() >= overall_deadline:
473
486
  raise asyncio.TimeoutError(
@@ -476,14 +489,22 @@ async def observe_signals_value(
476
489
  f"timeout {done_timeout}s"
477
490
  )
478
491
  iteration_timeout = _get_iteration_timeout(timeout, overall_deadline)
479
- item = await asyncio.wait_for(q.get(), iteration_timeout)
492
+ try:
493
+ item = await asyncio.wait_for(q.get(), iteration_timeout)
494
+ except asyncio.TimeoutError as exc:
495
+ raise asyncio.TimeoutError(
496
+ f"Timeout Error while waiting {iteration_timeout}s to update "
497
+ f"{[signal.source for signal in signals]}. "
498
+ f"Last observed signal and value were {last_item}"
499
+ ) from exc
480
500
  if done_status and item is done_status:
481
501
  if exc := done_status.exception():
482
502
  raise exc
483
503
  else:
484
504
  break
485
505
  else:
486
- yield cast(tuple[SignalR[SignalDatatypeT], SignalDatatypeT], item)
506
+ last_item = cast(tuple[SignalR[SignalDatatypeT], SignalDatatypeT], item)
507
+ yield last_item
487
508
  finally:
488
509
  for signal, cb in cbs.items():
489
510
  signal.clear_sub(cb)
@@ -6,8 +6,16 @@ import numpy as np
6
6
  from bluesky.protocols import Reading
7
7
  from event_model import DataKey, Dtype, Limits
8
8
 
9
+ from ophyd_async.core._utils import (
10
+ Callback,
11
+ EnumTypes,
12
+ StrictEnum,
13
+ SubsetEnum,
14
+ SupersetEnum,
15
+ get_enum_cls,
16
+ )
17
+
9
18
  from ._table import Table
10
- from ._utils import Callback, StrictEnum, get_enum_cls
11
19
 
12
20
  DTypeScalar_co = TypeVar("DTypeScalar_co", covariant=True, bound=np.generic)
13
21
  """A numpy dtype like [](#numpy.float64)."""
@@ -24,7 +32,7 @@ E.g. `Array1D[np.float64]` is a 1D numpy array of 64-bit floats."""
24
32
  Primitive = bool | int | float | str
25
33
  SignalDatatype = (
26
34
  Primitive
27
- | StrictEnum
35
+ | EnumTypes
28
36
  | Array1D[np.bool_]
29
37
  | Array1D[np.int8]
30
38
  | Array1D[np.uint8]
@@ -39,16 +47,18 @@ SignalDatatype = (
39
47
  | np.ndarray
40
48
  | Sequence[str]
41
49
  | Sequence[StrictEnum]
50
+ | Sequence[SubsetEnum]
51
+ | Sequence[SupersetEnum]
42
52
  | Table
43
53
  )
44
54
  """The supported [](#Signal) datatypes:
45
55
 
46
56
  - A python primitive [](#bool), [](#int), [](#float), [](#str)
47
- - A [](#StrictEnum) or [](#SubsetEnum) subclass
57
+ - An [](#EnumTypes) subclass
48
58
  - A fixed datatype [](#Array1D) of numpy bool, signed and unsigned integers or float
49
59
  - A [](#numpy.ndarray) which can change dimensions and datatype at runtime
50
60
  - A sequence of [](#str)
51
- - A sequence of [](#StrictEnum) or [](#SubsetEnum) subclass
61
+ - A sequence of [](#EnumTypes) subclasses
52
62
  - A [](#Table) subclass
53
63
  """
54
64
  # TODO: These typevars will not be needed when we drop python 3.11
@@ -58,7 +68,7 @@ PrimitiveT = TypeVar("PrimitiveT", bound=Primitive)
58
68
  SignalDatatypeT = TypeVar("SignalDatatypeT", bound=SignalDatatype)
59
69
  """A typevar for a [](#SignalDatatype)."""
60
70
  SignalDatatypeV = TypeVar("SignalDatatypeV", bound=SignalDatatype)
61
- EnumT = TypeVar("EnumT", bound=StrictEnum)
71
+ EnumT = TypeVar("EnumT", bound=EnumTypes)
62
72
  TableT = TypeVar("TableT", bound=Table)
63
73
 
64
74
 
@@ -136,7 +146,7 @@ def _datakey_dtype(datatype: type[SignalDatatype]) -> Dtype:
136
146
  or issubclass(datatype, Table)
137
147
  ):
138
148
  return "array"
139
- elif issubclass(datatype, StrictEnum):
149
+ elif issubclass(datatype, EnumTypes):
140
150
  return "string"
141
151
  elif issubclass(datatype, Primitive):
142
152
  return _primitive_dtype[datatype]
@@ -153,7 +163,7 @@ def _datakey_dtype_numpy(
153
163
  elif (
154
164
  get_origin(datatype) is Sequence
155
165
  or datatype is str
156
- or issubclass(datatype, StrictEnum)
166
+ or issubclass(datatype, EnumTypes)
157
167
  ):
158
168
  # TODO: use np.dtypes.StringDType when we can use in structured arrays
159
169
  # https://github.com/numpy/numpy/issues/25693
@@ -166,8 +176,8 @@ def _datakey_dtype_numpy(
166
176
  raise TypeError(f"Can't make dtype_numpy for {datatype}")
167
177
 
168
178
 
169
- def _datakey_shape(value: SignalDatatype) -> list[int]:
170
- if type(value) in _primitive_dtype or isinstance(value, StrictEnum):
179
+ def _datakey_shape(value: SignalDatatype) -> list[int | None]:
180
+ if type(value) in _primitive_dtype or isinstance(value, EnumTypes):
171
181
  return []
172
182
  elif isinstance(value, np.ndarray):
173
183
  return list(value.shape)
@@ -5,7 +5,15 @@ import logging
5
5
  from collections.abc import Awaitable, Callable, Iterable, Mapping, Sequence
6
6
  from dataclasses import dataclass
7
7
  from enum import Enum, EnumMeta
8
- from typing import Any, Generic, Literal, ParamSpec, TypeVar, get_args, get_origin
8
+ from typing import (
9
+ Any,
10
+ Generic,
11
+ Literal,
12
+ ParamSpec,
13
+ TypeVar,
14
+ get_args,
15
+ get_origin,
16
+ )
9
17
  from unittest.mock import Mock
10
18
 
11
19
  import numpy as np
@@ -19,20 +27,16 @@ DEFAULT_TIMEOUT = 10.0
19
27
  logger = logging.getLogger("ophyd_async")
20
28
 
21
29
 
22
- class StrictEnumMeta(EnumMeta):
23
- def __new__(metacls, *args, **kwargs):
24
- ret = super().__new__(metacls, *args, **kwargs)
30
+ class UppercaseNameEnumMeta(EnumMeta):
31
+ def __new__(cls, *args, **kwargs):
32
+ ret = super().__new__(cls, *args, **kwargs)
25
33
  lowercase_names = [x.name for x in ret if not x.name.isupper()] # type: ignore
26
34
  if lowercase_names:
27
35
  raise TypeError(f"Names {lowercase_names} should be uppercase")
28
36
  return ret
29
37
 
30
38
 
31
- class StrictEnum(str, Enum, metaclass=StrictEnumMeta):
32
- """All members should exist in the Backend, and there will be no extras."""
33
-
34
-
35
- class SubsetEnumMeta(StrictEnumMeta):
39
+ class AnyStringUppercaseNameEnumMeta(UppercaseNameEnumMeta):
36
40
  def __call__(self, value, *args, **kwargs): # type: ignore
37
41
  """Return given value if it is a string and not a member of the enum.
38
42
 
@@ -54,10 +58,21 @@ class SubsetEnumMeta(StrictEnumMeta):
54
58
  return super().__call__(value, *args, **kwargs)
55
59
 
56
60
 
57
- class SubsetEnum(StrictEnum, metaclass=SubsetEnumMeta):
61
+ class StrictEnum(str, Enum, metaclass=UppercaseNameEnumMeta):
62
+ """All members should exist in the Backend, and there will be no extras."""
63
+
64
+
65
+ class SubsetEnum(str, Enum, metaclass=AnyStringUppercaseNameEnumMeta):
58
66
  """All members should exist in the Backend, but there may be extras."""
59
67
 
60
68
 
69
+ class SupersetEnum(str, Enum, metaclass=UppercaseNameEnumMeta):
70
+ """Some members should exist in the Backend, and there should be no extras."""
71
+
72
+
73
+ EnumTypes = StrictEnum | SubsetEnum | SupersetEnum
74
+
75
+
61
76
  CALCULATE_TIMEOUT = "CALCULATE_TIMEOUT"
62
77
  """Sentinel used to implement ``myfunc(timeout=CalculateTimeout)``
63
78
 
@@ -207,10 +222,11 @@ def get_dtype(datatype: type) -> np.dtype:
207
222
  return np.dtype(get_args(get_args(datatype)[1])[0])
208
223
 
209
224
 
210
- def get_enum_cls(datatype: type | None) -> type[StrictEnum] | None:
225
+ def get_enum_cls(datatype: type | None) -> type[EnumTypes] | None:
211
226
  """Get the enum class from a datatype.
212
227
 
213
- :raises TypeError: if type is not a [](#StrictEnum) or [](#SubsetEnum) subclass
228
+ :raises TypeError: if type is not a [](#StrictEnum) or [](#SubsetEnum)
229
+ or [](#SupersetEnum) subclass
214
230
  ```python
215
231
  >>> from ophyd_async.core import StrictEnum
216
232
  >>> from collections.abc import Sequence
@@ -227,10 +243,11 @@ def get_enum_cls(datatype: type | None) -> type[StrictEnum] | None:
227
243
  if get_origin(datatype) is Sequence:
228
244
  datatype = get_args(datatype)[0]
229
245
  if datatype and issubclass(datatype, Enum):
230
- if not issubclass(datatype, StrictEnum):
246
+ if not issubclass(datatype, EnumTypes):
231
247
  raise TypeError(
232
248
  f"{datatype} should inherit from ophyd_async.core.SubsetEnum "
233
- "or ophyd_async.core.StrictEnum"
249
+ "or ophyd_async.core.StrictEnum "
250
+ "or ophyd_async.core.SupersetEnum."
234
251
  )
235
252
  return datatype
236
253
  return None
@@ -22,8 +22,8 @@ class AreaDetector(StandardDetector[ADBaseControllerT, ADWriter]):
22
22
  self.fileio = writer.fileio
23
23
 
24
24
  if plugins is not None:
25
- for name, plugin in plugins.items():
26
- setattr(self, name, plugin)
25
+ for plugin_name, plugin in plugins.items():
26
+ setattr(self, plugin_name, plugin)
27
27
 
28
28
  super().__init__(
29
29
  controller,
@@ -70,14 +70,14 @@ class NDPluginStatsIO(NDPluginBaseIO):
70
70
  # Basic statistics
71
71
  compute_statistics: A[SignalRW[bool], PvSuffix.rbv("ComputeStatistics")]
72
72
  bgd_width: A[SignalRW[int], PvSuffix.rbv("BgdWidth")]
73
- total_array: A[SignalRW[float], PvSuffix.rbv("TotalArray")]
73
+ total: A[SignalR[float], PvSuffix.rbv("Total")]
74
74
  # Centroid statistics
75
75
  compute_centroid: A[SignalRW[bool], PvSuffix.rbv("ComputeCentroid")]
76
76
  centroid_threshold: A[SignalRW[float], PvSuffix.rbv("CentroidThreshold")]
77
77
  # X and Y Profiles
78
78
  compute_profiles: A[SignalRW[bool], PvSuffix.rbv("ComputeProfiles")]
79
- profile_size_x: A[SignalRW[int], PvSuffix.rbv("ProfileSizeX")]
80
- profile_size_y: A[SignalRW[int], PvSuffix.rbv("ProfileSizeY")]
79
+ profile_size_x: A[SignalR[int], PvSuffix.rbv("ProfileSizeX")]
80
+ profile_size_y: A[SignalR[int], PvSuffix.rbv("ProfileSizeY")]
81
81
  cursor_x: A[SignalRW[int], PvSuffix.rbv("CursorX")]
82
82
  cursor_y: A[SignalRW[int], PvSuffix.rbv("CursorY")]
83
83
  # Array Histogram
@@ -7,6 +7,7 @@ from ophyd_async.core import (
7
7
  DetectorController,
8
8
  DetectorTrigger,
9
9
  TriggerInfo,
10
+ observe_value,
10
11
  set_and_wait_for_value,
11
12
  )
12
13
 
@@ -89,34 +90,57 @@ class ADBaseController(DetectorController, Generic[ADBaseIOT]):
89
90
  self.driver.acquire_period.set(full_frame_time, timeout=timeout),
90
91
  )
91
92
 
92
- async def start_acquiring_driver_and_ensure_status(self) -> AsyncStatus:
93
+ async def start_acquiring_driver_and_ensure_status(
94
+ self,
95
+ start_timeout: float = DEFAULT_TIMEOUT,
96
+ state_timeout: float = DEFAULT_TIMEOUT,
97
+ ) -> AsyncStatus:
93
98
  """Start acquiring driver, raising ValueError if the detector is in a bad state.
94
99
 
95
100
  This sets driver.acquire to True, and waits for it to be True up to a timeout.
96
101
  Then, it checks that the DetectorState PV is in DEFAULT_GOOD_STATES,
97
102
  and otherwise raises a ValueError.
98
103
 
104
+
105
+ :param start_timeout:
106
+ Timeout used for waiting for the driver to start
107
+ acquiring.
108
+ :param state_timeout:
109
+ Timeout used for waiting for the detector to be in a good
110
+ state after it stops acquiring.
99
111
  :returns AsyncStatus:
100
112
  An AsyncStatus that can be awaited to set driver.acquire to True and perform
101
113
  subsequent raising (if applicable) due to detector state.
114
+
102
115
  """
103
116
  status = await set_and_wait_for_value(
104
117
  self.driver.acquire,
105
118
  True,
106
- timeout=DEFAULT_TIMEOUT,
119
+ timeout=start_timeout,
107
120
  wait_for_set_completion=False,
108
121
  )
109
122
 
110
123
  async def complete_acquisition() -> None:
111
- # NOTE: possible race condition here between the callback from
112
- # set_and_wait_for_value and the detector state updating.
113
124
  await status
114
- state = await self.driver.detector_state.get_value()
115
- if state not in self.good_states:
116
- raise ValueError(
117
- f"Final detector state {state.value} not "
118
- "in valid end states: {self.good_states}"
119
- )
125
+ state = None
126
+ try:
127
+ async for state in observe_value(
128
+ self.driver.detector_state, done_timeout=state_timeout
129
+ ):
130
+ if state in self.good_states:
131
+ return
132
+ except asyncio.TimeoutError as exc:
133
+ if state is not None:
134
+ raise ValueError(
135
+ f"Final detector state {state.value} not in valid end "
136
+ f"states: {self.good_states}"
137
+ ) from exc
138
+ else:
139
+ # No updates from the detector, something else is wrong
140
+ raise asyncio.TimeoutError(
141
+ "Could not monitor detector state: "
142
+ + self.driver.detector_state.source
143
+ ) from exc
120
144
 
121
145
  return AsyncStatus(complete_acquisition())
122
146
 
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  from collections.abc import AsyncIterator
3
3
  from pathlib import Path
4
+ from typing import TypeGuard
4
5
  from xml.etree import ElementTree as ET
5
6
 
6
7
  from bluesky.protocols import StreamAsset
@@ -22,6 +23,10 @@ from ._utils import (
22
23
  )
23
24
 
24
25
 
26
+ def _is_fully_described(shape: tuple[int | None, ...]) -> TypeGuard[tuple[int, ...]]:
27
+ return None not in shape
28
+
29
+
25
30
  class ADHDFWriter(ADWriter[NDFileHDFIO]):
26
31
  """Allow `NDFileHDFIO` to be used within `StandardDetector`."""
27
32
 
@@ -75,6 +80,16 @@ class ADHDFWriter(ADWriter[NDFileHDFIO]):
75
80
  # Determine number of frames that will be saved per HDF chunk
76
81
  frames_per_chunk = await self.fileio.num_frames_chunks.get_value()
77
82
 
83
+ if not _is_fully_described(detector_shape):
84
+ # Questions:
85
+ # - Can AreaDetector support this?
86
+ # - How to deal with chunking?
87
+ # Don't support for now - leave option open to support it later
88
+ raise ValueError(
89
+ "Datasets with partially unknown dimensionality "
90
+ "are not currently supported by ADHDFWriter."
91
+ )
92
+
78
93
  # Add the main data
79
94
  self._datasets = [
80
95
  HDFDatasetDescription(
@@ -7,11 +7,12 @@ from ophyd_async.core import (
7
7
  SignalRW,
8
8
  StrictEnum,
9
9
  SubsetEnum,
10
+ SupersetEnum,
10
11
  wait_for_value,
11
12
  )
12
13
 
13
14
 
14
- class ADBaseDataType(StrictEnum):
15
+ class ADBaseDataType(SupersetEnum):
15
16
  INT8 = "Int8"
16
17
  UINT8 = "UInt8"
17
18
  INT16 = "Int16"
@@ -22,6 +23,9 @@ class ADBaseDataType(StrictEnum):
22
23
  UINT64 = "UInt64"
23
24
  FLOAT32 = "Float32"
24
25
  FLOAT64 = "Float64"
26
+ # Driver database override will blank the enum string if it doesn't
27
+ # support a datatype
28
+ UNDEFINED = ""
25
29
 
26
30
 
27
31
  def convert_ad_dtype_to_np(ad_dtype: ADBaseDataType) -> str:
@@ -37,7 +41,12 @@ def convert_ad_dtype_to_np(ad_dtype: ADBaseDataType) -> str:
37
41
  ADBaseDataType.FLOAT32: "<f4",
38
42
  ADBaseDataType.FLOAT64: "<f8",
39
43
  }
40
- return ad_dtype_to_np_dtype[ad_dtype]
44
+ np_type = ad_dtype_to_np_dtype.get(ad_dtype)
45
+ if np_type is None:
46
+ raise ValueError(
47
+ "Areadetector driver has a blank DataType, this is not supported"
48
+ )
49
+ return np_type
41
50
 
42
51
 
43
52
  def convert_pv_dtype_to_np(datatype: str) -> str:
@@ -27,7 +27,7 @@ class PilatusDetector(AreaDetector[PilatusController]):
27
27
  config_sigs: Sequence[SignalR] = (),
28
28
  ):
29
29
  driver = PilatusDriverIO(prefix + drv_suffix)
30
- controller = PilatusController(driver)
30
+ controller = PilatusController(driver, readout_time=readout_time)
31
31
 
32
32
  writer = writer_cls.with_io(
33
33
  prefix,