pymodaq_plugins_utils 5.0.7__tar.gz → 5.3.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 (37) hide show
  1. pymodaq_plugins_utils-5.3.0/.github/workflows/compatibility.yml +15 -0
  2. pymodaq_plugins_utils-5.3.0/.github/workflows/python-publish.yml +11 -0
  3. pymodaq_plugins_utils-5.3.0/.github/workflows/tests.yml +8 -0
  4. pymodaq_plugins_utils-5.3.0/AGENTS.md +206 -0
  5. pymodaq_plugins_utils-5.3.0/CLAUDE.md +3 -0
  6. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/PKG-INFO +5 -2
  7. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/pyproject.toml +4 -1
  8. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/src/pymodaq_plugins_utils/hardware/serial_ports.py +2 -2
  9. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/src/pymodaq_plugins_utils/hardware/visa.py +2 -2
  10. pymodaq_plugins_utils-5.3.0/tests/test_plugin_package_structure.py +28 -0
  11. pymodaq_plugins_utils-5.0.7/.github/workflows/Test.yml +0 -10
  12. pymodaq_plugins_utils-5.0.7/.github/workflows/Testbase.yml +0 -44
  13. pymodaq_plugins_utils-5.0.7/.github/workflows/compatibility.yml +0 -78
  14. pymodaq_plugins_utils-5.0.7/.github/workflows/python-publish.yml +0 -40
  15. pymodaq_plugins_utils-5.0.7/.github/workflows/updater.yml +0 -24
  16. pymodaq_plugins_utils-5.0.7/tests/test_plugin_package_structure.py +0 -111
  17. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/.gitattributes +0 -0
  18. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/.gitignore +0 -0
  19. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/LICENSE +0 -0
  20. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/README.rst +0 -0
  21. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/hatch_build.py +0 -0
  22. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/icon.ico +0 -0
  23. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/src/pymodaq_plugins_utils/__init__.py +0 -0
  24. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/src/pymodaq_plugins_utils/app/__init__.py +0 -0
  25. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/src/pymodaq_plugins_utils/exporters/__init__.py +0 -0
  26. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/src/pymodaq_plugins_utils/extensions/__init__.py +0 -0
  27. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/src/pymodaq_plugins_utils/extensions/custom_extension_template.py +0 -0
  28. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/src/pymodaq_plugins_utils/hardware/__init__.py +0 -0
  29. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/src/pymodaq_plugins_utils/hardware/base.py +0 -0
  30. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/src/pymodaq_plugins_utils/hardware/camera_base_pylablib.py +0 -0
  31. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/src/pymodaq_plugins_utils/models/__init__.py +0 -0
  32. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/src/pymodaq_plugins_utils/resources/__init__.py +0 -0
  33. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/src/pymodaq_plugins_utils/resources/config_template.toml +0 -0
  34. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/src/pymodaq_plugins_utils/scanners/__init__.py +0 -0
  35. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/src/pymodaq_plugins_utils/utils.py +0 -0
  36. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/tests/hardware_test.py +0 -0
  37. {pymodaq_plugins_utils-5.0.7 → pymodaq_plugins_utils-5.3.0}/tests/import_report_pymodaq_plugins_utils_None.txt +0 -0
@@ -0,0 +1,15 @@
1
+ # Everything is defined once in PyMoDAQ/pymodaq_plugins_template (reusable-*.yml). The template is never released, so the ref after @ is its 5.3.x branch.
2
+ name: Compatibility with pymodaq (latest release)
3
+
4
+ on:
5
+ pull_request:
6
+ push:
7
+ branches: [main, master, '[0-9]*.x']
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ compatibility:
15
+ uses: PyMoDAQ/pymodaq_plugins_template/.github/workflows/reusable-compatibility.yml@5.3.x
@@ -0,0 +1,11 @@
1
+ # Everything is defined once in PyMoDAQ/pymodaq_plugins_template (reusable-*.yml). The template is never released, so the ref after @ is its 5.3.x branch.
2
+ name: Upload Python Package
3
+
4
+ on:
5
+ release:
6
+ types: [created]
7
+
8
+ jobs:
9
+ publish:
10
+ uses: PyMoDAQ/pymodaq_plugins_template/.github/workflows/reusable-publish.yml@5.3.x
11
+ secrets: inherit
@@ -0,0 +1,8 @@
1
+ # Everything is defined once in PyMoDAQ/pymodaq_plugins_template (reusable-*.yml). The template is never released, so the ref after @ is its 5.3.x branch.
2
+ name: Tests
3
+
4
+ on: [push, pull_request]
5
+
6
+ jobs:
7
+ tests:
8
+ uses: PyMoDAQ/pymodaq_plugins_template/.github/workflows/reusable-tests.yml@5.3.x
@@ -0,0 +1,206 @@
1
+ # AGENTS.md
2
+
3
+ Guidance for AI coding assistants (and humans) building on **PyMoDAQ 5.3.x** from this template.
4
+ PyMoDAQ is a Qt framework that drives lab experiments: hardware is added through small *plugins*, and PyMoDAQ
5
+ provides the GUI, threading, data model, scans and HDF5 saving. Python 3.10 to 3.13.
6
+
7
+ This repository is a plugin package named `pymodaq_plugins_<name>`. Rename everything that still says `template`
8
+ before publishing.
9
+
10
+ ## 1. Choose what to build
11
+
12
+ Prefer the first option that fits:
13
+
14
+ 1. **Instrument plugin** (`daq_move_plugins/`, `daq_viewer_plugins/`): a new device must appear in the Dashboard.
15
+ 2. **Dashboard extension** (`extensions/`, class derived from `CustomExt`): a workflow that uses instruments
16
+ (scans, feedback, custom panels, logging). It runs inside the Dashboard and reuses its stable mechanisms for
17
+ instruments: the experiment manager and the state manager (how a Dashboard setup is described, loaded and
18
+ restored; the word "preset" is obsolete), the module manager, control-module threading, shared controllers,
19
+ saving. **If the application drives instruments, build a `CustomExt`, not a `CustomApp`.**
20
+ 3. **Standalone app** (`app/`, class derived from `CustomApp`): only when the application needs **no
21
+ instruments** (data viewer, analysis, h5 browsing, configuration tools).
22
+ 4. A plain script, for a one-off sequence that does not need a GUI.
23
+
24
+ A **model** (section 6) is not a separate option: it tailors an existing extension (PID, optimizers, data mixer,
25
+ sequencer) to your experiment.
26
+
27
+ ## 2. Workflow
28
+
29
+ ```bash
30
+ pip install -e . # editable install of this plugin, from the repository root
31
+ check_plugin # static report on this package (hardware not needed)
32
+ check_plugin --fail-on todo -v # also list every unfinished TODO
33
+ pytest # template tests (package structure, plugin checks, behaviour against a fake controller)
34
+ ```
35
+
36
+ Installing PyMoDAQ itself is a separate matter. A plugin only needs the released packages (`pymodaq`,
37
+ `pymodaq_utils`, `pymodaq_data`, `pymodaq_gui`, installed as dependencies). If you also have to modify them, work in a
38
+ clone of the PyMoDAQ repository and install the four packages in editable mode with the script at its root, never with
39
+ `pip install -e .` inside each package (the script handles the order and the options):
40
+
41
+ ```bash
42
+ python install-packages.py -d # editable + dev extras, from the PyMoDAQ repository root
43
+ python install-packages.py -d -qt pyside6 # choose the Qt backend (pyqt5, pyqt6 or pyside6)
44
+ ```
45
+
46
+ Do not consider a task finished until `check_plugin` and `pytest` pass. Do not invent PyMoDAQ APIs: if a method
47
+ or attribute is not in the base classes (`DAQ_Move_base`, `DAQ_Viewer_base`, `CustomExt`) or in an existing
48
+ plugin, look it up in the installed `pymodaq` package instead of guessing.
49
+
50
+ ## 3. Instrument plugins
51
+
52
+ File and class names must match: `daq_move_<Name>.py` holds `DAQ_Move_<Name>`; `daq_<N>Dviewer_<Name>.py` holds
53
+ `DAQ_<N>DViewer_<Name>` (N = 0, 1, 2 or N). Each file ends with `if __name__ == '__main__': main(__file__)`.
54
+
55
+ **Actuator** (`DAQ_Move_base`). Override at least, in the order of the template (the order in which to develop them),
56
+ `ini_attributes`, `ini_stage`, `close`, `get_actuator_value`, `move_abs`, `move_rel`, `move_home`, `stop_motion`,
57
+ `commit_settings`.
58
+
59
+ - `ini_stage(controller=None)` returns `(info: str, initialized: bool)`. If `self.is_master`, create the controller;
60
+ otherwise store the `controller` passed in (several axes share one controller).
61
+ - Class attributes: `is_multiaxes`, `_axis_names`, `_controller_units`, `_epsilons`,
62
+ `data_actuator_type = DataActuatorType.DataActuator`, optionally `ui_type` and `has_encoder`. Always write the
63
+ `data_actuator_type` line: the default of the base class is `DataActuatorType.float`, a backcompatibility mode where
64
+ `move_abs`/`move_rel` receive a plain float (already in the axis unit) and `get_actuator_value` returns one. PyMoDAQ
65
+ guards that case, but do not use it in new code.
66
+ - `params` = list of dicts + `comon_parameters_fun(is_multiaxes, axis_names=..., epsilon=...)`.
67
+ - Positions are `DataActuator` objects with **units** (`units=self.axis_unit`); use `check_bound`,
68
+ `set_position_with_scaling` and `get_position_with_scaling` as in the template.
69
+
70
+ **Detector** (`DAQ_Viewer_base`). Override at least, in the order of the template, `ini_attributes`, `ini_detector`,
71
+ `close`, `grab_data`, `stop`, `commit_settings`.
72
+
73
+ - `ini_detector(controller=None)` returns `(info, initialized)`, same master/slave logic.
74
+ - `params = comon_parameters + [...]`.
75
+ - Data leaves the plugin as `DataToExport` containing `DataFromPlugins` (with `dim='Data0D'|'Data1D'|'Data2D'|'DataND'`,
76
+ `labels`, `axes=[Axis(...)]`) emitted through `self.dte_signal` (grab) or `self.dte_signal_temp` (live preview).
77
+
78
+ **Rules that matter**
79
+
80
+ - Plugin code runs in a worker thread. **Never create or touch Qt widgets** from it. Report progress with
81
+ `self.emit_status(ThreadCommand('Update_Status', ['message']))`.
82
+ - Do not block for long inside `grab_data` or a move; use the asynchronous pattern shown in the template and the
83
+ polling that PyMoDAQ already does (`epsilon`).
84
+ - Always release the hardware in `close()`, only if `self.is_master and self.controller is not None` (the init may have
85
+ failed before the controller existed). Leave actuators in a safe state on error.
86
+ - Settings are `pyqtgraph` Parameter dicts (`{'title', 'name', 'type', 'value', ...}`). React to changes in
87
+ `commit_settings(param)` by `param.name()`.
88
+ - Units are `pint`-based. Give every actuator value and every `Axis` a unit; mismatches raise `DataUnitError`.
89
+ What you send to a controller must be expressed in the controller units: use `value.value(self.axis_unit)` in
90
+ `move_abs` / `move_rel`, never `value.value()` (the magnitude in whatever unit the `DataActuator` carries, so 1 cm
91
+ would be sent as 1.0 to a controller in mm). `axis_unit` is the unit of the current axis, `axis_units` the list/dict
92
+ of all the axes of a multiaxes controller.
93
+ - Hardware access code goes in `hardware/`, behind a small wrapper class. This lets the plugin be tested with a
94
+ mock controller and keeps the plugin file readable.
95
+ - Defaults live in `resources/config_template.toml`, not in the code. Read configuration values through
96
+ `GlobalConfig` from `pymodaq_utils.config`: a singleton that wraps the `Config` objects of all packages, e.g.
97
+ `GlobalConfig()('gui', 'style', 'theme')`. Do not parse the toml files yourself, and do not instantiate a `Config`
98
+ class directly (deprecated): PyMoDAQ registers the `Config` of `<your_package>/utils.py` in `GlobalConfig` when it
99
+ discovers the plugin, under the package name without `pymodaq_plugins_`.
100
+
101
+ **Testing behaviour without hardware.** `pymodaq.utils.plugin_testing` (shipped with PyMoDAQ >= 5.3.2) drives a plugin the way PyMoDAQ does, without a GUI:
102
+ `make_actuator` / `make_detector` (instantiate and call `ini_stage` / `ini_detector`), `move_abs_and_wait`,
103
+ `move_rel_and_wait`, `move_home_and_wait`, `grab_and_wait` (return the final position or the `DataToExport`, and fail
104
+ on a timeout) and `assert_units`. `tests/example_mock_plugins.py` shows a fake controller and the smallest actuator,
105
+ multi-axes actuator and 1D detector; `tests/test_plugin_behaviour.py` shows the tests to copy. Write the vendor
106
+ communication behind a wrapper class in `hardware/`, write a fake with the same public methods, and run your real
107
+ plugin class against it (monkeypatch the wrapper class). Targets reach the plugin in the axis unit, as in PyMoDAQ.
108
+
109
+ These tests only check your side of the PyMoDAQ contract (return types, units, signals, shared controller, `close`)
110
+ against a fake that encodes what you believe the driver does. They say nothing about the device: timing, error replies,
111
+ real ranges and units. A passing run is not a hardware validation: never state that a plugin works on an instrument
112
+ that was not used, say what was tested on the real device and what only against a fake.
113
+
114
+ **Legacy patterns to avoid** (older tutorials and models still produce them; `check_plugin` flags several):
115
+ `stage_names` (use `_axis_names`), `_epsilon` (use `_epsilons`), `data_actuator_type = float` (use
116
+ `DataActuatorType.DataActuator`), the group names `multiaxes` and `multi_status` (now `controller` and
117
+ `controller_status`), importing `CustomExt` from `pymodaq.extensions.custom_ext` (use `pymodaq.utils.custom_ext`),
118
+ and the deprecated `CustomApp` / `CustomExt` hooks `setup_docks` and `setup_menu` (use `setup_docks_and_widgets` and
119
+ `setup_menus_and_toolbars`).
120
+
121
+ ## 4. Dashboard extensions (`CustomExt`)
122
+
123
+ - Entry point group `pymodaq.extensions` in `pyproject.toml`; each module in `extensions/` defines
124
+ `EXTENSION_NAME` (menu label), `CLASS_NAME` (class name) and a class derived from `CustomExt`.
125
+ - Import: `from pymodaq.utils.custom_ext import CustomExt`.
126
+ - A plugin extension follows exactly the pattern of the core extensions (`daq_scan`, `sequencer`, `ramping`, data
127
+ mixer). The only difference is `EXTENSION_NAME` and `CLASS_NAME`, which the Dashboard needs to recognise it.
128
+ - **`_quit_fun(self) -> bool` must be overridden and return `True`** (return `False`, after telling the user, while
129
+ the extension is running). The base implementation returns `None`, which prevents closing.
130
+ - **`main()` pattern** (to run the extension alone, same as the core ones):
131
+
132
+ ```python
133
+ def main():
134
+ import sys
135
+ from pymodaq_gui.qt_utils import mkQApp
136
+ from pymodaq.dashboard import load_dashboard_with_arguments
137
+ from pymodaq.utils.gui_utils.loader_utils import create_extension
138
+
139
+ app = mkQApp('My Extension')
140
+ win, dashboard, _ = load_dashboard_with_arguments(show_dashboard=False, load_extension=False)
141
+ win.mainwindow.setVisible(False)
142
+ win_ext, ext = create_extension(dashboard, MyExtension, show_extension=True)
143
+ sys.exit(app.exec())
144
+ ```
145
+
146
+ `load_dashboard_with_arguments` reads the command line (`-x EXPERIMENT_NAME`, `-s STATE_NAME`).
147
+ `create_extension` already shows the window: do not call `win_ext.show()` again, and do not use `create_load_dashboard`
148
+ here.
149
+ - Long-running work (a scan, a sequence, a ramp) goes in an `ExtensionWorker` subclass run in a thread, started and
150
+ stopped from the extension's workflow actions, as in `sequencer.py` and `ramping.py`.
151
+ - `__init__(self, parent: DockArea, dashboard)`: call `super().__init__(parent, dashboard)`, then `self.setup_ui()`.
152
+ - **Experiment and state belong to the Dashboard.** The *experiment* is the list of instruments (actuators and
153
+ detectors) the Dashboard manages and controls; the *state* is the state of that experiment, mostly the values of the
154
+ instruments' settings and of the Dashboard's actuators. An extension must use the managers it gets through its
155
+ `dashboard` argument, as `self.experiment_manager` and `self.state_manager`, and never create its own, nor its own
156
+ save/load of instrument configurations. Override `do_things_after_experiment_set(experiment_name, show_dashboard)`
157
+ to react when an experiment is set (the base class refreshes `self.modules_manager` there), and use
158
+ `create_dashboard_toolbar(...)` to expose the experiment and state actions in your extension.
159
+ - The Dashboard's instruments are reached through `self.modules_manager`. This `ModulesManager` is created anew for
160
+ each extension by the base class `__init__`, so that every extension can handle different instruments: never share
161
+ it between extensions, replace it, or reuse the Dashboard's one. Do not create control modules yourself inside an
162
+ extension, and do not talk to the hardware directly.
163
+ - Implement the lifecycle, in the order `setup_ui` calls it: `setup_docks_and_widgets`, `setup_menus_and_toolbars`
164
+ (only creates the menus and toolbars), `setup_actions` (creates the actions and can put them in those menus and
165
+ toolbars with `add_action(..., menu=..., toolbar=...)`), `connect_things`, then `value_changed`, `quit_fun`. `setup_docks` and `setup_menu` are deprecated. The GUI thread runs
166
+ your code; react to module signals, never poll or sleep.
167
+
168
+ ## 5. Standalone apps (`CustomApp`)
169
+
170
+ Only when no instrument is needed. Same (non-deprecated) lifecycle as above, `parent` is a `DockArea` or `QMainWindow`, and
171
+ `main()` creates the app with `mkQApp`. If instruments become necessary, move the logic to a `CustomExt`.
172
+
173
+ ## 6. Models of the extensions (`models/`)
174
+
175
+ Four Dashboard extensions delegate the part that depends on your experiment to a model class that your package
176
+ provides: PID (`PIDModelGeneric`), Bayesian and Adaptive optimizers (`OptimizerModelGeneric` or `OptimizerModelDefault`),
177
+ DataMixer (`DataMixerModel`) and the Sequencer (`ChoiceModelBase`). Write a model, not an extension, when one of these
178
+ extensions already does the job and only the conversion of the measurements or of the outputs is specific.
179
+
180
+ - Set `models = true` in `[features]`; entry points are generated only then (`pymodaq.models`).
181
+ - Create the `models/` folder at the root of the package, one model per module; the module name is the name shown in
182
+ the extension. The model is the class defined in the module that derives from the base class of the extension. It may
183
+ derive from your own intermediate class, but PID and DataMixer only accepted a direct subclass of the generic base
184
+ before the release that follows 5.3.3, so derive directly from it if you must support older releases. The Sequencer
185
+ is the exception: it does not look at base classes, you register each choice model with
186
+ `@ChoiceModelFactory.register_choice()`.
187
+ - The template ships no model on purpose. Start from the working examples of
188
+ [`pymodaq_plugins_mockexamples`](https://github.com/PyMoDAQ/pymodaq_plugins_mockexamples) (`PIDModelBeamSteering`,
189
+ `PIDModelMockHeater`, `BayesianModelMock`, `DataMixerModelFit`; for the Sequencer, the choice models of
190
+ `pymodaq/extensions/sequencer/models`) and from the page "Models of the extensions" of the PyMoDAQ
191
+ documentation, which lists the methods to implement for each kind. Test a model with the mock actuators and
192
+ detectors of that package.
193
+ - Do not use the old `pid_models` flag nor the word "preset" in new code: a model script that opens the Dashboard loads
194
+ an experiment file.
195
+
196
+ ## 7. Done checklist
197
+
198
+ - [ ] Names (package, files, classes) match and the `template` placeholders are gone.
199
+ - [ ] The `[features]` flags at the top of `pyproject.toml` match the folders you use (`instruments`, `extensions`,
200
+ `models`...): entry points are generated only for the features set to `true` (`check_plugin` warns, PMQ109).
201
+ - [ ] `check_plugin` passes; no `NotImplementedError` or `TODO` left in the files you wrote.
202
+ - [ ] Units set on all actuator values and axes; safe behaviour in `close()` and on errors.
203
+ - [ ] Tests added for new behaviour using a mock controller (no real hardware in CI).
204
+ - [ ] Docstring of each plugin class states: compatible devices, what was tested, PyMoDAQ and OS versions,
205
+ drivers to install.
206
+ - [ ] `README.rst` updated; target PyMoDAQ version stated.
@@ -0,0 +1,3 @@
1
+ # CLAUDE.md
2
+
3
+ Read and follow [AGENTS.md](AGENTS.md): it holds all the guidance for AI coding assistants working in this repository.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pymodaq_plugins_utils
3
- Version: 5.0.7
3
+ Version: 5.3.0
4
4
  Summary: Set of utility methods and classes to interact with instruments
5
5
  Project-URL: Homepage, https://pymodaq.cnrs.fr
6
6
  Project-URL: Documentation , https://pymodaq.cnrs.fr
@@ -43,7 +43,10 @@ Classifier: Topic :: Scientific/Engineering :: Visualization
43
43
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
44
44
  Classifier: Topic :: Software Development :: User Interfaces
45
45
  Requires-Python: >=3.8
46
- Requires-Dist: pymodaq>=5.0.0
46
+ Requires-Dist: pymodaq-data>=5.3.0
47
+ Requires-Dist: pymodaq-gui>=5.3.0
48
+ Requires-Dist: pymodaq-utils>=5.3.1
49
+ Requires-Dist: pymodaq>=5.3.2
47
50
  Provides-Extra: camera
48
51
  Requires-Dist: opencv-python; extra == 'camera'
49
52
  Requires-Dist: pylablib; extra == 'camera'
@@ -23,7 +23,10 @@ camera = [
23
23
  name = "pymodaq_plugins_utils"
24
24
  description = 'Set of utility methods and classes to interact with instruments'
25
25
  dependencies = [
26
- "pymodaq>=5.0.0",
26
+ "pymodaq>=5.3.2",
27
+ "pymodaq_utils>=5.3.1",
28
+ "pymodaq_gui>=5.3.0",
29
+ "pymodaq_data>=5.3.0",
27
30
  #todo: list here all dependencies your package may have
28
31
  ]
29
32
 
@@ -6,13 +6,13 @@ not installed, all functions return empty lists and a warning is logged.
6
6
 
7
7
  Typical usage in a plugin::
8
8
 
9
- from pymodaq_utils.hardware.serial_ports import list_resources
9
+ from pymodaq_plugins_utils.hardware.serial_ports import list_resources
10
10
 
11
11
  ports = list_resources() # e.g. ['/dev/ttyUSB0', 'COM3']
12
12
 
13
13
  After hot-plugging a device, refresh the cache with::
14
14
 
15
- from pymodaq_utils.hardware.serial_ports import invalidate_cache
15
+ from pymodaq_plugins_utils.hardware.serial_ports import invalidate_cache
16
16
  invalidate_cache()
17
17
  """
18
18
  import pymodaq_utils.logger as logger_module
@@ -6,13 +6,13 @@ VISA backend is found, all functions return empty lists and a warning is logged.
6
6
 
7
7
  Typical usage in a plugin::
8
8
 
9
- from pymodaq_utils.hardware.visa import list_serial_resources
9
+ from pymodaq_plugins_utils.hardware.visa import list_serial_resources
10
10
 
11
11
  ports = list_serial_resources() # e.g. ['ASRL/dev/ttyUSB0::INSTR']
12
12
 
13
13
  After hot-plugging a device, refresh the cache with::
14
14
 
15
- from pymodaq_utils.hardware.visa import invalidate_cache
15
+ from pymodaq_plugins_utils.hardware.visa import invalidate_cache
16
16
  invalidate_cache()
17
17
  """
18
18
  import pymodaq_utils.logger as logger_module
@@ -0,0 +1,28 @@
1
+ """Acceptance tests of the plugin, provided by PyMoDAQ (from version 5.3.2).
2
+
3
+ They check the package structure, the entry points, the naming of the instrument modules and classes, the mandatory
4
+ attributes and methods of the plugin classes, and apply the static rules of PyMoDAQ on the sources
5
+ (see https://pymodaq.cnrs.fr/en/latest/developer_folder/instrument_plugins.html#testing-your-plugin).
6
+
7
+ The unfinished parts of the plugin (TODO comments, placeholders) are listed but do not fail the tests, set
8
+ ``fail_on = 'todo'`` before a release. Add your own tests, specific to your instruments, in other files.
9
+ """
10
+ import importlib.util
11
+
12
+ import pytest
13
+
14
+ try:
15
+ available = importlib.util.find_spec('pymodaq.utils.plugin_testing') is not None
16
+ except ModuleNotFoundError: # pymodaq is not installed
17
+ available = False
18
+ if not available:
19
+ pytest.skip('The plugin acceptance checks need PyMoDAQ >= 5.3.2', allow_module_level=True)
20
+
21
+ from pymodaq.utils.plugin_testing import PluginPackageChecks
22
+
23
+
24
+ class TestPlugin(PluginPackageChecks):
25
+ # package_name = 'pymodaq_plugins_xxxx' # only if it cannot be found from the pyproject.toml and the package folder
26
+ fail_on = 'error' # 'error', 'warning' or 'todo' (also fail on the unfinished parts)
27
+ strict_imports = False # True: also fail when a module cannot be imported (missing dependency or driver); the
28
+ # modules of an instrument whose driver cannot be installed on the CI runner are then skipped, not failed
@@ -1,10 +0,0 @@
1
- name: 'Tests'
2
-
3
- on: [push]
4
-
5
- jobs:
6
- call_workflow:
7
- uses: ./.github/workflows/Testbase.yml
8
- with:
9
- python: '3.12'
10
- qt5: 'pyside6'
@@ -1,44 +0,0 @@
1
- name: Base
2
-
3
- on:
4
- workflow_call:
5
- inputs:
6
- python:
7
- required: true
8
- type: string
9
- qt5:
10
- required: true
11
- type: string
12
-
13
- jobs:
14
- build:
15
- runs-on: ubuntu-latest
16
- env:
17
- DISPLAY: ':99.0'
18
- QT_DEBUG_PLUGINS: 1
19
- steps:
20
- - name: Set up Python ${{ inputs.python }}
21
- uses: actions/checkout@v7.0.1
22
- - name: Install dependencies
23
- uses: actions/setup-python@v7.0.0
24
- with:
25
- python-version: ${{ inputs.python }}
26
- - name: Install package
27
- run: |
28
- sudo apt install libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils
29
- python -m pip install --upgrade pip
30
- export QT_DEBUG_PLUGINS=1
31
- pip install flake8 pytest pytest-cov pytest-qt pytest-xdist pytest-xvfb setuptools wheel numpy h5py ${{ inputs.qt5 }} toml
32
- pip install pymodaq pyqt5
33
- pip install -e .
34
- - name: create local pymodaq folder and setting permissions
35
- run: |
36
- sudo mkdir /etc/.pymodaq
37
- sudo chmod uo+rw /etc/.pymodaq
38
- - name: Linting with flake8
39
- run: |
40
- # stop the build if there are Python syntax errors or undefined names
41
- flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=src/pymodaq/resources/QtDesigner_Ressources,docs
42
- - name: Test with pytest
43
- run: |
44
- pytest -n auto
@@ -1,78 +0,0 @@
1
- name: Compatibility with pymodaq (latest release)
2
-
3
- on:
4
- workflow_call:
5
-
6
- pull_request:
7
-
8
- push:
9
- branches:
10
- - '*'
11
-
12
- concurrency:
13
- # github.workflow: name of the workflow
14
- # github.event.pull_request.number || github.ref: pull request number or branch name if not a pull request
15
- group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
16
- # Cancel in-progress runs when a new workflow with the same group name is triggered
17
- cancel-in-progress: true
18
-
19
- jobs:
20
- tests:
21
- continue-on-error: true
22
- strategy:
23
- fail-fast: false
24
- matrix:
25
- os: ["ubuntu-latest", "windows-latest"]
26
- python-version: ["3.9", "3.10", "3.11", "3.12"]
27
- qt-backend: ["pyqt5", "pyqt6", "pyside6"]
28
- runs-on: ${{ matrix.os }}
29
- env:
30
- DISPLAY: ':99'
31
- QT_DEBUG_PLUGINS: 1
32
-
33
- steps:
34
- - name: Set project name environment variable
35
- run: |
36
- echo "plugin_name=$(echo '${{ github.repository }}' | cut -d'/' -f2)" >> $GITHUB_ENV
37
-
38
- - name: Checkout the repo
39
- uses: actions/checkout@v7.0.1
40
-
41
- - name: Set up Python ${{ matrix.python-version }}
42
- uses: actions/setup-python@v7.0.0
43
- with:
44
- python-version: ${{ matrix.python-version }}
45
-
46
- - name: Install dependencies
47
- run: |
48
- python -m pip install --upgrade pip
49
- pip install flake8 pytest pytest-cov pytest-qt pytest-xvfb pytest-xdist setuptools wheel numpy h5py pymodaq ${{ matrix.qt-backend }}
50
- pip install -e .
51
-
52
- # Create folder and set permissions on Ubuntu
53
- - name: Create local pymodaq folder setup env (Linux)
54
- if: runner.os == 'Linux'
55
- run: |
56
- sudo apt update
57
- sudo apt install -y libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-cursor0 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils libgl1 libegl1
58
- export QT_DEBUG_PLUGINS=1
59
- sudo mkdir -p /etc/.pymodaq
60
- sudo chmod uo+rw /etc/.pymodaq
61
- - name: Exporting debug variables (Windows)
62
- if: runner.os == 'Windows'
63
- run: |
64
- set QT_DEBUG_PLUGINS=1
65
-
66
- - name: Compatibility tests with ${{ matrix.os }} ${{ matrix.python-version }} ${{ matrix.qt-backend}}
67
- run: |
68
- pytest -vv -n 1 -k "test_compatibility"
69
-
70
- - name: Upload compatibility report
71
- if: failure()
72
- uses: actions/upload-artifact@v7.0.1
73
- with:
74
- name:
75
- path: 'import_report_tests_${{ env.plugin_name }}_None.txt'
76
- if-no-files-found: error
77
-
78
-
@@ -1,40 +0,0 @@
1
- # This workflow will upload a Python Package using Twine when a release is created
2
- # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3
-
4
- name: Upload Python Package
5
-
6
- on:
7
- release:
8
- types: [created]
9
-
10
- jobs:
11
- deploy:
12
-
13
- runs-on: ubuntu-latest
14
-
15
- steps:
16
- - uses: actions/checkout@v7.0.1
17
- - name: Set up Python
18
- uses: actions/setup-python@v7.0.0
19
- with:
20
- python-version: '3.13'
21
- - name: Install dependencies
22
- run: |
23
- python -m pip install --upgrade pip
24
- pip install hatch hatchling toml twine
25
- - name: Get history and tags for SCM versioning to work
26
- run: |
27
- git branch
28
- git fetch --prune --unshallow
29
- git fetch --depth=1 origin +refs/tags/*:refs/tags/*
30
- hatch version
31
- - name: Build
32
- run: hatch build
33
- - name: Check the build
34
- run: twine check dist/*
35
- - name: publish
36
- env:
37
- HATCH_INDEX_USER: ${{ secrets.PYPI_USERNAME }}
38
- HATCH_INDEX_AUTH: ${{ secrets.PYPI_PASSWORD }}
39
- run: |
40
- hatch publish
@@ -1,24 +0,0 @@
1
- name: GitHub Actions Version Updater
2
-
3
- # Controls when the action will run.
4
- on:
5
- workflow_dispatch:
6
- schedule:
7
- # Automatically run at 00:00 on day-of-month 5.
8
- - cron: '0 0 5 * *'
9
-
10
- jobs:
11
- build:
12
- runs-on: ubuntu-latest
13
-
14
- steps:
15
- - uses: actions/checkout@v7.0.1
16
- with:
17
- # [Required] Access token with `workflow` scope.
18
- token: ${{ secrets.WORKFLOW_SECRET }}
19
-
20
- - name: Run GitHub Actions Version Updater
21
- uses: saadmk11/github-actions-version-updater@v0.9.0
22
- with:
23
- # [Required] Access token with `workflow` scope.
24
- token: ${{ secrets.WORKFLOW_SECRET }}
@@ -1,111 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Created the 17/10/2023
4
-
5
- @author: Sebastien Weber
6
- """
7
- import pytest
8
- from pathlib import Path
9
- import importlib
10
- import pkgutil
11
- from collections.abc import Iterable
12
-
13
- from pymodaq_data import Q_, Unit
14
-
15
-
16
- MANDATORY_MOVE_METHODS = ['ini_attributes', 'get_actuator_value', 'close', 'commit_settings',
17
- 'ini_stage', 'move_abs', 'move_home', 'move_rel', 'stop_motion']
18
- MANDATORY_VIEWER_METHODS = ['ini_attributes', 'grab_data', 'close', 'commit_settings',
19
- 'ini_detector', ]
20
-
21
-
22
- def get_package_name():
23
- here = Path(__file__).parent
24
- package_name = here.parent.stem
25
- return package_name
26
-
27
- def get_move_plugins():
28
- pkg_name = get_package_name()
29
- try:
30
- move_mod = importlib.import_module(f'{pkg_name}.daq_move_plugins')
31
- plugin_list = [mod for mod in [mod[1] for mod in
32
- pkgutil.iter_modules([str(move_mod.path.parent)])]
33
- if 'daq_move_' in mod]
34
- except ModuleNotFoundError:
35
- plugin_list = []
36
- move_mod = None
37
- return plugin_list, move_mod
38
-
39
-
40
- def get_viewer_plugins(dim='0D'):
41
- pkg_name = get_package_name()
42
- try:
43
- viewer_mod = importlib.import_module(f'{pkg_name}.daq_viewer_plugins.plugins_{dim}')
44
-
45
- plugin_list = [mod for mod in [mod[1] for mod in
46
- pkgutil.iter_modules([str(viewer_mod.path.parent)])]
47
- if f'daq_{dim}viewer_' in mod]
48
- except ModuleNotFoundError:
49
- plugin_list = []
50
- viewer_mod = None
51
- return plugin_list, viewer_mod
52
-
53
-
54
- def test_package_name_ok():
55
- assert 'pymodaq_plugins_' in get_package_name()[0:16]
56
-
57
-
58
- def test_imports():
59
- pkg_name = get_package_name()
60
- mod = importlib.import_module(pkg_name)
61
- assert hasattr(mod, 'config')
62
- assert hasattr(mod, '__version__')
63
-
64
-
65
- def test_move_inst_plugins_name():
66
- plugin_list, move_mod = get_move_plugins()
67
- for plug in plugin_list:
68
- name = plug.split('daq_move_')[1]
69
- assert hasattr(getattr(move_mod, plug), f'DAQ_Move_{name}')
70
-
71
-
72
- def test_move_has_mandatory_methods():
73
- plugin_list, move_mod = get_move_plugins()
74
- for plug in plugin_list:
75
- name = plug.split('daq_move_')[1]
76
- klass = getattr(getattr(move_mod, plug), f'DAQ_Move_{name}')
77
- for meth in MANDATORY_MOVE_METHODS:
78
- assert hasattr(klass, meth)
79
-
80
-
81
- def test_move_has_correct_units():
82
- plugin_list, move_mod = get_move_plugins()
83
- for plug in plugin_list:
84
- name = plug.split('daq_move_')[1]
85
- klass = getattr(getattr(move_mod, plug), f'DAQ_Move_{name}')
86
- if not isinstance(klass._controller_units, list):
87
- if isinstance(klass._controller_units, dict):
88
- units = list(klass._controller_units.values())
89
- elif isinstance(klass._controller_units, str):
90
- units = [klass._controller_units]
91
- else:
92
- raise TypeError(f'{klass._controller_units} is an invalid type')
93
- else:
94
- units = klass._controller_units
95
- for unit in units:
96
- Unit(unit) # check if the unit is known from pint
97
-
98
-
99
- @pytest.mark.parametrize('dim', ('0D', '1D', '2D', 'ND'))
100
- def test_viewer_has_mandatory_methods(dim):
101
- plugin_list, mod = get_viewer_plugins(dim)
102
- for plug in plugin_list:
103
- name = plug.split(f'daq_{dim}viewer_')[1]
104
- try:
105
- module = importlib.import_module(f'.{plug}', mod.__package__)
106
- except Exception:
107
- break
108
- klass = getattr(module, f'DAQ_{dim}Viewer_{name}')
109
- for meth in MANDATORY_VIEWER_METHODS:
110
- assert hasattr(klass, meth)
111
-