ophyd-async 0.8.0a2__tar.gz → 0.8.0a4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.copier-answers.yml +1 -1
  2. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/CONTRIBUTING.md +1 -1
  3. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/workflows/_pypi.yml +1 -1
  4. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/workflows/_release.yml +1 -1
  5. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/workflows/ci.yml +1 -1
  6. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/PKG-INFO +1 -1
  7. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/examples/foo_detector.py +1 -1
  8. ophyd_async-0.8.0a4/docs/explanations/decisions/0009-procedural-vs-declarative-devices.md +140 -0
  9. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/pyproject.toml +2 -1
  10. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/_version.py +1 -1
  11. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/__init__.py +9 -1
  12. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_device.py +71 -49
  13. ophyd_async-0.8.0a4/src/ophyd_async/core/_device_filler.py +269 -0
  14. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_mock_signal_backend.py +10 -7
  15. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_mock_signal_utils.py +14 -11
  16. ophyd_async-0.8.0a4/src/ophyd_async/core/_readable.py +260 -0
  17. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_signal.py +22 -24
  18. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_soft_signal_backend.py +2 -0
  19. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_utils.py +64 -11
  20. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adaravis/_aravis_io.py +1 -1
  21. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adcore/_core_io.py +1 -1
  22. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adcore/_single_trigger.py +6 -10
  23. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adkinetix/_kinetix_io.py +1 -1
  24. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adpilatus/_pilatus_io.py +1 -1
  25. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/advimba/_vimba_io.py +1 -1
  26. ophyd_async-0.8.0a4/src/ophyd_async/epics/core/__init__.py +26 -0
  27. {ophyd_async-0.8.0a2/src/ophyd_async/epics/signal → ophyd_async-0.8.0a4/src/ophyd_async/epics/core}/_aioca.py +3 -6
  28. ophyd_async-0.8.0a4/src/ophyd_async/epics/core/_epics_connector.py +53 -0
  29. ophyd_async-0.8.0a4/src/ophyd_async/epics/core/_epics_device.py +13 -0
  30. {ophyd_async-0.8.0a2/src/ophyd_async/epics/signal → ophyd_async-0.8.0a4/src/ophyd_async/epics/core}/_p4p.py +3 -6
  31. ophyd_async-0.8.0a4/src/ophyd_async/epics/core/_pvi_connector.py +91 -0
  32. {ophyd_async-0.8.0a2/src/ophyd_async/epics/signal → ophyd_async-0.8.0a4/src/ophyd_async/epics/core}/_signal.py +31 -16
  33. ophyd_async-0.8.0a2/src/ophyd_async/epics/signal/_common.py → ophyd_async-0.8.0a4/src/ophyd_async/epics/core/_util.py +19 -1
  34. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/demo/_mover.py +4 -5
  35. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/demo/_sensor.py +9 -12
  36. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/eiger/_eiger_io.py +1 -1
  37. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/eiger/_odin_io.py +1 -1
  38. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/motor.py +4 -5
  39. ophyd_async-0.8.0a4/src/ophyd_async/epics/signal.py +11 -0
  40. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/core.py +2 -2
  41. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/plan_stubs/_ensure_connected.py +2 -4
  42. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/demo/_sim_motor.py +3 -4
  43. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/base_devices/_base_device.py +48 -48
  44. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/demo/_counter.py +6 -16
  45. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/demo/_mover.py +3 -4
  46. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async.egg-info/PKG-INFO +1 -1
  47. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async.egg-info/SOURCES.txt +10 -7
  48. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/system_tests/epics/eiger/test_eiger_system.py +1 -1
  49. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_device.py +22 -14
  50. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_device_save_loader.py +1 -1
  51. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_flyer.py +5 -1
  52. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_mock_signal_backend.py +3 -3
  53. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_readable.py +81 -58
  54. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_signal.py +23 -64
  55. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_soft_signal_backend.py +8 -1
  56. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_subset_enum.py +3 -3
  57. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_utils.py +1 -1
  58. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/adaravis/test_aravis.py +3 -1
  59. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/adcore/test_writers.py +1 -1
  60. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/adkinetix/test_kinetix.py +3 -1
  61. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/adsimdetector/test_sim.py +7 -3
  62. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/advimba/test_vimba.py +3 -1
  63. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/demo/test_demo.py +10 -5
  64. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/eiger/test_odin_io.py +3 -4
  65. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/pvi/test_pvi.py +33 -4
  66. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/signal/test_common.py +1 -1
  67. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/signal/test_signals.py +15 -4
  68. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/test_motor.py +18 -10
  69. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/fastcs/panda/db/panda.db +8 -8
  70. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/fastcs/panda/test_hdf_panda.py +1 -1
  71. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/fastcs/panda/test_panda_connect.py +6 -2
  72. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/fastcs/panda/test_panda_control.py +1 -1
  73. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/fastcs/panda/test_panda_utils.py +1 -1
  74. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/fastcs/panda/test_writer.py +3 -2
  75. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/plan_stubs/test_ensure_connected.py +3 -3
  76. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/plan_stubs/test_fly.py +1 -1
  77. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/sim/demo/test_sim_motor.py +2 -2
  78. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/sim/test_sim_detector.py +3 -0
  79. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/tango/test_base_device.py +8 -18
  80. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/tango/test_tango_transport.py +5 -3
  81. ophyd_async-0.8.0a2/src/ophyd_async/core/_device_filler.py +0 -191
  82. ophyd_async-0.8.0a2/src/ophyd_async/core/_readable.py +0 -261
  83. ophyd_async-0.8.0a2/src/ophyd_async/epics/pvi/__init__.py +0 -3
  84. ophyd_async-0.8.0a2/src/ophyd_async/epics/pvi/_pvi.py +0 -73
  85. ophyd_async-0.8.0a2/src/ophyd_async/epics/signal/__init__.py +0 -20
  86. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.codecov.yml +0 -0
  87. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.devcontainer/devcontainer.json +0 -0
  88. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.git-blame-ignore-revs +0 -0
  89. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  90. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/ISSUE_TEMPLATE/issue.md +0 -0
  91. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +0 -0
  92. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/actions/install_requirements/action.yml +0 -0
  93. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/dependabot.yml +0 -0
  94. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/pages/index.html +0 -0
  95. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/pages/make_switcher.py +0 -0
  96. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/workflows/_check.yml +0 -0
  97. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/workflows/_dist.yml +0 -0
  98. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/workflows/_docs.yml +0 -0
  99. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/workflows/_test.yml +0 -0
  100. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/workflows/_tox.yml +0 -0
  101. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.github/workflows/periodic.yml +0 -0
  102. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.gitignore +0 -0
  103. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.mailmap +0 -0
  104. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/.pre-commit-config.yaml +0 -0
  105. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/Dockerfile +0 -0
  106. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/LICENSE +0 -0
  107. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/README.md +0 -0
  108. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/_api.rst +0 -0
  109. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/_templates/custom-module-template.rst +0 -0
  110. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/conf.py +0 -0
  111. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/examples/epics_demo.py +0 -0
  112. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/examples/tango_demo.py +0 -0
  113. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0001-record-architecture-decisions.md +0 -0
  114. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0002-switched-to-python-copier-template.md +0 -0
  115. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0003-ophyd-async-migration.rst +0 -0
  116. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0004-repository-structure.rst +0 -0
  117. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0005-respect-black-line-length.rst +0 -0
  118. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0006-procedural-device-definitions.rst +0 -0
  119. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0007-subpackage-structure.md +0 -0
  120. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/explanations/decisions/0008-signal-types.md +0 -0
  121. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/explanations/decisions/COPYME +0 -0
  122. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/explanations/decisions.md +0 -0
  123. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/explanations/design-goals.rst +0 -0
  124. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/explanations/event-loop-choice.rst +0 -0
  125. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/explanations/flyscanning.rst +0 -0
  126. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/explanations.md +0 -0
  127. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/genindex.rst +0 -0
  128. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/how-to/choose-interfaces-for-devices.md +0 -0
  129. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/how-to/compound-devices.rst +0 -0
  130. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/how-to/contribute.md +0 -0
  131. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/how-to/make-a-simple-device.rst +0 -0
  132. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/how-to/make-a-standard-detector.rst +0 -0
  133. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/how-to/write-tests-for-devices.rst +0 -0
  134. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/how-to.md +0 -0
  135. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/images/ophyd-async-logo.svg +0 -0
  136. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/images/ophyd-favicon.svg +0 -0
  137. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/index.md +0 -0
  138. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/reference.md +0 -0
  139. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/tutorials/installation.md +0 -0
  140. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/tutorials/using-existing-devices.rst +0 -0
  141. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/docs/tutorials.md +0 -0
  142. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/setup.cfg +0 -0
  143. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/__init__.py +0 -0
  144. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/__main__.py +0 -0
  145. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_detector.py +0 -0
  146. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_device_save_loader.py +0 -0
  147. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_flyer.py +0 -0
  148. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_hdf_dataset.py +0 -0
  149. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_log.py +0 -0
  150. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_protocol.py +0 -0
  151. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_providers.py +0 -0
  152. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_signal_backend.py +0 -0
  153. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_status.py +0 -0
  154. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/core/_table.py +0 -0
  155. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/__init__.py +0 -0
  156. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adaravis/__init__.py +0 -0
  157. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adaravis/_aravis.py +0 -0
  158. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adaravis/_aravis_controller.py +0 -0
  159. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adcore/__init__.py +0 -0
  160. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adcore/_core_logic.py +0 -0
  161. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adcore/_hdf_writer.py +0 -0
  162. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adcore/_utils.py +0 -0
  163. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adkinetix/__init__.py +0 -0
  164. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adkinetix/_kinetix.py +0 -0
  165. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adkinetix/_kinetix_controller.py +0 -0
  166. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adpilatus/__init__.py +0 -0
  167. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adpilatus/_pilatus.py +0 -0
  168. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adpilatus/_pilatus_controller.py +0 -0
  169. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adsimdetector/__init__.py +0 -0
  170. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adsimdetector/_sim.py +0 -0
  171. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/adsimdetector/_sim_controller.py +0 -0
  172. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/advimba/__init__.py +0 -0
  173. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/advimba/_vimba.py +0 -0
  174. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/advimba/_vimba_controller.py +0 -0
  175. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/demo/__init__.py +0 -0
  176. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/demo/mover.db +0 -0
  177. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/demo/sensor.db +0 -0
  178. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/eiger/__init__.py +0 -0
  179. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/eiger/_eiger.py +0 -0
  180. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/epics/eiger/_eiger_controller.py +0 -0
  181. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/__init__.py +0 -0
  182. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/odin/__init__.py +0 -0
  183. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/__init__.py +0 -0
  184. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/_block.py +0 -0
  185. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/_control.py +0 -0
  186. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/_hdf_panda.py +0 -0
  187. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/_table.py +0 -0
  188. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/_trigger.py +0 -0
  189. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/_utils.py +0 -0
  190. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/fastcs/panda/_writer.py +0 -0
  191. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/plan_stubs/__init__.py +0 -0
  192. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/plan_stubs/_fly.py +0 -0
  193. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/plan_stubs/_nd_attributes.py +0 -0
  194. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/py.typed +0 -0
  195. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/__init__.py +0 -0
  196. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/demo/__init__.py +0 -0
  197. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/demo/_pattern_detector/__init__.py +0 -0
  198. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +0 -0
  199. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +0 -0
  200. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_detector_writer.py +0 -0
  201. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +0 -0
  202. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/sim/testing/__init__.py +0 -0
  203. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/__init__.py +0 -0
  204. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/base_devices/__init__.py +0 -0
  205. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/base_devices/_tango_readable.py +0 -0
  206. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/demo/__init__.py +0 -0
  207. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/demo/_detector.py +0 -0
  208. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/demo/_tango/__init__.py +0 -0
  209. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/demo/_tango/_servers.py +0 -0
  210. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/signal/__init__.py +0 -0
  211. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/signal/_signal.py +0 -0
  212. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async/tango/signal/_tango_transport.py +0 -0
  213. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async.egg-info/dependency_links.txt +0 -0
  214. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async.egg-info/entry_points.txt +0 -0
  215. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async.egg-info/requires.txt +0 -0
  216. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/src/ophyd_async.egg-info/top_level.txt +0 -0
  217. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/system_tests/epics/eiger/README.md +0 -0
  218. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/system_tests/epics/eiger/start_iocs_and_run_tests.sh +0 -0
  219. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/conftest.py +0 -0
  220. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_device_collector.py +0 -0
  221. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_log.py +0 -0
  222. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_protocol.py +0 -0
  223. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_providers.py +0 -0
  224. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_status.py +0 -0
  225. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_table.py +0 -0
  226. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/core/test_watchable_async_status.py +0 -0
  227. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/adcore/test_drivers.py +0 -0
  228. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/adcore/test_scans.py +0 -0
  229. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/adcore/test_single_trigger.py +0 -0
  230. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/adpilatus/test_pilatus.py +0 -0
  231. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/conftest.py +0 -0
  232. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/eiger/test_eiger_controller.py +0 -0
  233. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/eiger/test_eiger_detector.py +0 -0
  234. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/signal/test_records.db +0 -0
  235. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/epics/test_areadetector_subclass_naming.py +0 -0
  236. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/fastcs/panda/test_seq_table.py +0 -0
  237. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/fastcs/panda/test_trigger.py +0 -0
  238. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/sim/__init__.py +0 -0
  239. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/sim/conftest.py +0 -0
  240. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/sim/demo/__init__.py +0 -0
  241. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/sim/test_pattern_generator.py +0 -0
  242. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/sim/test_sim_writer.py +0 -0
  243. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/sim/test_streaming_plan.py +0 -0
  244. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/tango/test_tango_signals.py +0 -0
  245. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/test_cli.py +0 -0
  246. {ophyd_async-0.8.0a2 → ophyd_async-0.8.0a4}/tests/test_data/test_yaml_save.yml +0 -0
@@ -1,5 +1,5 @@
1
1
  # Changes here will be overwritten by Copier
2
- _commit: 2.4.0
2
+ _commit: 2.5.0
3
3
  _src_path: gh:DiamondLightSource/python-copier-template
4
4
  author_email: tom.cobb@diamond.ac.uk
5
5
  author_name: Tom Cobb
@@ -24,4 +24,4 @@ It is recommended that developers use a [vscode devcontainer](https://code.visua
24
24
 
25
25
  This project was created using the [Diamond Light Source Copier Template](https://github.com/DiamondLightSource/python-copier-template) for Python projects.
26
26
 
27
- For more information on common tasks like setting up a developer environment, running the tests, and setting a pre-commit hook, see the template's [How-to guides](https://diamondlightsource.github.io/python-copier-template/2.4.0/how-to.html).
27
+ For more information on common tasks like setting up a developer environment, running the tests, and setting a pre-commit hook, see the template's [How-to guides](https://diamondlightsource.github.io/python-copier-template/2.5.0/how-to.html).
@@ -16,4 +16,4 @@ jobs:
16
16
  - name: Publish to PyPI using trusted publishing
17
17
  uses: pypa/gh-action-pypi-publish@release/v1
18
18
  with:
19
- attestations: false
19
+ attestations: 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@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8
26
+ uses: softprops/action-gh-release@e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b8 # v2.0.9
27
27
  with:
28
28
  prerelease: ${{ contains(github.ref_name, 'a') || contains(github.ref_name, 'b') || contains(github.ref_name, 'rc') }}
29
29
  files: "*"
@@ -20,7 +20,7 @@ jobs:
20
20
  if: needs.check.outputs.branch-pr == ''
21
21
  strategy:
22
22
  matrix:
23
- runs-on: ["ubuntu-latest"] # can add windows-latest, macos-latest
23
+ runs-on: ["ubuntu-latest", "windows-latest"] # can add macos-latest
24
24
  python-version: ["3.10","3.11"] # 3.12 should be added when p4p is updated
25
25
  include:
26
26
  # Include one that runs in the dev environment
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ophyd-async
3
- Version: 0.8.0a2
3
+ Version: 0.8.0a4
4
4
  Summary: Asynchronous Bluesky hardware abstraction code, compatible with control systems like EPICS and Tango
5
5
  Author-email: Tom Cobb <tom.cobb@diamond.ac.uk>
6
6
  License: BSD 3-Clause License
@@ -10,7 +10,7 @@ from ophyd_async.core import (
10
10
  StandardDetector,
11
11
  )
12
12
  from ophyd_async.epics import adcore
13
- from ophyd_async.epics.signal import epics_signal_rw_rbv
13
+ from ophyd_async.epics.core import epics_signal_rw_rbv
14
14
 
15
15
 
16
16
  class FooDriver(adcore.ADBaseIO):
@@ -0,0 +1,140 @@
1
+ # 9. Procedural vs Declarative Devices
2
+
3
+ Date: 01/10/24
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ In [](./0006-procedural-device-definitions.rst) we decided we preferred the procedural approach to devices, because of the issue of applying structure like `DeviceVector`. Since then we have `FastCS` and `Tango` support which use a declarative approach. We need to decide whether we are happy with this situation, or whether we should go all in one way or the other. A suitable test Device would be:
12
+
13
+ ```python
14
+ class EpicsProceduralDevice(StandardReadable):
15
+ def __init__(self, prefix: str, num_values: int, name="") -> None:
16
+ with self.add_children_as_readables():
17
+ self.value = DeviceVector(
18
+ {
19
+ i: epics_signal_r(float, f"{prefix}Value{i}")
20
+ for i in range(1, num_values + 1)
21
+ }
22
+ )
23
+ with self.add_children_as_readables(ConfigSignal):
24
+ self.mode = epics_signal_rw(EnergyMode, prefix + "Mode")
25
+ super().__init__(name=name)
26
+ ```
27
+
28
+ and a Tango/FastCS procedural equivalent would be (if we add support to StandardReadable for Format.HINTED_SIGNAL and Format.CONFIG_SIGNAL annotations):
29
+ ```python
30
+ class TangoDeclarativeDevice(StandardReadable, TangoDevice):
31
+ value: Annotated[DeviceVector[SignalR[float]], Format.HINTED_SIGNAL]
32
+ mode: Annotated[SignalRW[EnergyMode], Format.CONFIG_SIGNAL]
33
+ ```
34
+
35
+ But we could specify the Tango one procedurally (with some slight ugliness around the DeviceVector):
36
+ ```python
37
+ class TangoProceduralDevice(StandardReadable):
38
+ def __init__(self, prefix: str, name="") -> None:
39
+ with self.add_children_as_readables():
40
+ self.value = DeviceVector({0: tango_signal_r(float)})
41
+ with self.add_children_as_readables(ConfigSignal):
42
+ self.mode = tango_signal_rw(EnergyMode)
43
+ super().__init__(name=name, connector=TangoConnector(prefix))
44
+ ```
45
+
46
+ or the EPICS one could be declarative:
47
+ ```python
48
+ class EpicsDeclarativeDevice(StandardReadable, EpicsDevice):
49
+ value: Annotated[
50
+ DeviceVector[SignalR[float]], Format.HINTED_SIGNAL, EpicsSuffix("Value%d", "num_values")
51
+ ]
52
+ mode: Annotated[SignalRW[EnergyMode], Format.CONFIG_SIGNAL, EpicsSuffix("Mode")]
53
+ ```
54
+
55
+ Which do we prefer?
56
+
57
+ ## Decision
58
+
59
+ We decided that the declarative approach is to be preferred until we need to write formatted strings. At that point we should drop to an `__init__` method and a for loop. This is not a step towards only supporting the declarative approach and there are no plans to drop the procedural approach.
60
+
61
+ The two approaches now look like:
62
+
63
+ ```python
64
+ class Sensor(StandardReadable, EpicsDevice):
65
+ """A demo sensor that produces a scalar value based on X and Y Movers"""
66
+
67
+ value: A[SignalR[float], PvSuffix("Value"), Format.HINTED_SIGNAL]
68
+ mode: A[SignalRW[EnergyMode], PvSuffix("Mode"), Format.CONFIG_SIGNAL]
69
+
70
+
71
+ class SensorGroup(StandardReadable):
72
+ def __init__(self, prefix: str, name: str = "", sensor_count: int = 3) -> None:
73
+ with self.add_children_as_readables():
74
+ self.sensors = DeviceVector(
75
+ {i: Sensor(f"{prefix}{i}:") for i in range(1, sensor_count + 1)}
76
+ )
77
+ super().__init__(name)
78
+ ```
79
+
80
+ ## Consequences
81
+
82
+ We need to:
83
+ - Add support for reading annotations and `PvSuffix` in an `ophyd_async.epics.core.EpicsDevice` baseclass
84
+ - Do the `Format.HINTED_SIGNAL` and `Format.CONFIG_SIGNAL` flags in annotations for `StandardReadable`
85
+ - Ensure we can always drop to `__init__`
86
+
87
+
88
+ ## pvi structure changes
89
+ Structure read from `.value` now includes `DeviceVector` support. Requires at least PandABlocks-ioc 0.11.2
90
+
91
+ ## Epics `signal` module moves
92
+ `ophyd_async.epics.signal` moves to `ophyd_async.epics.core` with a backwards compat module that emits deprecation warning.
93
+ ```python
94
+ # old
95
+ from ophyd_async.epics.signal import epics_signal_rw
96
+ # new
97
+ from ophyd_async.epics.core import epics_signal_rw
98
+ ```
99
+
100
+ ## `StandardReadable` wrappers change to `StandardReadableFormat`
101
+ `StandardReadable` wrappers change to enum members of `StandardReadableFormat` (normally imported as `Format`)
102
+ ```python
103
+ # old
104
+ from ophyd_async.core import ConfigSignal, HintedSignal
105
+ class MyDevice(StandardReadable):
106
+ def __init__(self):
107
+ self.add_readables([sig1], ConfigSignal)
108
+ self.add_readables([sig2], HintedSignal)
109
+ self.add_readables([sig3], HintedSignal.uncached)
110
+ # new
111
+ from ophyd_async.core import StandardReadableFormat as Format
112
+ class MyDevice(StandardReadable):
113
+ def __init__(self):
114
+ self.add_readables([sig1], Format.CONFIG_SIGNAL)
115
+ self.add_readables([sig2], Format.HINTED_SIGNAL)
116
+ self.add_readables([sig3], Format.HINTED_UNCACHED_SIGNAL
117
+ ```
118
+
119
+ ## Declarative Devices are now available
120
+ ```python
121
+ # old
122
+ from ophyd_async.core import ConfigSignal, HintedSignal
123
+ from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
124
+
125
+ class Sensor(StandardReadable):
126
+ def __init__(self, prefix: str, name="") -> None:
127
+ with self.add_children_as_readables(HintedSignal):
128
+ self.value = epics_signal_r(float, prefix + "Value")
129
+ with self.add_children_as_readables(ConfigSignal):
130
+ self.mode = epics_signal_rw(EnergyMode, prefix + "Mode")
131
+ super().__init__(name=name)
132
+ # new
133
+ from typing import Annotated as A
134
+ from ophyd_async.core import StandardReadableFormat as Format
135
+ from ophyd_async.epics.core import EpicsDevice, PvSuffix, epics_signal_r, epics_signal_rw
136
+
137
+ class Sensor(StandardReadable, EpicsDevice):
138
+ value: A[SignalR[float], PvSuffix("Value"), Format.HINTED_SIGNAL]
139
+ mode: A[SignalRW[EnergyMode], PvSuffix("Mode"), Format.CONFIG_SIGNAL]
140
+ ```
@@ -94,7 +94,8 @@ reportMissingImports = false # Ignore missing stubs in imported modules
94
94
  # Run pytest with all our checkers, and don't spam us with massive tracebacks on error
95
95
  addopts = """
96
96
  --tb=native -vv --strict-markers --doctest-modules
97
- --doctest-glob="*.rst" --doctest-glob="*.md" --ignore=docs/examples
97
+ --doctest-glob="*.rst" --doctest-glob="*.md"
98
+ --ignore=docs/examples --ignore=src/ophyd_async/epics/signal.py
98
99
  """
99
100
  # https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings
100
101
  filterwarnings = "error"
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.8.0a2'
15
+ __version__ = version = '0.8.0a4'
16
16
  __version_tuple__ = version_tuple = (0, 8, 0)
@@ -45,7 +45,12 @@ from ._providers import (
45
45
  UUIDFilenameProvider,
46
46
  YMDPathProvider,
47
47
  )
48
- from ._readable import ConfigSignal, HintedSignal, StandardReadable
48
+ from ._readable import (
49
+ ConfigSignal,
50
+ HintedSignal,
51
+ StandardReadable,
52
+ StandardReadableFormat,
53
+ )
49
54
  from ._signal import (
50
55
  Signal,
51
56
  SignalR,
@@ -78,6 +83,7 @@ from ._utils import (
78
83
  DEFAULT_TIMEOUT,
79
84
  CalculatableTimeout,
80
85
  Callback,
86
+ LazyMock,
81
87
  NotConnected,
82
88
  Reference,
83
89
  StrictEnum,
@@ -141,6 +147,7 @@ __all__ = [
141
147
  "ConfigSignal",
142
148
  "HintedSignal",
143
149
  "StandardReadable",
150
+ "StandardReadableFormat",
144
151
  "Signal",
145
152
  "SignalR",
146
153
  "SignalRW",
@@ -170,6 +177,7 @@ __all__ = [
170
177
  "DEFAULT_TIMEOUT",
171
178
  "CalculatableTimeout",
172
179
  "Callback",
180
+ "LazyMock",
173
181
  "CALCULATE_TIMEOUT",
174
182
  "NotConnected",
175
183
  "Reference",
@@ -3,17 +3,15 @@ from __future__ import annotations
3
3
  import asyncio
4
4
  import sys
5
5
  from collections.abc import Coroutine, Iterator, Mapping, MutableMapping
6
+ from functools import cached_property
6
7
  from logging import LoggerAdapter, getLogger
7
8
  from typing import Any, TypeVar
8
- from unittest.mock import Mock
9
9
 
10
10
  from bluesky.protocols import HasName
11
11
  from bluesky.run_engine import call_in_bluesky_event_loop, in_bluesky_event_loop
12
12
 
13
13
  from ._protocol import Connectable
14
- from ._utils import DEFAULT_TIMEOUT, NotConnected, wait_for_connection
15
-
16
- _device_mocks: dict[Device, Mock] = {}
14
+ from ._utils import DEFAULT_TIMEOUT, LazyMock, NotConnected, wait_for_connection
17
15
 
18
16
 
19
17
  class DeviceConnector:
@@ -37,25 +35,23 @@ class DeviceConnector:
37
35
  during ``__init__``.
38
36
  """
39
37
 
40
- async def connect(
41
- self,
42
- device: Device,
43
- mock: bool | Mock,
44
- timeout: float,
45
- force_reconnect: bool,
46
- ):
38
+ async def connect_mock(self, device: Device, mock: LazyMock):
39
+ # Connect serially, no errors to gather up as in mock mode
40
+ for name, child_device in device.children():
41
+ await child_device.connect(mock=mock.child(name))
42
+
43
+ async def connect_real(self, device: Device, timeout: float, force_reconnect: bool):
47
44
  """Used during ``Device.connect``.
48
45
 
49
46
  This is called when a previous connect has not been done, or has been
50
47
  done in a different mock more. It should connect the Device and all its
51
48
  children.
52
49
  """
53
- coros = {}
54
- for name, child_device in device.children():
55
- child_mock = getattr(mock, name) if mock else mock # Mock() or False
56
- coros[name] = child_device.connect(
57
- mock=child_mock, timeout=timeout, force_reconnect=force_reconnect
58
- )
50
+ # Connect in parallel, gathering up NotConnected errors
51
+ coros = {
52
+ name: child_device.connect(timeout=timeout, force_reconnect=force_reconnect)
53
+ for name, child_device in device.children()
54
+ }
59
55
  await wait_for_connection(**coros)
60
56
 
61
57
 
@@ -67,14 +63,14 @@ class Device(HasName, Connectable):
67
63
  parent: Device | None = None
68
64
  # None if connect hasn't started, a Task if it has
69
65
  _connect_task: asyncio.Task | None = None
70
- # If not None, then this is the mock arg of the previous connect
71
- # to let us know if we can reuse an existing connection
72
- _connect_mock_arg: bool | None = None
66
+ # The mock if we have connected in mock mode
67
+ _mock: LazyMock | None = None
73
68
 
74
69
  def __init__(
75
70
  self, name: str = "", connector: DeviceConnector | None = None
76
71
  ) -> None:
77
72
  self._connector = connector or DeviceConnector()
73
+ self._connector.create_children_from_annotations(self)
78
74
  self.set_name(name)
79
75
 
80
76
  @property
@@ -82,10 +78,18 @@ class Device(HasName, Connectable):
82
78
  """Return the name of the Device"""
83
79
  return self._name
84
80
 
81
+ @cached_property
82
+ def _child_devices(self) -> dict[str, Device]:
83
+ return {}
84
+
85
85
  def children(self) -> Iterator[tuple[str, Device]]:
86
- for attr_name, attr in self.__dict__.items():
87
- if attr_name != "parent" and isinstance(attr, Device):
88
- yield attr_name, attr
86
+ yield from self._child_devices.items()
87
+
88
+ @cached_property
89
+ def log(self) -> LoggerAdapter:
90
+ return LoggerAdapter(
91
+ getLogger("ophyd_async.devices"), {"ophyd_async_device_name": self.name}
92
+ )
89
93
 
90
94
  def set_name(self, name: str):
91
95
  """Set ``self.name=name`` and each ``self.child.name=name+"-child"``.
@@ -96,28 +100,33 @@ class Device(HasName, Connectable):
96
100
  New name to set
97
101
  """
98
102
  self._name = name
99
- # Ensure self.log is recreated after a name change
100
- self.log = LoggerAdapter(
101
- getLogger("ophyd_async.devices"), {"ophyd_async_device_name": self.name}
102
- )
103
+ # Ensure logger is recreated after a name change
104
+ if "log" in self.__dict__:
105
+ del self.log
103
106
  for child_name, child in self.children():
104
107
  child_name = f"{self.name}-{child_name.strip('_')}" if self.name else ""
105
108
  child.set_name(child_name)
106
109
 
107
110
  def __setattr__(self, name: str, value: Any) -> None:
111
+ # Bear in mind that this function is called *a lot*, so
112
+ # we need to make sure nothing expensive happens in it...
108
113
  if name == "parent":
109
114
  if self.parent not in (value, None):
110
115
  raise TypeError(
111
116
  f"Cannot set the parent of {self} to be {value}: "
112
117
  f"it is already a child of {self.parent}"
113
118
  )
114
- elif isinstance(value, Device):
119
+ # ...hence not doing an isinstance check for attributes we
120
+ # know not to be Devices
121
+ elif name not in _not_device_attrs and isinstance(value, Device):
115
122
  value.parent = self
116
- return super().__setattr__(name, value)
123
+ self._child_devices[name] = value
124
+ # ...and avoiding the super call as we know it resolves to `object`
125
+ return object.__setattr__(self, name, value)
117
126
 
118
127
  async def connect(
119
128
  self,
120
- mock: bool | Mock = False,
129
+ mock: bool | LazyMock = False,
121
130
  timeout: float = DEFAULT_TIMEOUT,
122
131
  force_reconnect: bool = False,
123
132
  ) -> None:
@@ -132,26 +141,39 @@ class Device(HasName, Connectable):
132
141
  timeout:
133
142
  Time to wait before failing with a TimeoutError.
134
143
  """
135
- uses_mock = bool(mock)
136
- can_use_previous_connect = (
137
- uses_mock is self._connect_mock_arg
138
- and self._connect_task
139
- and not (self._connect_task.done() and self._connect_task.exception())
140
- )
141
- if mock is True:
142
- mock = Mock() # create a new Mock if one not provided
143
- if force_reconnect or not can_use_previous_connect:
144
- self._connect_mock_arg = uses_mock
145
- if self._connect_mock_arg:
146
- _device_mocks[self] = mock
147
- coro = self._connector.connect(
148
- device=self, mock=mock, timeout=timeout, force_reconnect=force_reconnect
144
+ if mock:
145
+ # Always connect in mock mode serially
146
+ if isinstance(mock, LazyMock):
147
+ # Use the provided mock
148
+ self._mock = mock
149
+ elif not self._mock:
150
+ # Make one
151
+ self._mock = LazyMock()
152
+ await self._connector.connect_mock(self, self._mock)
153
+ else:
154
+ # Try to cache the connect in real mode
155
+ can_use_previous_connect = (
156
+ self._mock is None
157
+ and self._connect_task
158
+ and not (self._connect_task.done() and self._connect_task.exception())
149
159
  )
150
- self._connect_task = asyncio.create_task(coro)
151
-
152
- assert self._connect_task, "Connect task not created, this shouldn't happen"
153
- # Wait for it to complete
154
- await self._connect_task
160
+ if force_reconnect or not can_use_previous_connect:
161
+ self._mock = None
162
+ coro = self._connector.connect_real(self, timeout, force_reconnect)
163
+ self._connect_task = asyncio.create_task(coro)
164
+ assert self._connect_task, "Connect task not created, this shouldn't happen"
165
+ # Wait for it to complete
166
+ await self._connect_task
167
+
168
+
169
+ _not_device_attrs = {
170
+ "_name",
171
+ "_children",
172
+ "_connector",
173
+ "_timeout",
174
+ "_mock",
175
+ "_connect_task",
176
+ }
155
177
 
156
178
 
157
179
  DeviceT = TypeVar("DeviceT", bound=Device)