ophyd-async 0.3a5__tar.gz → 0.3rc2__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 (205) hide show
  1. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.codecov.yml +1 -1
  2. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.github/workflows/_release.yml +1 -1
  3. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/PKG-INFO +1 -1
  4. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/explanations/design-goals.rst +1 -1
  5. ophyd_async-0.3rc2/docs/explanations/flyscanning.rst +29 -0
  6. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/how-to/write-tests-for-devices.rst +5 -0
  7. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/_version.py +1 -1
  8. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/core/__init__.py +7 -5
  9. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/core/async_status.py +11 -28
  10. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/core/detector.py +1 -1
  11. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/core/device.py +2 -4
  12. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/core/flyer.py +1 -1
  13. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/core/mock_signal_backend.py +5 -8
  14. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/core/mock_signal_utils.py +10 -14
  15. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/core/signal.py +23 -17
  16. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/core/signal_backend.py +1 -1
  17. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/core/utils.py +11 -0
  18. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/drivers/ad_base.py +1 -7
  19. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/writers/hdf_writer.py +3 -2
  20. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/writers/nd_file_hdf.py +0 -2
  21. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/writers/nd_plugin.py +9 -0
  22. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/demo/__init__.py +30 -26
  23. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/motion/motor.py +35 -29
  24. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/panda/__init__.py +2 -0
  25. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/panda/writers/_hdf_writer.py +4 -4
  26. {ophyd_async-0.3a5/src/ophyd_async/planstubs → ophyd_async-0.3rc2/src/ophyd_async/plan_stubs}/__init__.py +5 -1
  27. ophyd_async-0.3rc2/src/ophyd_async/plan_stubs/fly.py +149 -0
  28. ophyd_async-0.3rc2/src/ophyd_async/sim/demo/sim_motor.py +103 -0
  29. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async.egg-info/PKG-INFO +1 -1
  30. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async.egg-info/SOURCES.txt +4 -7
  31. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/core/test_async_status.py +5 -4
  32. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/core/test_device.py +3 -5
  33. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/core/test_device_collector.py +2 -0
  34. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/core/test_flyer.py +12 -12
  35. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/core/test_mock_signal_backend.py +47 -27
  36. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/core/test_watchable_async_status.py +2 -6
  37. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/demo/test_demo.py +24 -0
  38. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/demo/test_demo_ad_sim_detector.py +28 -0
  39. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/motion/test_motor.py +28 -0
  40. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/panda/test_hdf_panda.py +4 -8
  41. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/panda/test_trigger.py +1 -2
  42. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/panda/test_writer.py +2 -2
  43. ophyd_async-0.3a5/tests/test_flyer_with_panda.py → ophyd_async-0.3rc2/tests/plan_stubs/test_fly.py +157 -13
  44. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/sim/demo/test_sim_motor.py +2 -37
  45. ophyd_async-0.3a5/docs/explanations/flyscanning.rst +0 -63
  46. ophyd_async-0.3a5/docs/images/hardware-triggered-scan.png +0 -0
  47. ophyd_async-0.3a5/docs/images/outer-scan.png +0 -0
  48. ophyd_async-0.3a5/docs/images/simple-hardware-scan.png +0 -0
  49. ophyd_async-0.3a5/src/ophyd_async/planstubs/prepare_trigger_and_dets.py +0 -57
  50. ophyd_async-0.3a5/src/ophyd_async/sim/demo/sim_motor.py +0 -133
  51. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.copier-answers.yml +0 -0
  52. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.devcontainer/devcontainer.json +0 -0
  53. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.git-blame-ignore-revs +0 -0
  54. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.github/CONTRIBUTING.md +0 -0
  55. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.github/actions/install_requirements/action.yml +0 -0
  56. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.github/dependabot.yml +0 -0
  57. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.github/pages/index.html +0 -0
  58. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.github/pages/make_switcher.py +0 -0
  59. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.github/workflows/_check.yml +0 -0
  60. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.github/workflows/_dist.yml +0 -0
  61. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.github/workflows/_docs.yml +0 -0
  62. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.github/workflows/_pypi.yml +0 -0
  63. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.github/workflows/_test.yml +0 -0
  64. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.github/workflows/_tox.yml +0 -0
  65. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.github/workflows/ci.yml +0 -0
  66. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.github/workflows/periodic.yml +0 -0
  67. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.gitignore +0 -0
  68. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.mailmap +0 -0
  69. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/.pre-commit-config.yaml +0 -0
  70. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/Dockerfile +0 -0
  71. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/LICENSE +0 -0
  72. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/README.md +0 -0
  73. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/_templates/README +0 -0
  74. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/_templates/custom-class-template.rst +0 -0
  75. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/_templates/custom-module-template.rst +0 -0
  76. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/conf.py +0 -0
  77. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/examples/epics_demo.py +0 -0
  78. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/examples/foo_detector.py +0 -0
  79. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/explanations/decisions/0001-record-architecture-decisions.md +0 -0
  80. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/explanations/decisions/0002-switched-to-python-copier-template.md +0 -0
  81. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/explanations/decisions/0003-ophyd-async-migration.rst +0 -0
  82. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/explanations/decisions/0004-repository-structure.rst +0 -0
  83. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/explanations/decisions/0005-respect-black-line-length.rst +0 -0
  84. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/explanations/decisions/0006-procedural-device-definitions.rst +0 -0
  85. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/explanations/decisions/COPYME +0 -0
  86. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/explanations/decisions.md +0 -0
  87. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/explanations/event-loop-choice.rst +0 -0
  88. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/explanations.md +0 -0
  89. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/genindex.rst +0 -0
  90. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/how-to/choose-interfaces-for-devices.md +0 -0
  91. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/how-to/compound-devices.rst +0 -0
  92. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/how-to/contribute.md +0 -0
  93. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/how-to/make-a-simple-device.rst +0 -0
  94. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/how-to/make-a-standard-detector.rst +0 -0
  95. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/how-to.md +0 -0
  96. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/images/bluesky_ophyd_epics_devices_logo.svg +0 -0
  97. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/images/bluesky_ophyd_logo.svg +0 -0
  98. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/images/ophyd_favicon.svg +0 -0
  99. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/index.md +0 -0
  100. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/reference/api.rst +0 -0
  101. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/reference.md +0 -0
  102. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/tutorials/installation.md +0 -0
  103. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/tutorials/using-existing-devices.rst +0 -0
  104. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/docs/tutorials.md +0 -0
  105. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/pyproject.toml +0 -0
  106. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/setup.cfg +0 -0
  107. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/__init__.py +0 -0
  108. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/__main__.py +0 -0
  109. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/core/_providers.py +0 -0
  110. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/core/device_save_loader.py +0 -0
  111. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/core/soft_signal_backend.py +0 -0
  112. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/core/standard_readable.py +0 -0
  113. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/__init__.py +0 -0
  114. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/_backend/__init__.py +0 -0
  115. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/_backend/_aioca.py +0 -0
  116. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/_backend/_p4p.py +0 -0
  117. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/_backend/common.py +0 -0
  118. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/__init__.py +0 -0
  119. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/aravis.py +0 -0
  120. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/controllers/__init__.py +0 -0
  121. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/controllers/ad_sim_controller.py +0 -0
  122. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/controllers/aravis_controller.py +0 -0
  123. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py +0 -0
  124. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/controllers/pilatus_controller.py +0 -0
  125. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py +0 -0
  126. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/drivers/__init__.py +0 -0
  127. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/drivers/aravis_driver.py +0 -0
  128. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py +0 -0
  129. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/drivers/pilatus_driver.py +0 -0
  130. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py +0 -0
  131. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/kinetix.py +0 -0
  132. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/pilatus.py +0 -0
  133. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/single_trigger_det.py +0 -0
  134. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/utils.py +0 -0
  135. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/vimba.py +0 -0
  136. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/writers/__init__.py +0 -0
  137. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/writers/_hdfdataset.py +0 -0
  138. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/areadetector/writers/_hdffile.py +0 -0
  139. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/demo/demo_ad_sim_detector.py +0 -0
  140. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/demo/mover.db +0 -0
  141. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/demo/sensor.db +0 -0
  142. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/motion/__init__.py +0 -0
  143. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/pvi/__init__.py +0 -0
  144. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/pvi/pvi.py +0 -0
  145. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/signal/__init__.py +0 -0
  146. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/signal/_epics_transport.py +0 -0
  147. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/epics/signal/signal.py +0 -0
  148. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/log.py +0 -0
  149. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/panda/_common_blocks.py +0 -0
  150. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/panda/_hdf_panda.py +0 -0
  151. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/panda/_panda_controller.py +0 -0
  152. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/panda/_table.py +0 -0
  153. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/panda/_trigger.py +0 -0
  154. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/panda/_utils.py +0 -0
  155. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/panda/writers/__init__.py +0 -0
  156. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/panda/writers/_panda_hdf_file.py +0 -0
  157. {ophyd_async-0.3a5/src/ophyd_async/planstubs → ophyd_async-0.3rc2/src/ophyd_async/plan_stubs}/ensure_connected.py +0 -0
  158. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/protocols.py +0 -0
  159. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/sim/__init__.py +0 -0
  160. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/sim/demo/__init__.py +0 -0
  161. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/sim/pattern_generator.py +0 -0
  162. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/sim/sim_pattern_detector_control.py +0 -0
  163. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/sim/sim_pattern_detector_writer.py +0 -0
  164. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async/sim/sim_pattern_generator.py +0 -0
  165. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async.egg-info/dependency_links.txt +0 -0
  166. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async.egg-info/entry_points.txt +0 -0
  167. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async.egg-info/requires.txt +0 -0
  168. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/src/ophyd_async.egg-info/top_level.txt +0 -0
  169. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/conftest.py +0 -0
  170. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/core/test_device_save_loader.py +0 -0
  171. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/core/test_signal.py +0 -0
  172. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/core/test_soft_signal_backend.py +0 -0
  173. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/core/test_standard_readable.py +0 -0
  174. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/core/test_utils.py +0 -0
  175. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/_backend/test_common.py +0 -0
  176. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/areadetector/__init__.py +0 -0
  177. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/areadetector/test_aravis.py +0 -0
  178. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/areadetector/test_controllers.py +0 -0
  179. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/areadetector/test_drivers.py +0 -0
  180. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/areadetector/test_kinetix.py +0 -0
  181. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/areadetector/test_pilatus.py +0 -0
  182. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/areadetector/test_scans.py +0 -0
  183. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/areadetector/test_single_trigger_det.py +0 -0
  184. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/areadetector/test_utils.py +0 -0
  185. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/areadetector/test_vimba.py +0 -0
  186. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/areadetector/test_writers.py +0 -0
  187. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/motion/__init__.py +0 -0
  188. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/test_pvi.py +0 -0
  189. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/test_records.db +0 -0
  190. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/epics/test_signals.py +0 -0
  191. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/panda/db/panda.db +0 -0
  192. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/panda/test_panda_connect.py +0 -0
  193. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/panda/test_panda_controller.py +0 -0
  194. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/panda/test_panda_utils.py +0 -0
  195. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/panda/test_table.py +0 -0
  196. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/protocols/test_protocols.py +0 -0
  197. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/sim/__init__.py +0 -0
  198. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/sim/conftest.py +0 -0
  199. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/sim/demo/__init__.py +0 -0
  200. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/sim/test_pattern_generator.py +0 -0
  201. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/sim/test_sim_detector.py +0 -0
  202. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/sim/test_sim_writer.py +0 -0
  203. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/sim/test_streaming_plan.py +0 -0
  204. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/test_cli.py +0 -0
  205. {ophyd_async-0.3a5 → ophyd_async-0.3rc2}/tests/test_log.py +0 -0
@@ -8,7 +8,7 @@ coverage:
8
8
  threshold: 1%
9
9
  patch:
10
10
  default:
11
- target: auto
11
+ target: 90
12
12
  threshold: 1%
13
13
  github_checks:
14
14
  annotations: false
@@ -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@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 # v2.0.4
26
+ uses: softprops/action-gh-release@69320dbe05506a9a39fc8ae11030b214ec2d1f87 # v2.0.5
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.3a5
3
+ Version: 0.3rc2
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
@@ -32,7 +32,7 @@ Parity with Malcolm
32
32
 
33
33
  Ophyd-async should provide the same building blocks for defining flyscans scans as malcolm_. It should support PandA and Zebra as timing masters by default, but also provide easy helpers for developers to write support for their own devices.
34
34
 
35
- It should enable `motor trajectory scanning <motortraj_>` and `multiple triggering rates<detectorsync_>` based around a base rate, and pausing/resuming scans. Scans should be modelled using scanspec_, which serves as a universal language for defining trajectory and time-resolved scans, and converted to the underlying format of the given motion controller. It should also be possible to define an `outer scan <outerscan_>`.
35
+ It should enable motor trajectory scanning and multiple triggering rates based around a base rate, and pausing/resuming scans. Scans should be modelled using scanspec_, which serves as a universal language for defining trajectory and time-resolved scans, and converted to the underlying format of the given motion controller. It should also be possible to define an outer scan .
36
36
 
37
37
 
38
38
  Improved Trajectory Calculation
@@ -0,0 +1,29 @@
1
+ Flyscanning
2
+ ===========
3
+
4
+ See the documents in the [bluesky cookbook](http://blueskyproject.io/bluesky-cookbook/glossary/flyscanning.html)
5
+
6
+ Hardware
7
+ --------
8
+
9
+ Ophyd-async ships with support for Quantum Detectors' PandA_ and Zebra_ as triggering mechanisms.
10
+
11
+ These are very modular and can be used to trigger a variety of detectors and handle readback signals from a variety of sample control devices. See full specs for more information.
12
+
13
+ It is possible to write support for additional systems/devices.
14
+
15
+
16
+ Role of Ophyd-Async
17
+ -------------------
18
+
19
+ Bluesky supports devices that configure acquisition and then hand over control to an external system via the ``Flyer`` protocol.
20
+
21
+ Ophyd-async's job is to provide devices that implement ``Flyer`` and can:
22
+
23
+ - Configure all necessary hardware for a scan
24
+ - Kickoff a scan and monitor progress until complete
25
+ - Produce documents representing the progress of the scan
26
+ - Allow handing control back and forth to enable outer scanning
27
+
28
+ .. _PandA: https://quantumdetectors.com/products/pandabox/
29
+ .. _Zebra: https://quantumdetectors.com/products/zebra/
@@ -41,6 +41,11 @@ In addition this example also utilizes helper functions like ``assert_reading``
41
41
  :pyobject: test_sensor_reading_shows_value
42
42
 
43
43
 
44
+ Given that the mock signal holds a ``unittest.mock.Mock`` object you can retrieve this object and assert that the device has been set correctly using ``get_mock_put``. You are also free to use any other behaviour that ``unittest.mock.Mock`` provides, such as in this example which sets the parent of the mock to allow ordering across signals to be asserted:
45
+
46
+ .. literalinclude:: ../../tests/epics/demo/test_demo.py
47
+ :pyobject: test_retrieve_mock_and_assert
48
+
44
49
  There are several other test utility functions:
45
50
 
46
51
  Use ``callback_on_mock_put``, for hooking in logic when a mock value changes (e.g. because someone puts to it). This can be called directly, or used as a context, with the callbacks ending after exit.
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.3a5'
15
+ __version__ = version = '0.3rc2'
16
16
  __version_tuple__ = version_tuple = (0, 3)
@@ -24,12 +24,10 @@ from .device_save_loader import (
24
24
  walk_rw_signals,
25
25
  )
26
26
  from .flyer import HardwareTriggeredFlyable, TriggerLogic
27
- from .mock_signal_backend import (
28
- MockSignalBackend,
29
- )
27
+ from .mock_signal_backend import MockSignalBackend
30
28
  from .mock_signal_utils import (
31
- assert_mock_put_called_with,
32
29
  callback_on_mock_put,
30
+ get_mock_put,
33
31
  mock_puts_blocked,
34
32
  reset_mock_put_calls,
35
33
  set_mock_put_proceeds,
@@ -57,6 +55,8 @@ from .soft_signal_backend import SoftSignalBackend
57
55
  from .standard_readable import ConfigSignal, HintedSignal, StandardReadable
58
56
  from .utils import (
59
57
  DEFAULT_TIMEOUT,
58
+ CalculatableTimeout,
59
+ CalculateTimeout,
60
60
  Callback,
61
61
  NotConnected,
62
62
  ReadingValueCallback,
@@ -68,7 +68,7 @@ from .utils import (
68
68
  )
69
69
 
70
70
  __all__ = [
71
- "assert_mock_put_called_with",
71
+ "get_mock_put",
72
72
  "callback_on_mock_put",
73
73
  "mock_puts_blocked",
74
74
  "set_mock_values",
@@ -108,6 +108,8 @@ __all__ = [
108
108
  "TriggerInfo",
109
109
  "TriggerLogic",
110
110
  "HardwareTriggeredFlyable",
111
+ "CalculateTimeout",
112
+ "CalculatableTimeout",
111
113
  "DEFAULT_TIMEOUT",
112
114
  "Callback",
113
115
  "NotConnected",
@@ -9,7 +9,6 @@ from typing import (
9
9
  Awaitable,
10
10
  Callable,
11
11
  Generic,
12
- SupportsFloat,
13
12
  Type,
14
13
  TypeVar,
15
14
  cast,
@@ -27,15 +26,11 @@ WAS = TypeVar("WAS", bound="WatchableAsyncStatus")
27
26
  class AsyncStatusBase(Status):
28
27
  """Convert asyncio awaitable to bluesky Status interface"""
29
28
 
30
- def __init__(self, awaitable: Awaitable, timeout: SupportsFloat | None = None):
31
- if isinstance(timeout, SupportsFloat):
32
- timeout = float(timeout)
29
+ def __init__(self, awaitable: Awaitable):
33
30
  if isinstance(awaitable, asyncio.Task):
34
31
  self.task = awaitable
35
32
  else:
36
- self.task = asyncio.create_task(
37
- asyncio.wait_for(awaitable, timeout=timeout)
38
- )
33
+ self.task = asyncio.create_task(awaitable)
39
34
  self.task.add_done_callback(self._run_callbacks)
40
35
  self._callbacks: list[Callback[Status]] = []
41
36
 
@@ -49,9 +44,8 @@ class AsyncStatusBase(Status):
49
44
  self._callbacks.append(callback)
50
45
 
51
46
  def _run_callbacks(self, task: asyncio.Task):
52
- if not task.cancelled():
53
- for callback in self._callbacks:
54
- callback(self)
47
+ for callback in self._callbacks:
48
+ callback(self)
55
49
 
56
50
  def exception(self, timeout: float | None = 0.0) -> BaseException | None:
57
51
  if timeout != 0.0:
@@ -93,14 +87,11 @@ class AsyncStatusBase(Status):
93
87
  class AsyncStatus(AsyncStatusBase):
94
88
  @classmethod
95
89
  def wrap(cls: Type[AS], f: Callable[P, Awaitable]) -> Callable[P, AS]:
90
+ """Wrap an async function in an AsyncStatus."""
91
+
96
92
  @functools.wraps(f)
97
93
  def wrap_f(*args: P.args, **kwargs: P.kwargs) -> AS:
98
- # We can't type this more properly because Concatenate/ParamSpec doesn't
99
- # yet support keywords
100
- # https://peps.python.org/pep-0612/#concatenating-keyword-parameters
101
- timeout = kwargs.get("timeout")
102
- assert isinstance(timeout, SupportsFloat) or timeout is None
103
- return cls(f(*args, **kwargs), timeout=timeout)
94
+ return cls(f(*args, **kwargs))
104
95
 
105
96
  # type is actually functools._Wrapped[P, Awaitable, P, AS]
106
97
  # but functools._Wrapped is not necessarily available
@@ -110,15 +101,11 @@ class AsyncStatus(AsyncStatusBase):
110
101
  class WatchableAsyncStatus(AsyncStatusBase, Generic[T]):
111
102
  """Convert AsyncIterator of WatcherUpdates to bluesky Status interface."""
112
103
 
113
- def __init__(
114
- self,
115
- iterator: AsyncIterator[WatcherUpdate[T]],
116
- timeout: SupportsFloat | None = None,
117
- ):
104
+ def __init__(self, iterator: AsyncIterator[WatcherUpdate[T]]):
118
105
  self._watchers: list[Watcher] = []
119
106
  self._start = time.monotonic()
120
107
  self._last_update: WatcherUpdate[T] | None = None
121
- super().__init__(self._notify_watchers_from(iterator), timeout)
108
+ super().__init__(self._notify_watchers_from(iterator))
122
109
 
123
110
  async def _notify_watchers_from(self, iterator: AsyncIterator[WatcherUpdate[T]]):
124
111
  async for update in iterator:
@@ -146,14 +133,10 @@ class WatchableAsyncStatus(AsyncStatusBase, Generic[T]):
146
133
  cls: Type[WAS],
147
134
  f: Callable[P, AsyncIterator[WatcherUpdate[T]]],
148
135
  ) -> Callable[P, WAS]:
149
- """Wrap an AsyncIterator in a WatchableAsyncStatus. If it takes
150
- 'timeout' as an argument, this must be a float or None, and it
151
- will be propagated to the status."""
136
+ """Wrap an AsyncIterator in a WatchableAsyncStatus."""
152
137
 
153
138
  @functools.wraps(f)
154
139
  def wrap_f(*args: P.args, **kwargs: P.kwargs) -> WAS:
155
- timeout = kwargs.get("timeout")
156
- assert isinstance(timeout, SupportsFloat) or timeout is None
157
- return cls(f(*args, **kwargs), timeout=timeout)
140
+ return cls(f(*args, **kwargs))
158
141
 
159
142
  return cast(Callable[P, WAS], wrap_f)
@@ -332,7 +332,7 @@ class StandardDetector(
332
332
  # Collect stream datum documents for all indices written.
333
333
  # The index is optional, and provided for fly scans, however this needs to be
334
334
  # retrieved for step scans.
335
- if not index:
335
+ if index is None:
336
336
  index = await self.writer.get_indices_written()
337
337
  async for doc in self.writer.collect_stream_docs(index):
338
338
  yield doc
@@ -1,7 +1,5 @@
1
1
  """Base device"""
2
2
 
3
- from __future__ import annotations
4
-
5
3
  import asyncio
6
4
  import sys
7
5
  from functools import cached_property
@@ -32,7 +30,7 @@ class Device(HasName):
32
30
 
33
31
  _name: str = ""
34
32
  #: The parent Device if it exists
35
- parent: Optional[Device] = None
33
+ parent: Optional["Device"] = None
36
34
  # None if connect hasn't started, a Task if it has
37
35
  _connect_task: Optional[asyncio.Task] = None
38
36
  _connect_mock_arg: bool = False
@@ -51,7 +49,7 @@ class Device(HasName):
51
49
  getLogger("ophyd_async.devices"), {"ophyd_async_device_name": self.name}
52
50
  )
53
51
 
54
- def children(self) -> Iterator[Tuple[str, Device]]:
52
+ def children(self) -> Iterator[Tuple[str, "Device"]]:
55
53
  for attr_name, attr in self.__dict__.items():
56
54
  if attr_name != "parent" and isinstance(attr, Device):
57
55
  yield attr_name, attr
@@ -39,7 +39,7 @@ class HardwareTriggeredFlyable(
39
39
  def __init__(
40
40
  self,
41
41
  trigger_logic: TriggerLogic[T],
42
- configuration_signals: Sequence[SignalR],
42
+ configuration_signals: Sequence[SignalR] = (),
43
43
  name: str = "",
44
44
  ):
45
45
  self._trigger_logic = trigger_logic
@@ -1,6 +1,6 @@
1
1
  import asyncio
2
2
  from functools import cached_property
3
- from typing import Optional, Type
3
+ from typing import Callable, Optional, Type
4
4
  from unittest.mock import Mock
5
5
 
6
6
  from bluesky.protocols import Descriptor, Reading
@@ -10,7 +10,7 @@ from ophyd_async.core.soft_signal_backend import SoftSignalBackend
10
10
  from ophyd_async.core.utils import DEFAULT_TIMEOUT, ReadingValueCallback, T
11
11
 
12
12
 
13
- class MockSignalBackend(SignalBackend):
13
+ class MockSignalBackend(SignalBackend[T]):
14
14
  def __init__(
15
15
  self,
16
16
  datatype: Optional[Type[T]] = None,
@@ -31,11 +31,11 @@ class MockSignalBackend(SignalBackend):
31
31
 
32
32
  if not isinstance(self.initial_backend, SoftSignalBackend):
33
33
  # If the backend is a hard signal backend, or not provided,
34
- # then we create a soft signal to mimick it
34
+ # then we create a soft signal to mimic it
35
35
 
36
36
  self.soft_backend = SoftSignalBackend(datatype=datatype)
37
37
  else:
38
- self.soft_backend = initial_backend
38
+ self.soft_backend = self.initial_backend
39
39
 
40
40
  def source(self, name: str) -> str:
41
41
  if self.initial_backend:
@@ -47,7 +47,7 @@ class MockSignalBackend(SignalBackend):
47
47
 
48
48
  @cached_property
49
49
  def put_mock(self) -> Mock:
50
- return Mock(name="put")
50
+ return Mock(name="put", spec=Callable)
51
51
 
52
52
  @cached_property
53
53
  def put_proceeds(self) -> asyncio.Event:
@@ -65,9 +65,6 @@ class MockSignalBackend(SignalBackend):
65
65
  def set_value(self, value: T):
66
66
  self.soft_backend.set_value(value)
67
67
 
68
- async def get_descriptor(self, source: str) -> Descriptor:
69
- return await self.soft_backend.get_descriptor(source)
70
-
71
68
  async def get_reading(self) -> Reading:
72
69
  return await self.soft_backend.get_reading()
73
70
 
@@ -1,6 +1,6 @@
1
1
  from contextlib import asynccontextmanager, contextmanager
2
- from typing import Any, Callable, Generator, Iterable, Iterator, List
3
- from unittest.mock import ANY, Mock
2
+ from typing import Any, Callable, Iterable
3
+ from unittest.mock import Mock
4
4
 
5
5
  from ophyd_async.core.signal import Signal
6
6
  from ophyd_async.core.utils import T
@@ -22,7 +22,7 @@ def set_mock_value(signal: Signal[T], value: T):
22
22
  backend.set_value(value)
23
23
 
24
24
 
25
- def set_mock_put_proceeds(signal: Signal[T], proceeds: bool):
25
+ def set_mock_put_proceeds(signal: Signal, proceeds: bool):
26
26
  """Allow or block a put with wait=True from proceeding"""
27
27
  backend = _get_mock_signal_backend(signal)
28
28
 
@@ -33,7 +33,7 @@ def set_mock_put_proceeds(signal: Signal[T], proceeds: bool):
33
33
 
34
34
 
35
35
  @asynccontextmanager
36
- async def mock_puts_blocked(*signals: List[Signal]):
36
+ async def mock_puts_blocked(*signals: Signal):
37
37
  for signal in signals:
38
38
  set_mock_put_proceeds(signal, False)
39
39
  yield
@@ -41,9 +41,9 @@ async def mock_puts_blocked(*signals: List[Signal]):
41
41
  set_mock_put_proceeds(signal, True)
42
42
 
43
43
 
44
- def assert_mock_put_called_with(signal: Signal, value: Any, wait=ANY, timeout=ANY):
45
- backend = _get_mock_signal_backend(signal)
46
- backend.put_mock.assert_called_with(value, wait=wait, timeout=timeout)
44
+ def get_mock_put(signal: Signal) -> Mock:
45
+ """Get the mock associated with the put call on the signal."""
46
+ return _get_mock_signal_backend(signal).put_mock
47
47
 
48
48
 
49
49
  def reset_mock_put_calls(signal: Signal):
@@ -79,7 +79,7 @@ class _SetValuesIterator:
79
79
  return next_value
80
80
 
81
81
  def __del__(self):
82
- if self.require_all_consumed and self.index != len(self.values):
82
+ if self.require_all_consumed and self.index != len(list(self.values)):
83
83
  raise AssertionError("Not all values have been consumed.")
84
84
 
85
85
 
@@ -87,7 +87,7 @@ def set_mock_values(
87
87
  signal: Signal,
88
88
  values: Iterable[Any],
89
89
  require_all_consumed: bool = False,
90
- ) -> Iterator[Any]:
90
+ ) -> _SetValuesIterator:
91
91
  """Iterator to set a signal to a sequence of values, optionally repeating the
92
92
  sequence.
93
93
 
@@ -127,11 +127,7 @@ def _unset_side_effect_cm(put_mock: Mock):
127
127
  put_mock.side_effect = None
128
128
 
129
129
 
130
- # linting isn't smart enought to realize @contextmanager will give use a
131
- # ContextManager[None]
132
- def callback_on_mock_put(
133
- signal: Signal, callback: Callable[[T], None]
134
- ) -> Generator[None, None, None]:
130
+ def callback_on_mock_put(signal: Signal[T], callback: Callable[[T], None]):
135
131
  """For setting a callback when a backend is put to.
136
132
 
137
133
  Can either be used in a context, with the callback being
@@ -32,7 +32,7 @@ from .async_status import AsyncStatus
32
32
  from .device import Device
33
33
  from .signal_backend import SignalBackend
34
34
  from .soft_signal_backend import SoftSignalBackend
35
- from .utils import DEFAULT_TIMEOUT, Callback, T
35
+ from .utils import DEFAULT_TIMEOUT, CalculatableTimeout, CalculateTimeout, Callback, T
36
36
 
37
37
 
38
38
  def _add_timeout(func):
@@ -213,15 +213,14 @@ class SignalR(Signal[T], AsyncReadable, AsyncStageable, Subscribable):
213
213
  self._del_cache(self._get_cache().set_staged(False))
214
214
 
215
215
 
216
- USE_DEFAULT_TIMEOUT = "USE_DEFAULT_TIMEOUT"
217
-
218
-
219
216
  class SignalW(Signal[T], Movable):
220
217
  """Signal that can be set"""
221
218
 
222
- def set(self, value: T, wait=True, timeout=USE_DEFAULT_TIMEOUT) -> AsyncStatus:
219
+ def set(
220
+ self, value: T, wait=True, timeout: CalculatableTimeout = CalculateTimeout
221
+ ) -> AsyncStatus:
223
222
  """Set the value and return a status saying when it's done"""
224
- if timeout is USE_DEFAULT_TIMEOUT:
223
+ if timeout is CalculateTimeout:
225
224
  timeout = self._timeout
226
225
 
227
226
  async def do_set():
@@ -248,9 +247,11 @@ class SignalRW(SignalR[T], SignalW[T], Locatable):
248
247
  class SignalX(Signal):
249
248
  """Signal that puts the default value"""
250
249
 
251
- def trigger(self, wait=True, timeout=USE_DEFAULT_TIMEOUT) -> AsyncStatus:
250
+ def trigger(
251
+ self, wait=True, timeout: CalculatableTimeout = CalculateTimeout
252
+ ) -> AsyncStatus:
252
253
  """Trigger the action and return a status saying when it's done"""
253
- if timeout is USE_DEFAULT_TIMEOUT:
254
+ if timeout is CalculateTimeout:
254
255
  timeout = self._timeout
255
256
  coro = self._backend.put(None, wait=wait, timeout=timeout)
256
257
  return AsyncStatus(coro)
@@ -270,7 +271,7 @@ def soft_signal_r_and_setter(
270
271
  datatype: Optional[Type[T]] = None,
271
272
  initial_value: Optional[T] = None,
272
273
  name: str = "",
273
- ) -> Tuple[SignalR[T], Callable[[T]]]:
274
+ ) -> Tuple[SignalR[T], Callable[[T], None]]:
274
275
  """Returns a tuple of a read-only Signal and a callable through
275
276
  which the signal can be internally modified within the device. Use
276
277
  soft_signal_rw if you want a device that is externally modifiable
@@ -394,7 +395,7 @@ def assert_emitted(docs: Mapping[str, list[dict]], **numbers: int):
394
395
 
395
396
 
396
397
  async def observe_value(
397
- signal: SignalR[T], timeout=None, done_status: Status | None = None
398
+ signal: SignalR[T], timeout: float | None = None, done_status: Status | None = None
398
399
  ) -> AsyncGenerator[T, None]:
399
400
  """Subscribe to the value of a signal so it can be iterated from.
400
401
 
@@ -403,8 +404,12 @@ async def observe_value(
403
404
  signal:
404
405
  Call subscribe_value on this at the start, and clear_sub on it at the
405
406
  end
407
+ timeout:
408
+ If given, how long to wait for each updated value in seconds. If an update
409
+ is not produced in this time then raise asyncio.TimeoutError
406
410
  done_status:
407
411
  If this status is complete, stop observing and make the iterator return.
412
+ If it raises an exception then this exception will be raised by the iterator.
408
413
 
409
414
  Notes
410
415
  -----
@@ -414,9 +419,7 @@ async def observe_value(
414
419
  do_something_with(value)
415
420
  """
416
421
 
417
- class StatusIsDone: ...
418
-
419
- q: asyncio.Queue[T | StatusIsDone] = asyncio.Queue()
422
+ q: asyncio.Queue[T | Status] = asyncio.Queue()
420
423
  if timeout is None:
421
424
  get_value = q.get
422
425
  else:
@@ -425,16 +428,19 @@ async def observe_value(
425
428
  return await asyncio.wait_for(q.get(), timeout)
426
429
 
427
430
  if done_status is not None:
428
- done_status.add_callback(lambda _: q.put_nowait(StatusIsDone()))
431
+ done_status.add_callback(q.put_nowait)
429
432
 
430
433
  signal.subscribe_value(q.put_nowait)
431
434
  try:
432
435
  while True:
433
436
  item = await get_value()
434
- if not isinstance(item, StatusIsDone):
435
- yield item
437
+ if done_status and item is done_status:
438
+ if exc := done_status.exception():
439
+ raise exc
440
+ else:
441
+ break
436
442
  else:
437
- break
443
+ yield item
438
444
  finally:
439
445
  signal.clear_sub(q.put_nowait)
440
446
 
@@ -14,7 +14,7 @@ class SignalBackend(Generic[T]):
14
14
 
15
15
  #: Like ca://PV_PREFIX:SIGNAL
16
16
  @abstractmethod
17
- def source(name: str) -> str:
17
+ def source(self, name: str) -> str:
18
18
  """Return source of signal. Signals may pass a name to the backend, which can be
19
19
  used or discarded."""
20
20
 
@@ -31,6 +31,17 @@ DEFAULT_TIMEOUT = 10.0
31
31
  ErrorText = Union[str, Dict[str, Exception]]
32
32
 
33
33
 
34
+ class CalculateTimeout:
35
+ """Sentinel class used to implement ``myfunc(timeout=CalculateTimeout)``
36
+
37
+ This signifies that the function should calculate a suitable non-zero
38
+ timeout itself
39
+ """
40
+
41
+
42
+ CalculatableTimeout = float | None | Type[CalculateTimeout]
43
+
44
+
34
45
  class NotConnected(Exception):
35
46
  """Exception to be raised if a `Device.connect` is cancelled"""
36
47
 
@@ -9,7 +9,7 @@ from ophyd_async.core import (
9
9
  set_and_wait_for_value,
10
10
  )
11
11
 
12
- from ...signal.signal import epics_signal_r, epics_signal_rw, epics_signal_rw_rbv
12
+ from ...signal.signal import epics_signal_r, epics_signal_rw_rbv
13
13
  from ..utils import ImageMode
14
14
  from ..writers.nd_plugin import NDArrayBase
15
15
 
@@ -43,18 +43,12 @@ DEFAULT_GOOD_STATES: FrozenSet[DetectorState] = frozenset(
43
43
  class ADBase(NDArrayBase):
44
44
  def __init__(self, prefix: str, name: str = "") -> None:
45
45
  # Define some signals
46
- self.acquire = epics_signal_rw_rbv(bool, prefix + "Acquire")
47
46
  self.acquire_time = epics_signal_rw_rbv(float, prefix + "AcquireTime")
48
47
  self.num_images = epics_signal_rw_rbv(int, prefix + "NumImages")
49
48
  self.image_mode = epics_signal_rw_rbv(ImageMode, prefix + "ImageMode")
50
- self.array_counter = epics_signal_rw_rbv(int, prefix + "ArrayCounter")
51
- self.array_size_x = epics_signal_r(int, prefix + "ArraySizeX_RBV")
52
- self.array_size_y = epics_signal_r(int, prefix + "ArraySizeY_RBV")
53
49
  self.detector_state = epics_signal_r(
54
50
  DetectorState, prefix + "DetectorState_RBV"
55
51
  )
56
- # There is no _RBV for this one
57
- self.wait_for_plugins = epics_signal_rw(bool, prefix + "WaitForPlugins")
58
52
  super().__init__(prefix, name=name)
59
53
 
60
54
 
@@ -43,12 +43,13 @@ class HDFWriter(DetectorWriter):
43
43
  async def open(self, multiplier: int = 1) -> Dict[str, DataKey]:
44
44
  self._file = None
45
45
  info = self._directory_provider()
46
+ file_path = str(info.root / info.resource_dir)
46
47
  await asyncio.gather(
47
48
  self.hdf.num_extra_dims.set(0),
48
49
  self.hdf.lazy_open.set(True),
49
50
  self.hdf.swmr_mode.set(True),
50
51
  # See https://github.com/bluesky/ophyd-async/issues/122
51
- self.hdf.file_path.set(str(info.root / info.resource_dir)),
52
+ self.hdf.file_path.set(file_path),
52
53
  self.hdf.file_name.set(f"{info.prefix}{self.hdf.name}{info.suffix}"),
53
54
  self.hdf.file_template.set("%s/%s.h5"),
54
55
  self.hdf.file_write_mode.set(FileWriteMode.stream),
@@ -59,7 +60,7 @@ class HDFWriter(DetectorWriter):
59
60
 
60
61
  assert (
61
62
  await self.hdf.file_path_exists.get_value()
62
- ), f"File path {self.hdf.file_path.get_value()} for hdf plugin does not exist"
63
+ ), f"File path {file_path} for hdf plugin does not exist"
63
64
 
64
65
  # Overwrite num_capture to go forever
65
66
  await self.hdf.num_capture.set(0)
@@ -36,7 +36,5 @@ class NDFileHDF(NDPluginBase):
36
36
  self.lazy_open = epics_signal_rw_rbv(bool, prefix + "LazyOpen")
37
37
  self.capture = epics_signal_rw_rbv(bool, prefix + "Capture")
38
38
  self.flush_now = epics_signal_rw(bool, prefix + "FlushNow")
39
- self.array_size0 = epics_signal_r(int, prefix + "ArraySize0_RBV")
40
- self.array_size1 = epics_signal_r(int, prefix + "ArraySize1_RBV")
41
39
  self.xml_file_name = epics_signal_rw_rbv(str, prefix + "XMLFileName")
42
40
  super().__init__(prefix, name)
@@ -14,6 +14,13 @@ class NDArrayBase(Device):
14
14
  def __init__(self, prefix: str, name: str = "") -> None:
15
15
  self.unique_id = epics_signal_r(int, prefix + "UniqueId_RBV")
16
16
  self.nd_attributes_file = epics_signal_rw(str, prefix + "NDAttributesFile")
17
+ self.acquire = epics_signal_rw_rbv(bool, prefix + "Acquire")
18
+ self.array_size_x = epics_signal_r(int, prefix + "ArraySizeX_RBV")
19
+ self.array_size_y = epics_signal_r(int, prefix + "ArraySizeY_RBV")
20
+ self.array_counter = epics_signal_rw_rbv(int, prefix + "ArrayCounter")
21
+ # There is no _RBV for this one
22
+ self.wait_for_plugins = epics_signal_rw(bool, prefix + "WaitForPlugins")
23
+
17
24
  super().__init__(name=name)
18
25
 
19
26
 
@@ -22,6 +29,8 @@ class NDPluginBase(NDArrayBase):
22
29
  self.nd_array_port = epics_signal_rw_rbv(str, prefix + "NDArrayPort")
23
30
  self.enable_callback = epics_signal_rw_rbv(Callback, prefix + "EnableCallbacks")
24
31
  self.nd_array_address = epics_signal_rw_rbv(int, prefix + "NDArrayAddress")
32
+ self.array_size0 = epics_signal_r(int, prefix + "ArraySize0_RBV")
33
+ self.array_size1 = epics_signal_r(int, prefix + "ArraySize1_RBV")
25
34
  super().__init__(prefix, name)
26
35
 
27
36