jetpytools 2.2.0__tar.gz → 2.2.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.
Files changed (39) hide show
  1. {jetpytools-2.2.0 → jetpytools-2.2.2}/PKG-INFO +2 -2
  2. {jetpytools-2.2.0 → jetpytools-2.2.2}/README.md +1 -1
  3. jetpytools-2.2.2/jetpytools/_version.py +2 -0
  4. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/enums/base.py +8 -5
  5. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/enums/other.py +2 -1
  6. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/exceptions/base.py +8 -6
  7. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/exceptions/file.py +8 -0
  8. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/exceptions/module.py +4 -3
  9. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/functions/funcs.py +29 -23
  10. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/functions/normalize.py +21 -20
  11. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/types/file.py +17 -15
  12. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/types/utils.py +52 -29
  13. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/utils/file.py +9 -7
  14. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/utils/ranges.py +8 -6
  15. {jetpytools-2.2.0 → jetpytools-2.2.2}/pyproject.toml +2 -2
  16. jetpytools-2.2.0/jetpytools/_version.py +0 -2
  17. {jetpytools-2.2.0 → jetpytools-2.2.2}/.gitignore +0 -0
  18. {jetpytools-2.2.0 → jetpytools-2.2.2}/LICENSE +0 -0
  19. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/__init__.py +0 -0
  20. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/enums/__init__.py +0 -0
  21. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/exceptions/__init__.py +0 -0
  22. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/exceptions/enum.py +0 -0
  23. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/exceptions/generic.py +0 -0
  24. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/functions/__init__.py +0 -0
  25. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/functions/other.py +0 -0
  26. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/py.typed +0 -0
  27. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/types/__init__.py +0 -0
  28. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/types/builtins.py +0 -0
  29. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/types/check.py +0 -0
  30. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/types/funcs.py +0 -0
  31. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/types/generic.py +0 -0
  32. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/types/supports.py +0 -0
  33. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/utils/__init__.py +0 -0
  34. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/utils/funcs.py +0 -0
  35. {jetpytools-2.2.0 → jetpytools-2.2.2}/jetpytools/utils/math.py +0 -0
  36. {jetpytools-2.2.0 → jetpytools-2.2.2}/tests/test_file.py +0 -0
  37. {jetpytools-2.2.0 → jetpytools-2.2.2}/tests/test_funcs.py +0 -0
  38. {jetpytools-2.2.0 → jetpytools-2.2.2}/tests/test_normalize.py +0 -0
  39. {jetpytools-2.2.0 → jetpytools-2.2.2}/tests/test_types_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jetpytools
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: Collection of stuff that's useful in general python programming
5
5
  Project-URL: Source Code, https://github.com/Jaded-Encoding-Thaumaturgy/jetpytools
6
6
  Project-URL: Contact, https://discord.gg/XTpc6Fa9eB
@@ -30,5 +30,5 @@ A collection of utilities useful for general Python programming.
30
30
  `jetpytools` is distributed via **PyPI**, and the latest stable release can be installed using:
31
31
 
32
32
  ```sh
33
- pip install vsjetpack
33
+ pip install jetpytools
34
34
  ```
@@ -9,5 +9,5 @@ A collection of utilities useful for general Python programming.
9
9
  `jetpytools` is distributed via **PyPI**, and the latest stable release can be installed using:
10
10
 
11
11
  ```sh
12
- pip install vsjetpack
12
+ pip install jetpytools
13
13
  ```
@@ -0,0 +1,2 @@
1
+ __version__ = "2.2.2"
2
+ __version_tuple__ = (2, 2, 2)
@@ -42,12 +42,15 @@ class CustomEnum(Enum):
42
42
  """
43
43
  Return the enum value from a parameter.
44
44
 
45
- :param value: Value to instantiate the enum class.
46
- :param func_except: Exception function.
45
+ Args:
46
+ value: Value to instantiate the enum class.
47
+ func_except: Exception function.
47
48
 
48
- :return: Enum value.
49
+ Returns:
50
+ Enum value.
49
51
 
50
- :raises NotFoundEnumValue: Variable not found in the given enum.
52
+ Raises:
53
+ NotFoundEnumValue: Variable not found in the given enum.
51
54
  """
52
55
  func_except = func_except or cls.from_param
53
56
 
@@ -68,7 +71,7 @@ class CustomEnum(Enum):
68
71
  var_name=var_name,
69
72
  enum_name=cls,
70
73
  value=value,
71
- readable_enum=(f"{name} ({value!r})" for name, value in cls.__members__.items()),
74
+ readable_enum=(f"{name} ({value!s})" for name, value in cls.__members__.items()),
72
75
  reason=value,
73
76
  ) from None
74
77
 
@@ -9,7 +9,8 @@ class Coordinate:
9
9
  """
10
10
  Positive set of (x, y) coordinates.
11
11
 
12
- :raises ValueError: Negative values were passed.
12
+ Raises:
13
+ ValueError: Negative values were passed.
13
14
  """
14
15
 
15
16
  x: int
@@ -48,9 +48,10 @@ class CustomError(Exception, metaclass=CustomErrorMeta):
48
48
  """
49
49
  Instantiate a new exception with pretty printing and more.
50
50
 
51
- :param message: Message of the error.
52
- :param func: Function this exception was raised from.
53
- :param reason: Reason of the exception. For example, an optional parameter.
51
+ Args:
52
+ message: Message of the error.
53
+ func: Function this exception was raised from.
54
+ reason: Reason of the exception. For example, an optional parameter.
54
55
  """
55
56
 
56
57
  self.message = message
@@ -70,9 +71,10 @@ class CustomError(Exception, metaclass=CustomErrorMeta):
70
71
  """
71
72
  Copy an existing exception with defaults and instantiate a new one.
72
73
 
73
- :param message: Message of the error.
74
- :param func: Function this exception was raised from.
75
- :param reason: Reason of the exception. For example, an optional parameter.
74
+ Args:
75
+ message: Message of the error.
76
+ func: Function this exception was raised from.
77
+ reason: Reason of the exception. For example, an optional parameter.
76
78
  """
77
79
  from copy import deepcopy
78
80
 
@@ -1,7 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import sys
4
+
3
5
  from .base import CustomError, CustomPermissionError
4
6
 
7
+ if sys.version_info < (3, 13):
8
+ from typing_extensions import deprecated
9
+ else:
10
+ from warnings import deprecated
11
+
5
12
  __all__ = [
6
13
  "FileIsADirectoryError",
7
14
  "FileNotExistsError",
@@ -24,6 +31,7 @@ class FilePermissionError(CustomPermissionError):
24
31
  """Raised when you try to access a file but haven't got permissions to do so"""
25
32
 
26
33
 
34
+ @deprecated("FileTypeMismatchError is deprecated and will be removed in a future version.", category=DeprecationWarning)
27
35
  class FileTypeMismatchError(CustomError, OSError):
28
36
  """Raised when you try to access a file with a FileType != AUTO and it's another file type"""
29
37
 
@@ -19,9 +19,10 @@ class CustomImportError(CustomError, ImportError):
19
19
  **kwargs: Any,
20
20
  ) -> None:
21
21
  """
22
- :param func: Function this error was raised from.
23
- :param package: Either the raised error or the name of the missing package.
24
- :param message: Custom error message.
22
+ Args:
23
+ func: Function this error was raised from.
24
+ package: Either the raised error or the name of the missing package.
25
+ message: Custom error message.
25
26
  """
26
27
 
27
28
  super().__init__(message, func, package=package if isinstance(package, str) else package.name, **kwargs)
@@ -17,19 +17,21 @@ def iterate[T, **P, R](
17
17
  Different from regular iteration functions is that you do not need to pass a partial object.
18
18
  This function accepts *args and **kwargs. These will be passed on to the given function.
19
19
 
20
- Examples:
21
-
22
- >>> iterate(5, lambda x: x * 2, 2)
20
+ - Example:
21
+ ```python
22
+ >>> iterate(5, lambda x: x * 2, 2)
23
23
  20
24
+ ```
24
25
 
25
- :param base: Base value, etc. to iterate over.
26
- :param function: Function to iterate over the base.
27
- :param count: Number of times to execute function.
28
- :param *args: Positional arguments to pass to the given function.
29
- :param **kwargs: Keyword arguments to pass to the given function.
26
+ Args:
27
+ base: Base value, etc. to iterate over.
28
+ function: Function to iterate over the base.
29
+ count: Number of times to execute function.
30
+ *args: Positional arguments to pass to the given function.
31
+ **kwargs: Keyword arguments to pass to the given function.
30
32
 
31
- :return: Value, etc. with the given function run over it
32
- *n* amount of times based on the given count.
33
+ Returns:
34
+ Value, etc. with the given function run over it *n* amount of times based on the given count.
33
35
  """
34
36
 
35
37
  if count <= 0:
@@ -54,19 +56,20 @@ def fallback[T](value: T | None, *fallbacks: T | None, default: Any = _fallback_
54
56
  """
55
57
  Utility function that returns a value or a fallback if the value is None.
56
58
 
57
- Example:
58
-
59
- .. code-block:: python
60
-
59
+ - Example:
60
+ ```python
61
61
  >>> fallback(5, 6)
62
62
  5
63
63
  >>> fallback(None, 6)
64
64
  6
65
+ ```
65
66
 
66
- :param value: Input value to evaluate. Can be None.
67
- :param fallbacks: Value to return if the input value is None.
67
+ Args:
68
+ value: Input value to evaluate. Can be None.
69
+ *fallbacks: Value to return if the input value is None.
68
70
 
69
- :return: Input value or fallback value if input value is None.
71
+ Returns:
72
+ Input value or fallback value if input value is None.
70
73
  """
71
74
  for v in (value, *fallbacks):
72
75
  if v is not None:
@@ -102,20 +105,23 @@ def filter_kwargs(func: Callable[..., Any], kwargs: dict[str, Any] | None = None
102
105
  """
103
106
  Filter kwargs to only include parameters that match the callable's signature, ignoring **kwargs.
104
107
 
105
- Examples:
106
-
108
+ - Examples:
109
+ ```python
107
110
  >>> def my_func(a: int, b: str, c: bool = True):
108
111
  ... return a, b, c
109
112
  >>> filter_kwargs(my_func, a=1, b="hello", c=False, d="extra")
110
113
  {'a': 1, 'b': 'hello', 'c': False}
111
114
  >>> filter_kwargs(my_func, {"a": 1, "b": "hello", "c": False, "d": "extra"})
112
115
  {'a': 1, 'b': 'hello', 'c': False}
116
+ ```
113
117
 
114
- :param func: The callable to filter kwargs for.
115
- :param kwargs: Dictionary of keyword arguments to filter.
116
- :param **kw: Keyword arguments to filter (used when kwargs is None).
118
+ Args:
119
+ func: The callable to filter kwargs for.
120
+ kwargs: Dictionary of keyword arguments to filter.
121
+ **kw: Keyword arguments to filter (used when kwargs is None).
117
122
 
118
- :return: A dictionary containing only the kwargs that match the callable's parameters.
123
+ Returns:
124
+ A dictionary containing only the kwargs that match the callable's parameters.
119
125
  """
120
126
 
121
127
  if not (filtered_kwargs := fallback(kwargs, kw)):
@@ -31,12 +31,13 @@ def normalize_seq[T](val: T | Sequence[T], length: int) -> list[T]:
31
31
  """
32
32
  Normalize a sequence of values.
33
33
 
34
- :param val: Input value.
35
- :param length: Amount of items in the output.
36
- If original sequence length is less that this,
37
- the last item will be repeated.
34
+ Args:
35
+ val: Input value.
36
+ length: Amount of items in the output. If original sequence length is less that this, the last item will be
37
+ repeated.
38
38
 
39
- :return: List of normalized values with a set amount of items.
39
+ Returns:
40
+ List of normalized values with a set amount of items.
40
41
  """
41
42
 
42
43
  val = to_arr(val)
@@ -88,11 +89,12 @@ def normalize_range(ranges: SoftRange, /, exclusive: bool = False) -> Sequence[i
88
89
  """
89
90
  Normalize ranges represented by a tuple to an iterable of frame numbers.
90
91
 
91
- :param ranges: Ranges to normalize.
92
- :param exclusive: Whether to use exclusive (Python-style) ranges.
93
- Defaults to False.
92
+ Args:
93
+ ranges: Ranges to normalize.
94
+ exclusive: Whether to use exclusive (Python-style) ranges. Defaults to False.
94
95
 
95
- :return: List of positive frame ranges.
96
+ Returns:
97
+ List of positive frame ranges.
96
98
  """
97
99
 
98
100
  if isinstance(ranges, int):
@@ -147,25 +149,24 @@ def normalize_ranges(
147
149
  None will be converted to either 0 if it's the first value in a SoftRange, or the end if it's the second item.
148
150
  Negative values will be subtracted from the end.
149
151
 
150
- Examples:
151
-
152
- .. code-block:: python
153
-
152
+ - Examples:
153
+ ```python
154
154
  >>> normalize_ranges((None, None), length=1000)
155
155
  [(0, 999)]
156
156
  >>> normalize_ranges((24, -24), length=1000)
157
157
  [(24, 975)]
158
158
  >>> normalize_ranges([(24, 100), (80, 150)], length=1000)
159
159
  [(24, 150)]
160
+ ```
160
161
 
162
+ Args:
163
+ ranges: Frame range or list of frame ranges.
164
+ length: Number of frames.
165
+ exclusive: Whether to use exclusive (Python-style) ranges. Defaults to False.
166
+ strict: Whether to enforce strict checking for out-of-range values.
161
167
 
162
- :param ranges: Frame range or list of frame ranges.
163
- :param length: Number of frames.
164
- :param exclusive: Whether to use exclusive (Python-style) ranges.
165
- Defaults to False.
166
- :param strict: Whether to enforce strict checking for out-of-range values.
167
-
168
- :return: List of positive frame ranges.
168
+ Returns:
169
+ List of positive frame ranges.
169
170
  """
170
171
  from ..utils import clamp
171
172
 
@@ -132,7 +132,7 @@ class SPath(Path):
132
132
  import shutil
133
133
 
134
134
  try:
135
- return shutil.rmtree(str(self.get_folder()), ignore_errors)
135
+ return shutil.rmtree(self.get_folder(), ignore_errors)
136
136
  except FileNotFoundError:
137
137
  if not missing_ok:
138
138
  raise
@@ -161,9 +161,11 @@ class SPath(Path):
161
161
 
162
162
  return self.is_dir() and not any(self.iterdir())
163
163
 
164
- def move_dir(self, dst: SPath, *, mode: int = 0o777) -> None:
164
+ def move_dir(self, dst: SPathLike, *, mode: int = 0o777) -> None:
165
165
  """Move the directory to the specified destination."""
166
166
 
167
+ dst = SPath(dst)
168
+
167
169
  dst.mkdir(mode, True, True)
168
170
 
169
171
  for file in listdir(self):
@@ -177,21 +179,22 @@ class SPath(Path):
177
179
 
178
180
  self.rmdir()
179
181
 
180
- def copy_dir(self, dst: SPath) -> SPath:
182
+ def copy_dir(self, dst: SPathLike) -> SPath:
181
183
  """Copy the directory to the specified destination."""
184
+ import shutil
182
185
 
183
186
  if not self.is_dir():
184
187
  from ..exceptions import PathIsNotADirectoryError
185
188
 
186
189
  raise PathIsNotADirectoryError('The given path, "{self}" is not a directory!', self.copy_dir)
187
190
 
188
- dst.mkdirp()
191
+ dst = SPath(dst)
189
192
 
190
- import shutil
193
+ dst.mkdirp()
191
194
 
192
195
  shutil.copytree(self, dst, dirs_exist_ok=True)
193
196
 
194
- return SPath(dst)
197
+ return dst
195
198
 
196
199
  def lglob(self, pattern: str = "*") -> list[SPath]:
197
200
  """Glob the path and return the list of paths."""
@@ -209,17 +212,16 @@ class SPath(Path):
209
212
 
210
213
  return None
211
214
 
212
- def find_newest_file(self, pattern: str = "*") -> SPath | None:
215
+ def find_newest_file(
216
+ self, pattern: str = "*", *, case_sensitive: bool | None = None, recurse_symlinks: bool = False
217
+ ) -> SPath | None:
213
218
  """Find the most recently modified file matching the given pattern in the directory."""
214
219
 
215
- matching_files = self.get_folder().glob(pattern)
216
-
217
- try:
218
- next(matching_files)
219
- except StopIteration:
220
- return None
221
-
222
- return max(matching_files, key=lambda p: p.stat().st_mtime)
220
+ return max(
221
+ self.get_folder().glob(pattern, case_sensitive=case_sensitive, recurse_symlinks=recurse_symlinks),
222
+ key=lambda p: p.stat().st_mtime,
223
+ default=None,
224
+ )
223
225
 
224
226
  def get_size(self) -> int:
225
227
  """Get the size of the file or directory in bytes."""
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import sys
4
4
  from contextlib import suppress
5
5
  from functools import wraps
6
+ from threading import Lock
6
7
  from types import LambdaType
7
8
  from typing import (
8
9
  TYPE_CHECKING,
@@ -114,9 +115,13 @@ class _InjectSelfMeta(type):
114
115
  """
115
116
  Recursively flatten and yield all nested inject_self metaclass relationships.
116
117
 
117
- :param d: Optional inner dictionary representing the hierarchy level.
118
- :yield: Each `_InjectSelfMeta` subclass found in the hierarchy.
118
+ Args:
119
+ d: Optional inner dictionary representing the hierarchy level.
120
+
121
+ Yields:
122
+ Each `_InjectSelfMeta` subclass found in the hierarchy.
119
123
  """
124
+
120
125
  if d is None:
121
126
  d = _inject_self_cls[cls]
122
127
 
@@ -152,7 +157,7 @@ _P0 = ParamSpec("_P0")
152
157
  _P1 = ParamSpec("_P1")
153
158
 
154
159
 
155
- class _InjectedSelfFunc(Protocol[_T_co, _P, _R_co]):
160
+ class _InjectedSelfFunc(Protocol[_T_co, _P, _R_co]): # type: ignore[misc]
156
161
  """
157
162
  Protocol defining the callable interface for wrapped functions under `inject_self`.
158
163
 
@@ -188,9 +193,10 @@ class _InjectSelfBase(Generic[_T_co, _P, _R_co]):
188
193
  """
189
194
  Initialize the inject_self descriptor.
190
195
 
191
- :param function: The function or method to wrap.
192
- :param *args: Positional arguments to pass when instantiating the target class.
193
- :param **kwargs: Keyword arguments to pass when instantiating the target class.
196
+ Args:
197
+ function: The function or method to wrap.
198
+ *args: Positional arguments to pass when instantiating the target class.
199
+ **kwargs: Keyword arguments to pass when instantiating the target class.
194
200
  """
195
201
  self._function = function
196
202
 
@@ -247,9 +253,12 @@ class _InjectSelfBase(Generic[_T_co, _P, _R_co]):
247
253
  """
248
254
  Handle logic when the descriptor is accessed from the class level.
249
255
 
250
- :param owner: The class object owning the descriptor.
251
- :param kwargs: Keyword arguments passed to the wrapped function.
252
- :return: A tuple of `(self_object, updated_kwargs)`.
256
+ Args:
257
+ owner: The class object owning the descriptor.
258
+ kwargs: Keyword arguments passed to the wrapped function.
259
+
260
+ Returns:
261
+ A tuple of `(self_object, updated_kwargs)`.
253
262
  """
254
263
  if isinstance(self, inject_self.cached):
255
264
  # Cached instance creation
@@ -403,8 +412,9 @@ class _InjectKwargsParamsBase(Generic[_T_co, _P, _R_co]):
403
412
  """
404
413
  Initialize the inject_kwargs_params descriptor.
405
414
 
406
- :param function: The target function or method whose parameters will be injected
407
- from the instance's `self.kwargs` mapping.
415
+ Args:
416
+ func: The target function or method whose parameters will be injected
417
+ from the instance's `self.kwargs` mapping.
408
418
  """
409
419
  self._function = func
410
420
  self._signature = None
@@ -541,9 +551,11 @@ class _ComplexHash[**P, R]:
541
551
  """
542
552
  Recursively hash every unhashable object in ``*args``.
543
553
 
544
- :param *args: Objects to be hashed.
554
+ Args:
555
+ *args: Objects to be hashed.
545
556
 
546
- :return: Hash of all the combined objects' hashes.
557
+ Returns:
558
+ Hash of all the combined objects' hashes.
547
559
  """
548
560
  values = list[str]()
549
561
 
@@ -587,11 +599,13 @@ def get_subclasses[T](family: type[T], exclude: Sequence[type[T]] = []) -> list[
587
599
  """
588
600
  Get all subclasses of a given type.
589
601
 
590
- :param family: "Main" type all other classes inherit from.
591
- :param exclude: Excluded types from the yield. Note that they won't be excluded from search.
592
- For examples, subclasses of these excluded classes will be yield.
602
+ Args:
603
+ family: "Main" type all other classes inherit from.
604
+ exclude: Excluded types from the yield. Note that they won't be excluded from search. For examples, subclasses
605
+ of these excluded classes will be yield.
593
606
 
594
- :return: List of all subclasses of "family".
607
+ Returns:
608
+ List of all subclasses of "family".
595
609
  """
596
610
 
597
611
  def _subclasses(cls: type[T]) -> Iterator[type[T]]:
@@ -706,8 +720,9 @@ class classproperty(classproperty_base[_T, _R_co, _T_Any]):
706
720
  """
707
721
  Clear cached properties of an type instance.
708
722
 
709
- :param type_: The type whose cache should be cleared.
710
- :param names: Specific property names to clear. If None, all cached properties are cleared.
723
+ Args:
724
+ type_: The type whose cache should be cleared.
725
+ names: Specific property names to clear. If None, all cached properties are cleared.
711
726
  """
712
727
  if names is None:
713
728
  with suppress(AttributeError):
@@ -805,8 +820,9 @@ class cachedproperty(property, Generic[_R_co, _T_Any]):
805
820
  """
806
821
  Clear cached properties of an object instance.
807
822
 
808
- :param obj: The object whose cache should be cleared.
809
- :param names: Specific property names to clear. If None, all cached properties are cleared.
823
+ Args:
824
+ obj: The object whose cache should be cleared.
825
+ names: Specific property names to clear. If None, all cached properties are cleared.
810
826
  """
811
827
  if names is None:
812
828
  obj.__dict__.get(cls.cache_key, {}).clear()
@@ -825,9 +841,10 @@ class cachedproperty(property, Generic[_R_co, _T_Any]):
825
841
  """
826
842
  Update cached property of an object instance.
827
843
 
828
- :param obj: The object whose cache should be updated.
829
- :param names: Property name to update.
830
- :param value: The value to assign.
844
+ Args:
845
+ obj: The object whose cache should be updated.
846
+ name: Property name to update.
847
+ value: The value to assign.
831
848
  """
832
849
  obj.__dict__.setdefault(cls.cache_key, {})[name] = value
833
850
 
@@ -842,6 +859,8 @@ class KwargsNotNone(KwargsT):
842
859
 
843
860
  class SingletonMeta(type):
844
861
  _instances: ClassVar[dict[SingletonMeta, Any]] = {}
862
+ _lock = Lock()
863
+
845
864
  _singleton_init: bool
846
865
 
847
866
  def __new__[MetaSelf: SingletonMeta](
@@ -860,12 +879,16 @@ class SingletonMeta(type):
860
879
  if not TYPE_CHECKING:
861
880
 
862
881
  def __call__(cls, *args: Any, **kwargs: Any) -> Any:
863
- if cls not in cls._instances:
864
- cls._instances[cls] = obj = super().__call__(*args, **kwargs)
865
- return obj
882
+ if cls in cls._instances and not cls._singleton_init:
883
+ return cls._instances[cls]
884
+
885
+ with cls._lock:
886
+ if cls not in cls._instances:
887
+ cls._instances[cls] = obj = super().__call__(*args, **kwargs)
888
+ return obj
866
889
 
867
- if cls._singleton_init:
868
- cls._instances[cls].__init__(*args, **kwargs)
890
+ if cls._singleton_init:
891
+ cls._instances[cls].__init__(*args, **kwargs)
869
892
 
870
893
  return cls._instances[cls]
871
894
 
@@ -68,16 +68,18 @@ def check_perms(
68
68
  """
69
69
  Confirm whether the user has write/read access to a file.
70
70
 
71
- :param file: Path to file.
72
- :param mode: Read/Write mode.
73
- :param func: Function that this was called from, only useful to *func writers.
71
+ Args:
72
+ file: Path to file.
73
+ mode: Read/Write mode.
74
+ func: Function that this was called from, only useful to *func writers.
74
75
 
75
76
  :param: True if the user has write/read access, else False.
76
77
 
77
- :raises FileNotExistsError: File could not be found.
78
- :raises FilePermissionError: User does not have access to the file.
79
- :raises FileIsADirectoryError: Given path is a directory, not a file.
80
- :raises FileWasNotFoundError: Parent directories exist, but the given file could not be found.
78
+ Raises:
79
+ FileNotExistsError: File could not be found.
80
+ FilePermissionError: User does not have access to the file.
81
+ FileIsADirectoryError: Given path is a directory, not a file.
82
+ FileWasNotFoundError: Parent directories exist, but the given file could not be found.
81
83
  """
82
84
 
83
85
  file = Path(str(file))
@@ -10,13 +10,15 @@ def interleave_arr[T0, T1](arr0: Iterable[T0], arr1: Iterable[T1], n: int = 2) -
10
10
  """
11
11
  Interleave two arrays of variable length.
12
12
 
13
- :param arr0: First array to be interleaved.
14
- :param arr1: Second array to be interleaved.
15
- :param n: The number of elements from arr0 to include in the interleaved sequence
16
- before including an element from arr1.
17
-
18
- :yield: Elements from either arr0 or arr01.
13
+ Args:
14
+ arr0: First array to be interleaved.
15
+ arr1: Second array to be interleaved.
16
+ n: The number of elements from arr0 to include in the interleaved sequence before including an element from
17
+ arr1.
18
+ Yields:
19
+ Elements from either arr0 or arr01.
19
20
  """
21
+
20
22
  if n <= 0:
21
23
  raise ValueError("n must be a positive integer")
22
24
 
@@ -25,7 +25,7 @@ dependencies = ["typing_extensions>=4.15.0; python_version<'3.13'"]
25
25
  "Contact" = "https://discord.gg/XTpc6Fa9eB"
26
26
 
27
27
  [dependency-groups]
28
- dev = ["mypy~=1.18.0", "ruff~=0.14.0", "pytest>=9.0.0, <10.0.0", "twine>=6.2.0"]
28
+ dev = ["mypy~=1.19.0", "ruff~=0.14.0", "pytest>=9.0.0, <10.0.0", "twine>=6.2.0"]
29
29
 
30
30
  [build-system]
31
31
  requires = ["hatchling>=1.27.0", "versioningit"]
@@ -34,7 +34,7 @@ build-backend = "hatchling.build"
34
34
  [tool.hatch.version]
35
35
  source = "versioningit"
36
36
  default-version = "0.0.0+unknown"
37
- next-version = "minor-release"
37
+ next-version = "minor"
38
38
  write = { file = "jetpytools/_version.py", template = "__version__ = \"{normalized_version}\"\n__version_tuple__ = {version_tuple}" }
39
39
 
40
40
  [tool.hatch.version.format]
@@ -1,2 +0,0 @@
1
- __version__ = "2.2.0"
2
- __version_tuple__ = (2, 2, 0)
File without changes
File without changes