pymmcore-plus 0.15.4__tar.gz → 0.17.0__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 (128) hide show
  1. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/PKG-INFO +7 -4
  2. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/pyproject.toml +16 -6
  3. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/__init__.py +20 -1
  4. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/_accumulator.py +23 -5
  5. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/_cli.py +44 -26
  6. pymmcore_plus-0.17.0/src/pymmcore_plus/_discovery.py +344 -0
  7. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/_ipy_completion.py +1 -1
  8. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/_logger.py +3 -3
  9. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/_util.py +9 -245
  10. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/_device.py +57 -13
  11. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/_mmcore_plus.py +20 -23
  12. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/_property.py +35 -29
  13. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/_sequencing.py +2 -0
  14. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/events/_device_signal_view.py +8 -1
  15. pymmcore_plus-0.17.0/src/pymmcore_plus/experimental/simulate/__init__.py +88 -0
  16. pymmcore_plus-0.17.0/src/pymmcore_plus/experimental/simulate/_objects.py +670 -0
  17. pymmcore_plus-0.17.0/src/pymmcore_plus/experimental/simulate/_render.py +510 -0
  18. pymmcore_plus-0.17.0/src/pymmcore_plus/experimental/simulate/_sample.py +156 -0
  19. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/unicore/__init__.py +2 -0
  20. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/unicore/_device_manager.py +46 -13
  21. pymmcore_plus-0.17.0/src/pymmcore_plus/experimental/unicore/core/_config.py +706 -0
  22. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/unicore/core/_unicore.py +834 -18
  23. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/unicore/devices/_device_base.py +13 -0
  24. pymmcore_plus-0.17.0/src/pymmcore_plus/experimental/unicore/devices/_hub.py +50 -0
  25. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/unicore/devices/_stage.py +46 -1
  26. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/unicore/devices/_state.py +6 -0
  27. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/install.py +149 -18
  28. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/_engine.py +268 -73
  29. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/handlers/_5d_writer_base.py +16 -5
  30. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/handlers/_tensorstore_handler.py +7 -1
  31. pymmcore_plus-0.17.0/src/pymmcore_plus/metadata/_ome.py +553 -0
  32. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/metadata/functions.py +2 -1
  33. {pymmcore_plus-0.15.4/tests/unicore → pymmcore_plus-0.17.0/tests/00_unicore}/test_camera.py +1 -1
  34. pymmcore_plus-0.17.0/tests/00_unicore/test_pydevice_config.py +249 -0
  35. {pymmcore_plus-0.15.4/tests/unicore → pymmcore_plus-0.17.0/tests/00_unicore}/test_slm.py +1 -1
  36. {pymmcore_plus-0.15.4/tests/unicore → pymmcore_plus-0.17.0/tests/00_unicore}/test_state.py +35 -0
  37. pymmcore_plus-0.17.0/tests/00_unicore/test_unicore.py +1190 -0
  38. pymmcore_plus-0.17.0/tests/00_unicore/test_z_stage.py +86 -0
  39. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_accumulators.py +1 -1
  40. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_cli.py +21 -15
  41. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_config_group_class.py +1 -1
  42. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_core.py +4 -4
  43. pymmcore_plus-0.17.0/tests/test_core_references.py +194 -0
  44. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_device_class.py +98 -2
  45. pymmcore_plus-0.17.0/tests/test_install.py +171 -0
  46. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_ipy_completions.py +5 -5
  47. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_mda.py +119 -2
  48. pymmcore_plus-0.17.0/tests/test_metadata_to_ome.py +354 -0
  49. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_model.py +9 -1
  50. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_sequencing.py +19 -1
  51. pymmcore_plus-0.17.0/tests/test_simulate.py +552 -0
  52. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_slm_image.py +1 -1
  53. pymmcore_plus-0.15.4/tests/unicore/test_unicore.py +0 -291
  54. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/.gitignore +0 -0
  55. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/LICENSE +0 -0
  56. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/README.md +0 -0
  57. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/_benchmark.py +0 -0
  58. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/_build.py +0 -0
  59. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/_pymmcore.py +0 -0
  60. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/__init__.py +0 -0
  61. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/_adapter.py +0 -0
  62. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/_config.py +0 -0
  63. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/_config_group.py +0 -0
  64. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/_constants.py +0 -0
  65. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/_metadata.py +0 -0
  66. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/events/__init__.py +0 -0
  67. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/events/_deprecated.py +0 -0
  68. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/events/_norm_slot.py +0 -0
  69. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/events/_prop_event_mixin.py +0 -0
  70. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/events/_protocol.py +0 -0
  71. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/events/_psygnal.py +0 -0
  72. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/core/events/_qsignals.py +0 -0
  73. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/__init__.py +0 -0
  74. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/unicore/_proxy.py +0 -0
  75. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/unicore/core/__init__.py +0 -0
  76. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/unicore/core/_sequence_buffer.py +0 -0
  77. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/unicore/devices/__init__.py +0 -0
  78. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/unicore/devices/_camera.py +0 -0
  79. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/unicore/devices/_generic_device.py +0 -0
  80. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/unicore/devices/_properties.py +0 -0
  81. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/unicore/devices/_shutter.py +0 -0
  82. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/experimental/unicore/devices/_slm.py +0 -0
  83. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/__init__.py +0 -0
  84. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/_protocol.py +0 -0
  85. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/_runner.py +0 -0
  86. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/_thread_relay.py +0 -0
  87. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/events/__init__.py +0 -0
  88. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/events/_protocol.py +0 -0
  89. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/events/_psygnal.py +0 -0
  90. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/events/_qsignals.py +0 -0
  91. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/handlers/__init__.py +0 -0
  92. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/handlers/_img_sequence_writer.py +0 -0
  93. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/handlers/_ome_tiff_writer.py +0 -0
  94. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/handlers/_ome_zarr_writer.py +0 -0
  95. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mda/handlers/_util.py +0 -0
  96. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/metadata/__init__.py +0 -0
  97. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/metadata/schema.py +0 -0
  98. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/metadata/serialize.py +0 -0
  99. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/mocks.py +0 -0
  100. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/model/__init__.py +0 -0
  101. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/model/_config_file.py +0 -0
  102. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/model/_config_group.py +0 -0
  103. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/model/_core_device.py +0 -0
  104. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/model/_core_link.py +0 -0
  105. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/model/_device.py +0 -0
  106. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/model/_microscope.py +0 -0
  107. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/model/_pixel_size_config.py +0 -0
  108. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/model/_property.py +0 -0
  109. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/py.typed +0 -0
  110. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/src/pymmcore_plus/seq_tester.py +0 -0
  111. {pymmcore_plus-0.15.4/tests/unicore → pymmcore_plus-0.17.0/tests/00_unicore}/conftest.py +0 -0
  112. {pymmcore_plus-0.15.4/tests/unicore → pymmcore_plus-0.17.0/tests/00_unicore}/test_sequence_buffer.py +0 -0
  113. {pymmcore_plus-0.15.4/tests/unicore → pymmcore_plus-0.17.0/tests/00_unicore}/test_shutter.py +0 -0
  114. {pymmcore_plus-0.15.4/tests/unicore → pymmcore_plus-0.17.0/tests/00_unicore}/test_xy_stage.py +0 -0
  115. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/__init__.py +0 -0
  116. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/conftest.py +0 -0
  117. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/io/test_image_sequence_writer.py +0 -0
  118. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/io/test_ome_tiff.py +0 -0
  119. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/io/test_zarr_writers.py +0 -0
  120. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/local_config.cfg +0 -0
  121. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_adapter_class.py +0 -0
  122. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_bench.py +0 -0
  123. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_events.py +0 -0
  124. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_metadata.py +0 -0
  125. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_misc.py +0 -0
  126. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_pixel_config_class.py +0 -0
  127. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_property_class.py +0 -0
  128. {pymmcore_plus-0.15.4 → pymmcore_plus-0.17.0}/tests/test_thread_relay.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pymmcore-plus
3
- Version: 0.15.4
3
+ Version: 0.17.0
4
4
  Summary: pymmcore superset providing improved APIs, event handling, and a pure python acquisition engine
5
5
  Project-URL: Source, https://github.com/pymmcore-plus/pymmcore-plus
6
6
  Project-URL: Tracker, https://github.com/pymmcore-plus/pymmcore-plus/issues
@@ -28,18 +28,19 @@ Requires-Python: >=3.9
28
28
  Requires-Dist: numpy>=1.25.2
29
29
  Requires-Dist: numpy>=1.26.0; python_version >= '3.12'
30
30
  Requires-Dist: numpy>=2.1.0; python_version >= '3.13'
31
+ Requires-Dist: ome-types>=0.6.0
31
32
  Requires-Dist: platformdirs>=3.0.0
32
33
  Requires-Dist: psygnal>=0.10
33
- Requires-Dist: pymmcore>=11.9.0.73.0
34
+ Requires-Dist: pymmcore>=11.10.0.74.0
34
35
  Requires-Dist: rich>=10.2.0
35
36
  Requires-Dist: tensorstore!=0.1.72,>=0.1.67
36
37
  Requires-Dist: tensorstore!=0.1.72,>=0.1.71; python_version >= '3.13'
37
- Requires-Dist: typer>=0.4.2
38
+ Requires-Dist: typer>=0.13.0
38
39
  Requires-Dist: typing-extensions>=4
39
40
  Requires-Dist: useq-schema>=0.7.2
40
41
  Provides-Extra: cli
41
42
  Requires-Dist: rich>=10.2.0; extra == 'cli'
42
- Requires-Dist: typer>=0.4.2; extra == 'cli'
43
+ Requires-Dist: typer>=0.13.0; extra == 'cli'
43
44
  Provides-Extra: io
44
45
  Requires-Dist: tifffile>=2021.6.14; extra == 'io'
45
46
  Requires-Dist: zarr<3,>=2.15; extra == 'io'
@@ -51,6 +52,8 @@ Provides-Extra: pyside2
51
52
  Requires-Dist: pyside2>=5.15.2.1; extra == 'pyside2'
52
53
  Provides-Extra: pyside6
53
54
  Requires-Dist: pyside6==6.7.3; extra == 'pyside6'
55
+ Provides-Extra: simulate
56
+ Requires-Dist: pillow>=11.0; extra == 'simulate'
54
57
  Description-Content-Type: text/markdown
55
58
 
56
59
  # pymmcore-plus
@@ -40,27 +40,30 @@ dependencies = [
40
40
  "numpy >=1.26.0; python_version >= '3.12'",
41
41
  "numpy >=1.25.2",
42
42
  "psygnal >=0.10",
43
- "pymmcore >=11.9.0.73.0",
43
+ "pymmcore >=11.10.0.74.0",
44
44
  "typing-extensions >=4", # not actually required at runtime
45
45
  "useq-schema >=0.7.2",
46
46
  "tensorstore >=0.1.71,!=0.1.72; python_version >= '3.13'",
47
47
  "tensorstore >=0.1.67,!=0.1.72",
48
48
  # cli requirements included by default for now
49
- "typer >=0.4.2",
49
+ "typer >=0.13.0",
50
50
  "rich >=10.2.0",
51
+ "ome-types >=0.6.0",
51
52
  ]
52
53
 
53
54
  # extras
54
55
  # https://peps.python.org/pep-0621/#dependencies-optional-dependencies
55
56
  [project.optional-dependencies]
56
- cli = ["typer >=0.4.2", "rich >=10.2.0"]
57
+ cli = ["typer >=0.13.0", "rich >=10.2.0"]
57
58
  io = ["tifffile >=2021.6.14", "zarr >=2.15,<3"]
59
+ simulate = ["pillow >=11.0"]
58
60
  PySide2 = ["PySide2 >=5.15.2.1"]
59
61
  PySide6 = ["PySide6 ==6.7.3"]
60
62
  PyQt5 = ["PyQt5 >=5.15.4"]
61
63
  PyQt6 = ["PyQt6 >=6.4.2"]
62
64
 
63
65
  [dependency-groups]
66
+ nano = ["pymmcore-nano==11.10.0.74.0"]
64
67
  docs = [
65
68
  "mkdocs >=1.4",
66
69
  "mkdocs-material>=9.5",
@@ -70,13 +73,14 @@ docs = [
70
73
  "mkdocs-typer ==0.0.3",
71
74
  ]
72
75
  test = [
73
- "pymmcore-plus[io]",
76
+ "pymmcore-plus[io,simulate]",
74
77
  "msgspec >= 0.19",
75
78
  "msgpack >=1",
76
- "pytest-cov >=5",
79
+ "pytest-cov >=7",
77
80
  "ipython>=8.18.0",
78
81
  "pytest >=8",
79
82
  "xarray >=2024.1",
83
+ "lxml >=6.0",
80
84
  ]
81
85
  test-codspeed = [{ include-group = "test" }, "pytest-codspeed >=3.2.0"]
82
86
  test-qt = [{ include-group = 'test' }, "pytest-qt ==4.4", "qtpy >=2"]
@@ -92,6 +96,8 @@ dev = [
92
96
  "pre-commit>=4.1.0",
93
97
  "ruff>=0.9.4",
94
98
  "pydantic >2.7.4; python_version >= '3.13'",
99
+ "opencv-python>=4.11.0.86",
100
+ "pytest-xdist>=3.8.0",
95
101
  ]
96
102
 
97
103
  [tool.uv.sources]
@@ -173,6 +179,9 @@ filterwarnings = [
173
179
  "error",
174
180
  "ignore:Failed to disconnect::pytestqt",
175
181
  "ignore:numpy.core.multiarray is deprecated",
182
+ "ignore:Focus direction is unknown",
183
+ "ignore:'BaseCommand' is deprecated:DeprecationWarning:typer",
184
+ "ignore:Failing to pass a value to the 'type_params' parameter",
176
185
  ]
177
186
  markers = ["run_last: mark a test to run last"]
178
187
 
@@ -200,6 +209,7 @@ exclude_lines = [
200
209
  "if TYPE_CHECKING:",
201
210
  "@overload",
202
211
  "except ImportError",
212
+ "except Exception as e:", # ok not to cover bare exception catches in tests
203
213
  "raise AssertionError",
204
214
  "\\.\\.\\.",
205
215
  "if __name__ == .__main__.:",
@@ -222,4 +232,4 @@ ignore = [
222
232
  ]
223
233
 
224
234
  [tool.typos.default]
225
- extend-ignore-identifiers-re = ["(?i)nd2?.*", "(?i)ome", "anager", "ba"]
235
+ extend-ignore-identifiers-re = ["(?i)nd2?.*", "(?i)ome", "anager", "ba", "(?:FOVs?)"]
@@ -2,16 +2,19 @@
2
2
 
3
3
  import logging
4
4
  from importlib.metadata import PackageNotFoundError, version
5
+ from typing import TYPE_CHECKING, Any
5
6
 
6
7
  try:
7
8
  __version__ = version("pymmcore-plus")
8
9
  except PackageNotFoundError: # pragma: no cover
9
10
  __version__ = "unknown"
10
11
 
12
+ if TYPE_CHECKING:
13
+ from ._ipy_completion import install_pymmcore_ipy_completion
11
14
 
12
15
  from ._accumulator import AbstractChangeAccumulator
16
+ from ._discovery import find_micromanager, use_micromanager
13
17
  from ._logger import configure_logging
14
- from ._util import find_micromanager, use_micromanager
15
18
  from .core import (
16
19
  ActionType,
17
20
  CFGCommand,
@@ -63,10 +66,26 @@ __all__ = [
63
66
  "__version__",
64
67
  "configure_logging",
65
68
  "find_micromanager",
69
+ "install_pymmcore_ipy_completion",
66
70
  "use_micromanager",
67
71
  ]
68
72
 
69
73
 
74
+ def __getattr__(name: str) -> Any:
75
+ """Lazy import for compatibility with pymmcore."""
76
+ if name == "install_pymmcore_ipy_completion":
77
+ try:
78
+ from ._ipy_completion import install_pymmcore_ipy_completion
79
+ except ImportError as e: # pragma: no cover
80
+ raise ImportError(
81
+ f"Error importing IPython completion for pymmcore-plus: {e}"
82
+ ) from None
83
+
84
+ return install_pymmcore_ipy_completion
85
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'. ")
86
+
87
+
88
+ # install the IPython completer when imported, if running in an IPython environment
70
89
  def _install_ipy_completer() -> None: # pragma: no cover
71
90
  import os
72
91
  import sys
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import abc
6
6
  import sys
7
+ import weakref
7
8
  from abc import ABC, abstractmethod
8
9
  from collections.abc import Sequence
9
10
  from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal, TypeVar
@@ -166,9 +167,10 @@ class DeviceAccumulator(abc.ABC, Generic[DT]):
166
167
  mmcore: CMMCorePlus | None = None,
167
168
  **kwargs: Any,
168
169
  ) -> None:
169
- self._mmcore = mmcore or CMMCorePlus.instance()
170
+ core = mmcore or CMMCorePlus.instance()
171
+ self._mmcore_ref = weakref.ref(core)
170
172
  dev_type = self._device_type()
171
- if not self._mmcore.getDeviceType(device_label) == dev_type: # pragma: no cover
173
+ if not core.getDeviceType(device_label) == dev_type: # pragma: no cover
172
174
  raise ValueError(
173
175
  f"Cannot create {self.__class__.__name__}. "
174
176
  f"Device {device_label!r} is not a {dev_type.name}. "
@@ -177,6 +179,16 @@ class DeviceAccumulator(abc.ABC, Generic[DT]):
177
179
  self._device_label = device_label
178
180
  super().__init__(**kwargs)
179
181
 
182
+ @property
183
+ def _mmcore(self) -> CMMCorePlus:
184
+ """Get the CMMCorePlus instance for this accumulator."""
185
+ if (mmcore := self._mmcore_ref()) is None: # pragma: no cover
186
+ raise RuntimeError(
187
+ f"The CMMCorePlus instance for this {self.__class__.__name__!r} has "
188
+ "been garbage collected."
189
+ )
190
+ return mmcore
191
+
180
192
  def _is_busy(self) -> bool:
181
193
  return self._mmcore.deviceBusy(self._device_label)
182
194
 
@@ -204,18 +216,24 @@ class DeviceAccumulator(abc.ABC, Generic[DT]):
204
216
  device_type = mmcore.getDeviceType(device)
205
217
  if cache_key not in DeviceAccumulator._CACHE:
206
218
  if device_type == cls._device_type():
207
- cls._CACHE[cache_key] = cls(device_label=device, mmcore=mmcore)
219
+ DeviceAccumulator._CACHE[cache_key] = cls(
220
+ device_label=device, mmcore=mmcore
221
+ )
208
222
  else:
209
223
  for sub in cls.__subclasses__():
210
224
  if sub._device_type() == device_type: # noqa: SLF001
211
- cls._CACHE[cache_key] = sub(device_label=device, mmcore=mmcore)
225
+ DeviceAccumulator._CACHE[cache_key] = sub(
226
+ device_label=device, mmcore=mmcore
227
+ )
212
228
  break
213
229
  else:
214
230
  raise ValueError(
215
231
  "No matching DeviceTypeMixin subclass found for device type "
216
232
  f"{device_type.name} (for device {device!r})."
217
233
  )
218
- obj = cls._CACHE[cache_key]
234
+
235
+ weakref.finalize(mmcore, DeviceAccumulator._CACHE.pop, cache_key, None)
236
+ obj = DeviceAccumulator._CACHE[cache_key]
219
237
  if not isinstance(obj, cls):
220
238
  raise TypeError(
221
239
  f"Cannot create {cls.__name__} for {device!r}. "
@@ -7,9 +7,9 @@ import sys
7
7
  import time
8
8
  from contextlib import suppress
9
9
  from pathlib import Path
10
+ from platform import system
10
11
  from typing import Optional, Union, cast
11
12
 
12
- from pymmcore_plus._util import get_device_interface_version
13
13
  from pymmcore_plus.core._device import Device
14
14
  from pymmcore_plus.core._mmcore_plus import CMMCorePlus
15
15
 
@@ -26,9 +26,9 @@ import pymmcore_plus
26
26
  from pymmcore_plus._build import DEFAULT_PACKAGES, build
27
27
  from pymmcore_plus._logger import configure_logging
28
28
  from pymmcore_plus._util import USER_DATA_MM_PATH
29
- from pymmcore_plus.install import PLATFORM
30
29
 
31
30
  app = typer.Typer(name="mmcore", no_args_is_help=True)
31
+ PLATFORM = system()
32
32
 
33
33
 
34
34
  def _show_version_and_exit(value: bool) -> None:
@@ -103,32 +103,44 @@ def clean(
103
103
 
104
104
  @app.command(name="list")
105
105
  def _list() -> None:
106
- """Show all Micro-Manager installs downloaded by pymmcore-plus."""
106
+ """Show all discovered Micro-Manager installations."""
107
+ from pymmcore_plus import _discovery
108
+
107
109
  configure_logging(stderr_level="CRITICAL")
108
- found: dict[Path, list[str]] = {}
110
+ found: dict[Path, list[tuple[str, _discovery.DiscoveredMM]]] = {}
109
111
  with suppress(Exception):
110
- for p in pymmcore_plus.find_micromanager(return_first=False):
111
- pth = Path(p)
112
- found.setdefault(pth.parent, []).append(pth.name)
112
+ for dm in _discovery.discover_mm():
113
+ pth = Path(dm.path)
114
+ found.setdefault(pth.parent, []).append((pth.name, dm))
115
+
116
+ active_mm = _discovery.find_micromanager(return_first=True)
117
+ required_div = _discovery.PYMMCORE_DIV
113
118
 
114
119
  if found:
115
- first = True
120
+ print(f"[magenta]Required pymmcore device interface version: {required_div}")
116
121
  for parent, items in found.items():
117
- print(f":file_folder:[bold green] {parent}")
118
- for item in items:
119
- version = ""
120
- for _lib in (parent / item).glob("*_dal_*"):
121
- with suppress(Exception):
122
- div = get_device_interface_version(_lib)
123
- version = f" (Dev. Interface {div})"
124
- break
125
- bullet = " [bold yellow]*" if first else " •"
126
- using = " [bold blue](active)" if first else ""
127
- print(f"{bullet} [cyan]{item}{version}{using}")
128
- first = False
122
+ print(f"\n:file_folder:[bold green] {parent}")
123
+ for item_name, dm in items:
124
+ version = f" (DIV {dm.device_interface})" if dm.device_interface else ""
125
+ is_active = str(dm.path) == active_mm
126
+ is_compatible = dm.div_compatible
127
+
128
+ # Choose bullet and status
129
+ bullet = " •"
130
+ status = ""
131
+ if is_active:
132
+ bullet = " [bold yellow]"
133
+ if is_compatible:
134
+ status = " [bold blue](active)[/bold blue]"
135
+ else:
136
+ status = " [bold red](active, incompatible!)[/bold red]"
137
+ else:
138
+ status = " [red](incompatible)[/red]"
139
+
140
+ print(f"{bullet} [cyan]{item_name}{version}{status}")
129
141
  else:
130
- print(":x: [bold red]There are no pymmcore-plus Micro-Manager files.")
131
- print("[magenta]run `mmcore install` to install a version of Micro-Manager")
142
+ print(":x: [bold red]No Micro-Manager installations found.")
143
+ print("[magenta]Run `mmcore install` to install a version of Micro-Manager")
132
144
 
133
145
 
134
146
  @app.command()
@@ -174,18 +186,24 @@ def install(
174
186
  help="Do not use rich output. Useful for scripting.",
175
187
  show_default=False,
176
188
  ),
189
+ test_adapters: bool = typer.Option(
190
+ False,
191
+ "--test-adapters",
192
+ help="Install only test adapters (e.g. DemoCamera and others for testing).",
193
+ ),
177
194
  ) -> None:
178
195
  """Install Micro-Manager Device adapters from <https://download.micro-manager.org>."""
179
196
  import pymmcore_plus.install
180
197
 
198
+ kwargs = {}
181
199
  if plain_output:
182
200
 
183
201
  def _log_msg(text: str, color: str = "", emoji: str = "") -> None:
184
202
  print(text)
185
203
 
186
- pymmcore_plus.install.install(dest, release, log_msg=_log_msg)
187
- else:
188
- pymmcore_plus.install.install(dest, release)
204
+ kwargs["log_msg"] = _log_msg
205
+
206
+ pymmcore_plus.install.install(dest, release, test_adapters=test_adapters, **kwargs)
189
207
 
190
208
 
191
209
  @app.command()
@@ -414,7 +432,7 @@ def use(
414
432
  ),
415
433
  ) -> None:
416
434
  """Change the currently used Micro-manager version/path."""
417
- from pymmcore_plus._util import use_micromanager
435
+ from pymmcore_plus._discovery import use_micromanager
418
436
 
419
437
  _pth = Path(pattern)
420
438
  if _pth.exists():