pymodaq_utils 5.3.7__tar.gz → 5.3.9__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 (45) hide show
  1. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/PKG-INFO +1 -1
  2. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/abstract.py +6 -2
  3. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/config.py +4 -3
  4. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/enums.py +12 -9
  5. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/math_utils.py +1 -1
  6. pymodaq_utils-5.3.9/src/pymodaq_utils/serialize/__init__.py +0 -0
  7. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/utils.py +97 -13
  8. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/.gitignore +0 -0
  9. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/LICENSE +0 -0
  10. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/README.rst +0 -0
  11. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/pyproject.toml +0 -0
  12. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/__init__.py +0 -0
  13. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/array_manipulation.py +0 -0
  14. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/categorizing.py +0 -0
  15. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/environment.py +0 -0
  16. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/factory.py +0 -0
  17. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/help.py +0 -0
  18. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/icon.ico +0 -0
  19. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/logger.py +0 -0
  20. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/mysocket.py +0 -0
  21. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/packages.py +0 -0
  22. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/plugin_checks.py +0 -0
  23. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/plugin_rules.py +0 -0
  24. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/plugin_scaffold.py +0 -0
  25. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/plugin_testing.py +0 -0
  26. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/plugin_tools/__init__.py +0 -0
  27. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/plugin_tools/checks.py +0 -0
  28. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/plugin_tools/contract.py +0 -0
  29. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/plugin_tools/extension_checks.py +0 -0
  30. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/plugin_tools/platforms.py +0 -0
  31. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/plugin_tools/rules.py +0 -0
  32. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/plugin_tools/scaffold.py +0 -0
  33. /pymodaq_utils-5.3.7/src/pymodaq_utils/resources/__init__.py → /pymodaq_utils-5.3.9/src/pymodaq_utils/py.typed +0 -0
  34. {pymodaq_utils-5.3.7/src/pymodaq_utils/serialize → pymodaq_utils-5.3.9/src/pymodaq_utils/resources}/__init__.py +0 -0
  35. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/resources/config_template.toml +0 -0
  36. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/resources/hatch_build_plugins.py +0 -0
  37. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/serialize/factory.py +0 -0
  38. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/serialize/mysocket.py +0 -0
  39. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/serialize/serializer.py +0 -0
  40. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/serialize/serializer_legacy.py +0 -0
  41. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/serialize/utils.py +0 -0
  42. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/singleton.py +0 -0
  43. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/splash.png +0 -0
  44. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/units.py +0 -0
  45. {pymodaq_utils-5.3.7 → pymodaq_utils-5.3.9}/src/pymodaq_utils/warnings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pymodaq_utils
3
- Version: 5.3.7
3
+ Version: 5.3.9
4
4
  Summary: Modular Data Acquisition with Python
5
5
  Project-URL: Homepage, http://pymodaq.cnrs.fr
6
6
  Project-URL: Source, https://github.com/PyMoDAQ/PyMoDAQ
@@ -10,7 +10,7 @@ See https://stackoverflow.com/a/50381071/8090831
10
10
 
11
11
  from abc import ABCMeta as NativeABCMeta
12
12
  from abc import abstractmethod
13
- from typing import cast, Any, Callable, TypeVar
13
+ from typing import cast, Any, Callable, Optional, TypeVar
14
14
 
15
15
 
16
16
  R = TypeVar('R')
@@ -20,7 +20,11 @@ class DummyAttribute:
20
20
  pass
21
21
 
22
22
 
23
- def abstract_attribute(obj: Callable[[Any], R] = None) -> R:
23
+ def abstract_attribute(obj: Optional[Callable[[Any], R]] = None) -> R:
24
+ """Declare an attribute that subclasses have to define, checked when instantiating (see ABCMeta)
25
+
26
+ Use it as a decorator on a property or call it without argument: ``name = abstract_attribute()``.
27
+ """
24
28
  _obj = cast(Any, obj)
25
29
  if obj is None:
26
30
  _obj = DummyAttribute()
@@ -10,7 +10,7 @@ from os import environ
10
10
  import sys
11
11
  import datetime
12
12
  from pathlib import Path
13
- from typing import Union, Dict, TypeVar, Any, List, TYPE_CHECKING, Callable, Type
13
+ from typing import Optional, Union, Dict, TypeVar, Any, List, TYPE_CHECKING, Callable, Type
14
14
  from typing import Iterable as IterableType
15
15
 
16
16
  from pymodaq_utils import warnings
@@ -209,8 +209,8 @@ def check_config(config_base: dict, config_local: dict):
209
209
  return status
210
210
 
211
211
 
212
- def copy_template_config(config_file_name: str = 'config', source_path: Union[Path, str] = None,
213
- dest_path: Union[Path, str] = None):
212
+ def copy_template_config(config_file_name: str = 'config', source_path: Optional[Union[Path, str]] = None,
213
+ dest_path: Optional[Union[Path, str]] = None):
214
214
  """Get a toml file path and copy it
215
215
 
216
216
  the destination is made of a given folder path (or the system-wide local path by default) and the config_file_name
@@ -233,6 +233,7 @@ def copy_template_config(config_file_name: str = 'config', source_path: Union[Pa
233
233
  """
234
234
  if dest_path is None:
235
235
  dest_path = get_set_config_dir('config')
236
+ dest_path = Path(dest_path)
236
237
 
237
238
  file_name = Path(config_file_name).stem # remove eventual extensions
238
239
  file_name += '.toml'
@@ -1,16 +1,15 @@
1
- import platform
2
- from packaging.version import Version
1
+ import sys
3
2
 
4
- if Version(platform.python_version()) < Version('3.11'):
5
- from strenum import StrEnum # noqa # pylint: disable=unused-import
3
+ if sys.version_info < (3, 11):
4
+ from strenum import StrEnum as _StrEnum
6
5
  else:
7
- from enum import StrEnum # noqa # pylint: disable=unused-import
6
+ from enum import StrEnum as _StrEnum
8
7
 
9
8
  from enum import Enum, auto # noqa # pylint: disable=unused-import
10
- from typing import List, Union
9
+ from typing import List, Type, TypeVar, Union
11
10
 
12
11
 
13
- class StrEnum(StrEnum): #to be imported in other modules
12
+ class StrEnum(_StrEnum): #to be imported in other modules
14
13
  @classmethod
15
14
  def names(cls) -> List[str]:
16
15
  """Returns all the names of the enum"""
@@ -60,7 +59,10 @@ class BaseEnum(Enum):
60
59
  return super().__eq__(other)
61
60
 
62
61
 
63
- def enum_checker(enum: BaseEnum, item: Union[BaseEnum, str]):
62
+ EnumT = TypeVar('EnumT', bound=BaseEnum)
63
+
64
+
65
+ def enum_checker(enum: Type[EnumT], item: Union[EnumT, str]) -> EnumT:
64
66
  """Check if the item parameter is a valid enum or at least one valid string name of the enum
65
67
 
66
68
  If a string, transforms it to a valid enum (case not important)
@@ -73,7 +75,8 @@ def enum_checker(enum: BaseEnum, item: Union[BaseEnum, str]):
73
75
 
74
76
  Returns
75
77
  -------
76
- BaseEnum class or one of its derivated class
78
+ BaseEnum
79
+ The member of the ``enum`` class corresponding to item
77
80
  """
78
81
 
79
82
  if not isinstance(item, enum):
@@ -174,7 +174,7 @@ def linspace_step_N(start, step, Npts):
174
174
  return linspace_step(start, stop, step)
175
175
 
176
176
 
177
- def find_index(x, threshold: Union[Number, List[Number]]) -> List[tuple]:
177
+ def find_index(x, threshold: Union[float, Number, List[Union[float, Number]]]) -> List[tuple]:
178
178
  """find_index finds the index ix such that x(ix) is the closest from threshold
179
179
 
180
180
  Parameters
@@ -9,7 +9,7 @@ import time
9
9
  from packaging import version as version_mod
10
10
  from pathlib import Path
11
11
  import traceback
12
- from typing import Any, cast, List, Optional, Tuple
12
+ from typing import Any, cast, List, Optional, Sequence, Tuple, Union
13
13
 
14
14
  import numpy as np
15
15
 
@@ -35,7 +35,8 @@ ser_factory = SerializableFactory()
35
35
  config = Config()
36
36
 
37
37
 
38
- def is_64bits():
38
+ def is_64bits() -> bool:
39
+ """Return True if the running Python interpreter is a 64 bits one"""
39
40
  return sys.maxsize > 2**32
40
41
 
41
42
 
@@ -59,25 +60,41 @@ def get_version(package_name='pymodaq'):
59
60
 
60
61
 
61
62
  class JsonConverter:
63
+ """Convert simple python objects to and from a json string
64
+
65
+ The json string holds the module, type and ``repr`` of the object. Only the types listed in
66
+ :meth:`trusted_types` are rebuilt when converting back.
67
+ """
62
68
  def __init__(self):
63
69
  super().__init__()
64
70
 
65
71
  @classmethod
66
- def trusted_types(cls):
72
+ def trusted_types(cls) -> list[str]:
73
+ """Names of the types :meth:`json2object` is allowed to rebuild"""
67
74
  return ['float', 'int', 'str', 'datetime', 'date', 'time', 'tuple', 'list', 'bool', 'bytes',
68
75
  'float64']
69
76
 
70
77
  @classmethod
71
- def istrusted(cls, type_name):
78
+ def istrusted(cls, type_name: str) -> bool:
79
+ """Return True if ``type_name`` is one of the :meth:`trusted_types`"""
72
80
  return type_name in cls.trusted_types()
73
81
 
74
82
  @classmethod
75
- def object2json(cls, obj):
83
+ def object2json(cls, obj: Any) -> str:
84
+ """Return a json string with the module, type name and repr of ``obj``"""
76
85
  dic = dict(module=type(obj).__module__, type=type(obj).__name__, data=repr(obj))
77
86
  return json.dumps(dic)
78
87
 
79
88
  @classmethod
80
- def json2object(cls, jsonstring):
89
+ def json2object(cls, jsonstring: Any) -> Any:
90
+ """Rebuild an object from the json string created by :meth:`object2json`
91
+
92
+ Returns
93
+ -------
94
+ Any
95
+ The object if its type is trusted, otherwise the decoded dict. If ``jsonstring`` cannot be
96
+ decoded, it is returned unchanged.
97
+ """
81
98
  try:
82
99
  dic = json.loads(jsonstring)
83
100
  if isinstance(dic, dict):
@@ -105,7 +122,8 @@ def capitalize(string, Nfirst=1):
105
122
  return string[:Nfirst].upper() + string[Nfirst:]
106
123
 
107
124
 
108
- def uncapitalize(string, Nfirst=1):
125
+ def uncapitalize(string: str, Nfirst: int = 1) -> str:
126
+ """Return the same string but with its first ``Nfirst`` letters in lower case"""
109
127
  return string[:Nfirst].lower() + string[Nfirst:]
110
128
 
111
129
 
@@ -167,6 +185,7 @@ class ThreadCommand(SerializableBase):
167
185
 
168
186
  @staticmethod
169
187
  def serialize(obj: "ThreadCommand") -> bytes: # type: ignore[override]
188
+ """Serialize the command, attribute, args and kwargs of a ThreadCommand into bytes"""
170
189
  serialize_factory = SerializableFactory()
171
190
  byte_string = b""
172
191
  byte_string += serialize_factory.get_apply_serializer(obj.command)
@@ -177,6 +196,13 @@ class ThreadCommand(SerializableBase):
177
196
 
178
197
  @staticmethod
179
198
  def deserialize(bytes_str: bytes) -> Tuple["ThreadCommand", bytes]:
199
+ """Rebuild a ThreadCommand from bytes created by :meth:`serialize`
200
+
201
+ Returns
202
+ -------
203
+ tuple
204
+ The ThreadCommand and the remaining bytes that were not consumed.
205
+ """
180
206
  serialize_factory = SerializableFactory()
181
207
  command, remaining = cast(
182
208
  Tuple[str, bytes],
@@ -219,6 +245,10 @@ def ensure_ndarray(data):
219
245
 
220
246
 
221
247
  def recursive_find_files_extension(ini_path, ext, paths=[]):
248
+ """Collect the paths of all files with the extension ``ext`` (without dot) below ``ini_path``
249
+
250
+ Note that ``paths`` is shared between calls when not given (mutable default): pass a new list.
251
+ """
222
252
  with os.scandir(ini_path) as it:
223
253
  for entry in it:
224
254
  if os.path.splitext(entry.name)[1][1:] == ext and entry.is_file():
@@ -230,6 +260,11 @@ def recursive_find_files_extension(ini_path, ext, paths=[]):
230
260
 
231
261
  def recursive_find_files(ini_path, exp='make_enum', paths=[],
232
262
  filters=['build']):
263
+ """Collect the paths of all files below ``ini_path`` whose name (without extension) contains ``exp``
264
+
265
+ Paths containing one of the ``filters`` strings are skipped. As in
266
+ :func:`recursive_find_files_extension`, pass a new list as ``paths``.
267
+ """
233
268
  for child in Path(ini_path).iterdir():
234
269
  if child.is_dir():
235
270
  recursive_find_files(child, exp, paths, filters)
@@ -243,7 +278,13 @@ def recursive_find_files(ini_path, exp='make_enum', paths=[],
243
278
  def recursive_find_expr_in_files(ini_path, exp='make_enum', paths=[],
244
279
  filters=['.git', '.idea', '__pycache__', 'build', 'egg', 'documentation', '.tox'],
245
280
  replace=False, replace_str=''):
281
+ """Search the text ``exp`` in the files below ``ini_path``
246
282
 
283
+ Each match is appended to ``paths`` as ``[file path, line index, line]``. Paths containing one of the
284
+ ``filters`` strings are skipped and unreadable files are ignored. If ``replace`` is True the matches
285
+ are replaced in the files by ``replace_str``. As in :func:`recursive_find_files_extension`, pass a new
286
+ list as ``paths``.
287
+ """
247
288
  for child in Path(ini_path).iterdir():
248
289
  if not any(filt in str(child) for filt in filters):
249
290
  if child.is_dir():
@@ -271,6 +312,10 @@ def recursive_find_expr_in_files(ini_path, exp='make_enum', paths=[],
271
312
 
272
313
 
273
314
  def count_lines(ini_path, count=0, filters=['lextab', 'yacctab','pycache', 'pyc']):
315
+ """Count the lines of the python files below ``ini_path``, starting from ``count``
316
+
317
+ The names of the files skipped because of ``filters`` are printed.
318
+ """
274
319
  # if Path(ini_path).is_file():
275
320
  # with Path(ini_path).open('r') as f:
276
321
  # count += len(f.readlines())
@@ -320,6 +365,16 @@ def rint(x):
320
365
 
321
366
 
322
367
  def elt_as_first_element(elt_list, match_word='Mock'):
368
+ """Return a list of str with the first one containing ``match_word`` moved to the front
369
+
370
+ The first element is kept in front if none matches. Note that the matching element is removed from
371
+ ``elt_list`` itself, which is modified.
372
+
373
+ Raises
374
+ ------
375
+ TypeError
376
+ If ``elt_list`` is not an iterable of str.
377
+ """
323
378
  if not hasattr(elt_list, '__iter__'):
324
379
  raise TypeError('elt_list must be an iterable')
325
380
  if elt_list:
@@ -340,6 +395,13 @@ def elt_as_first_element(elt_list, match_word='Mock'):
340
395
 
341
396
 
342
397
  def elt_as_first_element_dicts(elt_list, match_word='Mock', key='name'):
398
+ """Same as :func:`elt_as_first_element` for a list of dicts, matching ``match_word`` in ``elt[key]``
399
+
400
+ Raises
401
+ ------
402
+ TypeError
403
+ If ``elt_list`` is not an iterable of dict.
404
+ """
343
405
  if not hasattr(elt_list, '__iter__'):
344
406
  raise TypeError('elt_list must be an iterable')
345
407
  if elt_list:
@@ -386,7 +448,7 @@ def find_object_if_matched_attr_name_val(obj, attr_name, attr_value):
386
448
  return False
387
449
 
388
450
 
389
- def find_objects_in_list_from_attr_name_val(objects: List[object], attr_name: str,
451
+ def find_objects_in_list_from_attr_name_val(objects: Sequence[Any], attr_name: str,
390
452
  attr_value: object, return_first=True):
391
453
  """ lookup within a list of objects. Look for the objects within the list which has the correct attribute name,
392
454
  value pair
@@ -504,13 +566,14 @@ def get_entrypoints(group='pymodaq.plugins') -> List[metadata.EntryPoint]:
504
566
  group: str
505
567
  the name of the group
506
568
  """
569
+ discovered_entrypoints: Any
507
570
  try:
508
571
  discovered_entrypoints = metadata.entry_points(group=group)
509
572
  except TypeError:
510
573
  try:
511
574
  discovered_entrypoints = metadata.entry_points().select(group=group)
512
575
  except AttributeError:
513
- discovered_entrypoints = metadata.entry_points().get(group, [])
576
+ discovered_entrypoints = metadata.entry_points().get(group, []) # type: ignore[attr-defined] # Python < 3.10
514
577
  if isinstance(discovered_entrypoints, tuple): # API for python > 3.8
515
578
  discovered_entrypoints = list(discovered_entrypoints)
516
579
  if not isinstance(discovered_entrypoints, list):
@@ -518,7 +581,8 @@ def get_entrypoints(group='pymodaq.plugins') -> List[metadata.EntryPoint]:
518
581
  return discovered_entrypoints
519
582
 
520
583
 
521
- def check_vals_in_iterable(iterable1, iterable2):
584
+ def check_vals_in_iterable(iterable1, iterable2) -> None:
585
+ """Assert that two iterables (list, tuple, ndarray...) have the same length and equal values"""
522
586
  assert len(iterable1) == len(iterable2)
523
587
  iterable1 = list(iterable1) # so the assertion below is valid for any kind of iterable, list, tuple, ndarray...
524
588
  iterable2 = list(iterable2)
@@ -582,7 +646,15 @@ def zeros_aligned(n, align, dtype=np.uint32):
582
646
  # ########################
583
647
  # #File management
584
648
 
585
- def get_new_file_name(base_path: Path, base_name: str = 'tttr_data'):
649
+ def get_new_file_name(base_path: Union[Path, str], base_name: str = 'tttr_data') -> Tuple[str, Path]:
650
+ """Get the name of a new file in the folder ``<base_path>/<year>/<date>`` (created if needed)
651
+
652
+ Returns
653
+ -------
654
+ tuple
655
+ The file name without extension, ``<base_name>_NNN`` with NNN the index following the last existing
656
+ file starting with ``base_name`` in the folder, and the path of the folder.
657
+ """
586
658
  if isinstance(base_path, str):
587
659
  base_path = Path(base_path)
588
660
 
@@ -614,16 +686,28 @@ def get_module_path(module_name: str) -> Path:
614
686
  """ Get Path of a given module represented by its name
615
687
 
616
688
  You get the name usually with the dunder __module__ on an object or class
689
+
690
+ Raises
691
+ ------
692
+ TypeError
693
+ If the module has no file (namespace package or built-in module)
617
694
  """
618
695
  module = sys.modules[module_name]
696
+ if module.__file__ is None:
697
+ raise TypeError(f'The module {module_name} has no file (namespace package or built-in module)')
619
698
  return Path(module.__file__)
620
699
 
621
700
  def format_dir_path(path: Path) -> str:
622
- dir_trailing_symbol = '\\' if sys.platform == 'win32' else '/'
623
- return f'{path}{dir_trailing_symbol if path.is_dir() else ""}'
701
+ """Return ``path`` as a str, with the separator of the OS appended if it is an existing folder"""
702
+ dir_trailing_symbol = '\\' if sys.platform == 'win32' else '/'
703
+ return f'{path}{dir_trailing_symbol if path.is_dir() else ""}'
624
704
 
625
705
 
626
706
  def read_binary_and_deserialize(fname: Path) -> list[SerializableBase]:
707
+ """Deserialize all the objects stored one after the other in a binary file
708
+
709
+ Returns an empty list if the file does not exist.
710
+ """
627
711
  if not fname.exists():
628
712
  return []
629
713
  with open(fname, 'rb') as file:
File without changes
File without changes
File without changes