pymodaq_utils 5.3.1__tar.gz → 5.3.2__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.
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/PKG-INFO +1 -1
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/plugin_checks.py +11 -2
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/plugin_rules.py +119 -2
- pymodaq_utils-5.3.2/src/pymodaq_utils/plugin_testing.py +22 -0
- pymodaq_utils-5.3.1/src/pymodaq_utils/plugin_testing.py +0 -69
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/.gitignore +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/LICENSE +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/README.rst +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/pyproject.toml +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/__init__.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/abstract.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/array_manipulation.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/categorizing.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/config.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/enums.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/environment.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/factory.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/help.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/icon.ico +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/logger.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/math_utils.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/mysocket.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/packages.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/plugin_scaffold.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/resources/__init__.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/resources/config_template.toml +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/resources/hatch_build_plugins.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/serialize/__init__.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/serialize/factory.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/serialize/mysocket.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/serialize/serializer.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/serialize/serializer_legacy.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/serialize/utils.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/singleton.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/splash.png +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/units.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/utils.py +0 -0
- {pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/warnings.py +0 -0
|
@@ -34,7 +34,7 @@ import toml
|
|
|
34
34
|
from pymodaq_utils.utils import get_entrypoints
|
|
35
35
|
|
|
36
36
|
from pymodaq_utils.plugin_rules import (PLACEHOLDER_NAMES_RE, Finding, Severity, check_file_names, check_leftovers,
|
|
37
|
-
check_plugin_source, check_pyproject, colorize, is_valid_unit, package_root,
|
|
37
|
+
check_plugin_source, check_python_code, check_pyproject, colorize, is_valid_unit, package_root,
|
|
38
38
|
project_root, use_color)
|
|
39
39
|
|
|
40
40
|
VIEWER_DIMS = ('0D', '1D', '2D', 'ND')
|
|
@@ -44,7 +44,7 @@ OPTIONAL_ENTRYPOINT_GROUPS = ('pymodaq.extensions', 'pymodaq.models', 'pymodaq.p
|
|
|
44
44
|
|
|
45
45
|
# methods that have to be overridden by the plugin (they are abstract in the base classes, but these are not
|
|
46
46
|
# enforced as the bases are QObjects)
|
|
47
|
-
MANDATORY_MOVE_METHODS = ('ini_stage', 'get_actuator_value', '
|
|
47
|
+
MANDATORY_MOVE_METHODS = ('ini_stage', 'get_actuator_value', 'close') # stop_motion has a default that does nothing
|
|
48
48
|
MANDATORY_VIEWER_METHODS = ('ini_detector', 'grab_data', 'stop', 'close')
|
|
49
49
|
|
|
50
50
|
# files generated by parser generators (pycparser/ply) that can end up within the plugin packages
|
|
@@ -365,6 +365,14 @@ def check_plugin_module(plugin_module: PluginModule) -> CheckResult:
|
|
|
365
365
|
(result.warnings if e.environmental else result.problems).append(str(e))
|
|
366
366
|
if loaded:
|
|
367
367
|
result.problems = check_move_class(klass) if plugin_module.kind == 'move' else check_viewer_class(klass)
|
|
368
|
+
if plugin_module.kind == 'move':
|
|
369
|
+
from pymodaq.control_modules.move_utility_classes import DAQ_Move_base
|
|
370
|
+
if not _is_overridden(klass, DAQ_Move_base, 'stop_motion'):
|
|
371
|
+
result.findings.append(Finding(
|
|
372
|
+
'PMQ311', Severity.WARNING,
|
|
373
|
+
'stop_motion() is not overridden: the Stop of the actuator does nothing',
|
|
374
|
+
'implement it with the stop command of your controller (or ignore this if it cannot be stopped)',
|
|
375
|
+
path, None))
|
|
368
376
|
if path is not None:
|
|
369
377
|
result.findings.extend(check_plugin_source(path, plugin_module.kind, plugin_module.class_name,
|
|
370
378
|
static_fallback=not loaded))
|
|
@@ -444,6 +452,7 @@ def check_package_sources(package: str, project: Optional[Path] = None) -> Check
|
|
|
444
452
|
result.findings.extend(check_pyproject(package, project))
|
|
445
453
|
result.findings.extend(check_file_names(root))
|
|
446
454
|
result.findings.extend(check_leftovers(package, root, project))
|
|
455
|
+
result.findings.extend(check_python_code(root))
|
|
447
456
|
return result
|
|
448
457
|
|
|
449
458
|
|
|
@@ -25,6 +25,7 @@ import importlib.util
|
|
|
25
25
|
import os
|
|
26
26
|
import re
|
|
27
27
|
import sys
|
|
28
|
+
import warnings
|
|
28
29
|
from dataclasses import dataclass
|
|
29
30
|
from enum import Enum
|
|
30
31
|
from pathlib import Path
|
|
@@ -42,9 +43,20 @@ PLACEHOLDER_AUTHORS = ('Name Surname', 'myname@test.fr')
|
|
|
42
43
|
PLACEHOLDER_NAMES_RE = re.compile(r'python_wrapper_file_of_your_instrument|PythonWrapperObjectOfYourInstrument|'
|
|
43
44
|
r'your_method_to_\w+|a_method_or_atttribute_to_check_if_init')
|
|
44
45
|
|
|
45
|
-
MANDATORY_MOVE_METHODS = ('ini_stage', 'get_actuator_value', '
|
|
46
|
+
MANDATORY_MOVE_METHODS = ('ini_stage', 'get_actuator_value', 'close') # stop_motion has a default that does nothing
|
|
46
47
|
MANDATORY_VIEWER_METHODS = ('ini_detector', 'grab_data', 'stop', 'close')
|
|
47
48
|
|
|
49
|
+
# hooks of the plugins that were renamed: PyMoDAQ still calls the old ones, with a deprecation warning
|
|
50
|
+
DEPRECATED_HOOKS = {'check_position': 'get_actuator_value', 'move_Abs': 'move_abs', 'move_Rel': 'move_rel',
|
|
51
|
+
'move_Home': 'move_home'}
|
|
52
|
+
# attributes of the actuators that were renamed
|
|
53
|
+
DEPRECATED_ATTRIBUTES = {'current_position': 'current_value', 'target_position': 'target_value'}
|
|
54
|
+
# names of the groups of settings that were renamed: PyMoDAQ only translates them in settings[...], not in
|
|
55
|
+
# settings.child(...)
|
|
56
|
+
LEGACY_SETTINGS = {'multiaxes': 'controller', 'multi_status': 'controller_status'}
|
|
57
|
+
WINDOWS_ONLY_IMPORTS = {'winreg', 'msvcrt', '_winreg', 'win32api', 'win32com'}
|
|
58
|
+
WINDOWS_PATH_RE = re.compile(r'^[A-Za-z]:[\\/]')
|
|
59
|
+
|
|
48
60
|
# features of [features] in pyproject.toml -> folders (relative to the package) holding the corresponding code
|
|
49
61
|
FEATURE_FOLDERS = {'instruments': ('daq_move_plugins', 'daq_viewer_plugins'),
|
|
50
62
|
'extensions': ('extensions',),
|
|
@@ -194,6 +206,65 @@ def check_file_names(root: Path) -> list[Finding]:
|
|
|
194
206
|
if '__pycache__' not in path.parts and path.stem not in IGNORED_MODULES and not path.stem.isidentifier()]
|
|
195
207
|
|
|
196
208
|
|
|
209
|
+
def _guarded(node: ast.AST, parents: dict) -> bool:
|
|
210
|
+
"""Whether a node is inside a ``try`` with handlers or an ``if`` testing the platform"""
|
|
211
|
+
parent = parents.get(node)
|
|
212
|
+
while parent is not None:
|
|
213
|
+
if isinstance(parent, ast.Try) and parent.handlers:
|
|
214
|
+
return True
|
|
215
|
+
if isinstance(parent, ast.If) and re.search(r'platform|os\.name|\'nt\'|win32|system\(', ast.dump(parent.test)):
|
|
216
|
+
return True
|
|
217
|
+
parent = parents.get(parent)
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def check_python_code(root: Path) -> list[Finding]:
|
|
222
|
+
"""Rules on all the python files of the package (plugins and hardware wrappers)
|
|
223
|
+
|
|
224
|
+
* PMQ315: invalid escape sequence in a string (``"C:\\Program Files"``): a warning of python since 3.6, a
|
|
225
|
+
``SyntaxWarning`` since 3.12, that will be an error
|
|
226
|
+
* PMQ317: code that only works on Windows and is executed when the module is imported (``windll``, ``winreg``,
|
|
227
|
+
path with a drive letter): the plugin cannot be imported, nor listed, on the other systems
|
|
228
|
+
"""
|
|
229
|
+
findings = []
|
|
230
|
+
for path in _python_files(root):
|
|
231
|
+
source = path.read_text(errors='replace')
|
|
232
|
+
with warnings.catch_warnings(record=True) as caught:
|
|
233
|
+
warnings.simplefilter('always')
|
|
234
|
+
try:
|
|
235
|
+
tree = ast.parse(source, filename=str(path))
|
|
236
|
+
except SyntaxError:
|
|
237
|
+
continue # reported by the other rules
|
|
238
|
+
for warning in caught:
|
|
239
|
+
if 'invalid escape sequence' in str(warning.message):
|
|
240
|
+
findings.append(Finding('PMQ315', Severity.WARNING, str(warning.message),
|
|
241
|
+
"use a raw string (r'...') or double the backslash", path, warning.lineno))
|
|
242
|
+
parents = {child: node for node in ast.walk(tree) for child in ast.iter_child_nodes(node)}
|
|
243
|
+
windows = []
|
|
244
|
+
for node in ast.walk(tree):
|
|
245
|
+
what = None
|
|
246
|
+
if isinstance(node, ast.ImportFrom) and node.module == 'ctypes' and any(a.name == 'windll' for a in
|
|
247
|
+
node.names):
|
|
248
|
+
what = 'ctypes.windll'
|
|
249
|
+
elif isinstance(node, ast.Import) and any(a.name.split('.')[0] in WINDOWS_ONLY_IMPORTS
|
|
250
|
+
for a in node.names):
|
|
251
|
+
what = next(a.name for a in node.names if a.name.split('.')[0] in WINDOWS_ONLY_IMPORTS)
|
|
252
|
+
elif isinstance(node, ast.ImportFrom) and (node.module or '').split('.')[0] in WINDOWS_ONLY_IMPORTS:
|
|
253
|
+
what = node.module
|
|
254
|
+
elif isinstance(node, ast.Constant) and isinstance(node.value, str) and WINDOWS_PATH_RE.match(node.value):
|
|
255
|
+
what = f'the path {node.value!r}'
|
|
256
|
+
if what is not None and not _guarded(node, parents):
|
|
257
|
+
windows.append((node.lineno, what))
|
|
258
|
+
if windows:
|
|
259
|
+
line, what = windows[0]
|
|
260
|
+
more = f' (and {len(windows) - 1} more in this file)' if len(windows) > 1 else ''
|
|
261
|
+
findings.append(Finding('PMQ317', Severity.WARNING, f'only works on Windows: {what}{more}',
|
|
262
|
+
'import it lazily, in the method that needs it, or guard it with '
|
|
263
|
+
'sys.platform, so that the plugin can be imported on the other systems',
|
|
264
|
+
path, line))
|
|
265
|
+
return findings
|
|
266
|
+
|
|
267
|
+
|
|
197
268
|
# -------------------------------------------------------------------------------------------------------------------
|
|
198
269
|
# PMQ1xx: packaging
|
|
199
270
|
# -------------------------------------------------------------------------------------------------------------------
|
|
@@ -406,13 +477,16 @@ def check_plugin_source(path: Path, kind: str, class_name: str, static_fallback:
|
|
|
406
477
|
methods = {n.name: n for n in cls.body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))}
|
|
407
478
|
if kind == 'move':
|
|
408
479
|
findings.extend(_check_move_source(path, cls, attrs, static_fallback))
|
|
480
|
+
findings.extend(_check_deprecated_api(path, tree, methods))
|
|
409
481
|
mandatory = MANDATORY_MOVE_METHODS
|
|
410
482
|
else:
|
|
411
483
|
mandatory = MANDATORY_VIEWER_METHODS
|
|
484
|
+
findings.extend(_check_legacy_settings(path, tree))
|
|
412
485
|
simple_bases = all(b in ('DAQ_Move_base', 'DAQ_Viewer_base') for b in _base_names(cls))
|
|
413
486
|
if static_fallback and simple_bases:
|
|
414
487
|
for meth in mandatory:
|
|
415
|
-
if
|
|
488
|
+
# the base class still calls check_position if get_actuator_value is not defined (see PMQ312)
|
|
489
|
+
if meth not in methods and not (meth == 'get_actuator_value' and 'check_position' in methods):
|
|
416
490
|
findings.append(Finding('PMQ306', Severity.ERROR, f'{class_name} should define {meth}()',
|
|
417
491
|
'implement it (see the template)', path, cls.lineno))
|
|
418
492
|
init = methods.get('ini_stage' if kind == 'move' else 'ini_detector')
|
|
@@ -461,6 +535,48 @@ def _check_move_source(path: Path, cls: ast.ClassDef, attrs: dict, static_fallba
|
|
|
461
535
|
return findings
|
|
462
536
|
|
|
463
537
|
|
|
538
|
+
def _check_deprecated_api(path: Path, tree: ast.Module, methods: dict) -> list[Finding]:
|
|
539
|
+
"""PMQ312-314: parts of the API of an actuator that were renamed or changed"""
|
|
540
|
+
findings = []
|
|
541
|
+
for old, new in DEPRECATED_HOOKS.items():
|
|
542
|
+
if old in methods:
|
|
543
|
+
findings.append(Finding('PMQ312', Severity.WARNING, f'{old}() is deprecated, it is still called but with '
|
|
544
|
+
f'a warning', f'rename it {new}()', path, methods[old].lineno))
|
|
545
|
+
for attr, new in DEPRECATED_ATTRIBUTES.items():
|
|
546
|
+
uses = [n for n in ast.walk(tree) if isinstance(n, ast.Attribute) and n.attr == attr and
|
|
547
|
+
isinstance(n.value, ast.Name) and n.value.id == 'self']
|
|
548
|
+
if uses:
|
|
549
|
+
more = f' ({len(uses)} times in this file)' if len(uses) > 1 else ''
|
|
550
|
+
findings.append(Finding('PMQ313', Severity.WARNING, f"'{attr}' is deprecated{more}", f"use '{new}'",
|
|
551
|
+
path, min(n.lineno for n in uses)))
|
|
552
|
+
# comon_parameters of the actuators is a function since pymodaq 5 (it was a list), the one of the viewers a list
|
|
553
|
+
names = {a.asname or a.name for n in ast.walk(tree) if isinstance(n, ast.ImportFrom) and
|
|
554
|
+
(n.module or '').endswith('move_utility_classes') for a in n.names if a.name == 'comon_parameters'}
|
|
555
|
+
if names:
|
|
556
|
+
called = {id(n.func) for n in ast.walk(tree) if isinstance(n, ast.Call)}
|
|
557
|
+
for node in ast.walk(tree):
|
|
558
|
+
if isinstance(node, ast.Name) and node.id in names and isinstance(node.ctx, ast.Load) and \
|
|
559
|
+
id(node) not in called:
|
|
560
|
+
findings.append(Finding('PMQ314', Severity.ERROR, "'comon_parameters' is used as a list but it is "
|
|
561
|
+
"a function", "call it: comon_parameters_fun(is_multiaxes, axis_names=..., "
|
|
562
|
+
"epsilon=...), or comon_parameters(epsilon)", path, node.lineno))
|
|
563
|
+
return findings
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def _check_legacy_settings(path: Path, tree: ast.Module) -> list[Finding]:
|
|
567
|
+
"""PMQ316: names of groups of settings that were renamed"""
|
|
568
|
+
hits = [n for n in ast.walk(tree) if isinstance(n, ast.Constant) and n.value in LEGACY_SETTINGS]
|
|
569
|
+
if not hits:
|
|
570
|
+
return []
|
|
571
|
+
first = min(hits, key=lambda n: n.lineno)
|
|
572
|
+
more = f' ({len(hits)} times in this file)' if len(hits) > 1 else ''
|
|
573
|
+
return [Finding('PMQ316', Severity.WARNING, f"settings group '{first.value}' was renamed "
|
|
574
|
+
f"'{LEGACY_SETTINGS[first.value]}'{more}",
|
|
575
|
+
"PyMoDAQ only translates the old names in settings[...], not in settings.child(...): use the "
|
|
576
|
+
"new ones", path,
|
|
577
|
+
first.lineno)]
|
|
578
|
+
|
|
579
|
+
|
|
464
580
|
def check_package_sources(package: str) -> list[Finding]:
|
|
465
581
|
"""All the static rules on an installed (or editable) plugin package: packaging, leftovers and plugin classes"""
|
|
466
582
|
from pymodaq_utils.plugin_checks import find_plugin_modules # no import cycle at module level
|
|
@@ -472,6 +588,7 @@ def check_package_sources(package: str) -> list[Finding]:
|
|
|
472
588
|
findings = check_pyproject(package, project) if project is not None else []
|
|
473
589
|
findings.extend(check_file_names(root))
|
|
474
590
|
findings.extend(check_leftovers(package, root, project))
|
|
591
|
+
findings.extend(check_python_code(root))
|
|
475
592
|
for mod in find_plugin_modules(package):
|
|
476
593
|
file = Path(importlib.util.find_spec(mod.import_path).origin)
|
|
477
594
|
findings.extend(check_plugin_source(file, mod.kind, mod.class_name))
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Moved to :mod:`pymodaq.utils.plugin_testing`, with the tools to test the behaviour of a plugin.
|
|
2
|
+
|
|
3
|
+
The pytest mixin was in ``pymodaq_utils`` to avoid the initialization of ``pymodaq`` when importing it. As it is used
|
|
4
|
+
in tests that import the plugins (hence ``pymodaq``) anyway, it now sits with the other plugin testing tools. This
|
|
5
|
+
module is kept for the plugin repositories written for PyMoDAQ 5.3.1 and will be removed in a later version.
|
|
6
|
+
|
|
7
|
+
.. versionadded:: 5.3.1
|
|
8
|
+
.. deprecated:: 5.3.2
|
|
9
|
+
Use ``from pymodaq.utils.plugin_testing import PluginPackageChecks``.
|
|
10
|
+
"""
|
|
11
|
+
from pymodaq_utils.warnings import deprecation_msg
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def __getattr__(name):
|
|
15
|
+
if name == 'PluginPackageChecks':
|
|
16
|
+
try:
|
|
17
|
+
from pymodaq.utils.plugin_testing import PluginPackageChecks
|
|
18
|
+
except ImportError as e: # pragma: no cover
|
|
19
|
+
raise ImportError('PluginPackageChecks needs pymodaq >= 5.3.2: pymodaq.utils.plugin_testing') from e
|
|
20
|
+
deprecation_msg('pymodaq_utils.plugin_testing is deprecated, use pymodaq.utils.plugin_testing instead')
|
|
21
|
+
return PluginPackageChecks
|
|
22
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
"""Pytest mixin applying the acceptance checks of :mod:`pymodaq_utils.plugin_checks` to a plugin package.
|
|
2
|
-
|
|
3
|
-
It is in ``pymodaq_utils`` (and not in ``pymodaq``) so that importing it does not start the whole PyMoDAQ
|
|
4
|
-
initialization. Use it in a test module of a plugin repository::
|
|
5
|
-
|
|
6
|
-
from pymodaq_utils.plugin_testing import PluginPackageChecks
|
|
7
|
-
|
|
8
|
-
class TestMyPlugin(PluginPackageChecks):
|
|
9
|
-
package_name = 'pymodaq_plugins_myinstrument' # optional, found from the package folder if omitted
|
|
10
|
-
fail_on = 'error' # or 'warning', 'todo'
|
|
11
|
-
strict_imports = True
|
|
12
|
-
|
|
13
|
-
.. versionadded:: 5.3.1
|
|
14
|
-
"""
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from typing import Optional
|
|
17
|
-
|
|
18
|
-
import pytest
|
|
19
|
-
|
|
20
|
-
from pymodaq_utils.plugin_checks import (VIEWER_DIMS, PluginModule, check_package_layout, check_package_sources,
|
|
21
|
-
check_plugin_module, find_plugin_modules, guess_package_name)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class PluginPackageChecks:
|
|
25
|
-
"""Mixin to subclass in a test module of a plugin repository, see the module documentation.
|
|
26
|
-
|
|
27
|
-
Test classes are parametrized per plugin module so that every failure is reported individually.
|
|
28
|
-
"""
|
|
29
|
-
package_name: Optional[str] = None
|
|
30
|
-
fail_on = 'error' # 'error', 'warning' or 'todo' (the unfinished parts: TODO comments, placeholders...): the
|
|
31
|
-
# findings of the static rules of this level and above fail the tests
|
|
32
|
-
strict_imports = False # if True, a module that cannot be imported because of a missing third party module or
|
|
33
|
-
# SDK fails instead of being skipped (recommended in the CI of a plugin as its dependencies are installed there)
|
|
34
|
-
|
|
35
|
-
@pytest.fixture
|
|
36
|
-
def package(self, request) -> str:
|
|
37
|
-
return self.package_name or guess_package_name(Path(request.fspath).parent)
|
|
38
|
-
|
|
39
|
-
def pytest_generate_tests(self, metafunc):
|
|
40
|
-
if 'plugin_module' not in metafunc.fixturenames:
|
|
41
|
-
return
|
|
42
|
-
package = self.package_name or guess_package_name(Path(str(metafunc.definition.fspath)).parent)
|
|
43
|
-
kinds = {'move': ('move',), 'viewer': VIEWER_DIMS}
|
|
44
|
-
wanted = metafunc.function.__name__
|
|
45
|
-
modules = [m for m in find_plugin_modules(package)
|
|
46
|
-
if m.kind in (kinds['move'] if '_move_' in wanted else kinds['viewer'])]
|
|
47
|
-
metafunc.parametrize('plugin_module', modules, ids=str)
|
|
48
|
-
|
|
49
|
-
def test_package_layout(self, package):
|
|
50
|
-
problems = check_package_layout(package)
|
|
51
|
-
assert not problems, '\n'.join(problems)
|
|
52
|
-
|
|
53
|
-
def test_move_plugin(self, plugin_module):
|
|
54
|
-
self._assert_module_ok(plugin_module)
|
|
55
|
-
|
|
56
|
-
def test_viewer_plugin(self, plugin_module):
|
|
57
|
-
self._assert_module_ok(plugin_module)
|
|
58
|
-
|
|
59
|
-
def test_package_sources(self, package):
|
|
60
|
-
result = check_package_sources(package)
|
|
61
|
-
messages = result.failing(self.fail_on)
|
|
62
|
-
assert not messages, '\n'.join(messages)
|
|
63
|
-
|
|
64
|
-
def _assert_module_ok(self, plugin_module: PluginModule):
|
|
65
|
-
result = check_plugin_module(plugin_module)
|
|
66
|
-
messages = result.failing(self.fail_on, self.strict_imports)
|
|
67
|
-
assert not messages, '\n'.join(messages)
|
|
68
|
-
if result.warnings:
|
|
69
|
-
pytest.skip('; '.join(result.warnings))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/resources/config_template.toml
RENAMED
|
File without changes
|
{pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/resources/hatch_build_plugins.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pymodaq_utils-5.3.1 → pymodaq_utils-5.3.2}/src/pymodaq_utils/serialize/serializer_legacy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|