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

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