jetpytools 1.3.0__tar.gz → 1.5.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.

Potentially problematic release.


This version of jetpytools might be problematic. Click here for more details.

Files changed (41) hide show
  1. {jetpytools-1.3.0 → jetpytools-1.5.0}/PKG-INFO +1 -1
  2. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/_metadata.py +1 -1
  3. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/exceptions/base.py +3 -13
  4. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/functions/funcs.py +46 -2
  5. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/functions/normalize.py +85 -28
  6. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/types/file.py +8 -2
  7. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/types/utils.py +22 -57
  8. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/utils/math.py +3 -1
  9. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools.egg-info/PKG-INFO +1 -1
  10. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools.egg-info/SOURCES.txt +3 -1
  11. {jetpytools-1.3.0 → jetpytools-1.5.0}/setup.cfg +1 -0
  12. jetpytools-1.5.0/tests/test_funcs.py +12 -0
  13. jetpytools-1.5.0/tests/test_normalize.py +63 -0
  14. {jetpytools-1.3.0 → jetpytools-1.5.0}/LICENSE +0 -0
  15. {jetpytools-1.3.0 → jetpytools-1.5.0}/README.md +0 -0
  16. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/__init__.py +0 -0
  17. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/enums/__init__.py +0 -0
  18. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/enums/base.py +0 -0
  19. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/enums/other.py +0 -0
  20. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/exceptions/__init__.py +0 -0
  21. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/exceptions/enum.py +0 -0
  22. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/exceptions/file.py +0 -0
  23. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/exceptions/generic.py +0 -0
  24. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/exceptions/module.py +0 -0
  25. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/functions/__init__.py +0 -0
  26. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/functions/other.py +0 -0
  27. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/py.typed +0 -0
  28. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/types/__init__.py +0 -0
  29. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/types/builtins.py +0 -0
  30. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/types/check.py +0 -0
  31. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/types/funcs.py +0 -0
  32. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/types/generic.py +0 -0
  33. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/types/supports.py +0 -0
  34. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/utils/__init__.py +0 -0
  35. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/utils/file.py +0 -0
  36. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/utils/funcs.py +0 -0
  37. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools/utils/ranges.py +0 -0
  38. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools.egg-info/dependency_links.txt +0 -0
  39. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools.egg-info/requires.txt +0 -0
  40. {jetpytools-1.3.0 → jetpytools-1.5.0}/jetpytools.egg-info/top_level.txt +0 -0
  41. {jetpytools-1.3.0 → jetpytools-1.5.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jetpytools
3
- Version: 1.3.0
3
+ Version: 1.5.0
4
4
  Summary: Collection of stuff that's useful in general python programming
5
5
  Author: Jaded Encoding Thaumaturgy
6
6
  Author-email: jaded.encoding.thaumaturgy@gmail.com
@@ -1,6 +1,6 @@
1
1
  """Collection of stuff that's useful in general python programming"""
2
2
 
3
- __version__ = '1.3.0'
3
+ __version__ = '1.5.0'
4
4
 
5
5
  __author_name__, __author_email__ = 'Jaded Encoding Thaumaturgy', 'jaded.encoding.thaumaturgy@gmail.com'
6
6
  __maintainer_name__, __maintainer_email__ = __author_name__, __author_email__
@@ -72,10 +72,6 @@ class CustomErrorMeta(type):
72
72
 
73
73
  return exception
74
74
 
75
- if TYPE_CHECKING:
76
- def __getitem__(self, exception: type[Exception]) -> CustomError:
77
- ...
78
-
79
75
 
80
76
  SelfCErrorMeta = TypeVar('SelfCErrorMeta', bound=CustomErrorMeta)
81
77
 
@@ -102,17 +98,11 @@ class CustomError(ExceptionT, metaclass=CustomErrorMeta):
102
98
  super().__init__(message)
103
99
 
104
100
  def __class_getitem__(cls, exception: str | type[ExceptionT] | ExceptionT) -> CustomError:
105
- if isinstance(exception, str):
106
- class inner_exception(cls): # type: ignore
107
- ...
108
- else:
109
- if not issubclass(exception, type): # type: ignore
110
- exception = exception.__class__ # type: ignore
101
+ from warnings import warn
111
102
 
112
- class inner_exception(cls, exception): # type: ignore
113
- ...
103
+ warn("Custom error is not subscriptable anymore. Don't use it", DeprecationWarning)
114
104
 
115
- return CustomErrorMeta.setup_exception(inner_exception, exception) # type: ignore
105
+ return cls()
116
106
 
117
107
  def __call__(
118
108
  self,
@@ -1,12 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from typing import Any, Callable, Concatenate, overload
4
+ from inspect import signature
4
5
 
5
- from ..exceptions import CustomRuntimeError
6
+ from ..exceptions import CustomRuntimeError, CustomValueError
6
7
  from ..types import MISSING, KwargsT, MissingT, P, R, T
7
8
 
8
9
  __all__ = [
9
- 'iterate', 'fallback', 'kwargs_fallback'
10
+ 'iterate', 'fallback', 'kwargs_fallback', 'filter_kwargs'
10
11
  ]
11
12
 
12
13
 
@@ -150,3 +151,46 @@ def kwargs_fallback( # type: ignore
150
151
  """Utility function to return a fallback value from kwargs if value was not found or is None."""
151
152
 
152
153
  return fallback(value, kwargs[0].get(kwargs[1], None), *fallbacks, default=default)
154
+
155
+
156
+ @overload
157
+ def filter_kwargs(func: Callable[..., Any], kwargs: dict[str, Any]) -> dict[str, Any]:
158
+ ...
159
+
160
+
161
+ @overload
162
+ def filter_kwargs(func: Callable[..., Any], **kwargs: Any) -> dict[str, Any]:
163
+ ...
164
+
165
+
166
+ def filter_kwargs(func: Callable[..., Any], kwargs: dict[str, Any] | None = None, **kw: Any) -> dict[str, Any]:
167
+ """
168
+ Filter kwargs to only include parameters that match the callable's signature, ignoring **kwargs.
169
+
170
+ Examples:
171
+
172
+ >>> def my_func(a: int, b: str, c: bool = True):
173
+ ... return a, b, c
174
+ >>> filter_kwargs(my_func, a=1, b="hello", c=False, d="extra")
175
+ {'a': 1, 'b': 'hello', 'c': False}
176
+ >>> filter_kwargs(my_func, {"a": 1, "b": "hello", "c": False, "d": "extra"})
177
+ {'a': 1, 'b': 'hello', 'c': False}
178
+
179
+ :param func: The callable to filter kwargs for.
180
+ :param kwargs: Dictionary of keyword arguments to filter.
181
+ :param **kw: Keyword arguments to filter (used when kwargs is None).
182
+
183
+ :return: A dictionary containing only the kwargs that match the callable's parameters.
184
+ """
185
+
186
+ if not (filtered_kwargs := fallback(kwargs, kw)):
187
+ return {}
188
+
189
+ try:
190
+ sig = signature(func)
191
+ except Exception as e:
192
+ raise CustomValueError(e.args[0], filter_kwargs, func) from e
193
+
194
+ param_names = {name for name, param in sig.parameters.items() if param.kind != param.VAR_KEYWORD}
195
+
196
+ return {name: value for name, value in filtered_kwargs.items() if name in param_names}
@@ -1,9 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from fractions import Fraction
4
+ import sys
4
5
  from typing import Any, Callable, Iterable, Iterator, Sequence, overload
5
6
 
6
7
  from ..types import SoftRange, SoftRangeN, SoftRangesN, StrictRange, SupportsString, T, is_soft_range_n
8
+ from ..exceptions import CustomOverflowError
7
9
 
8
10
  __all__ = [
9
11
  'normalize_seq',
@@ -93,11 +95,13 @@ def flatten(items: Any) -> Iterator[Any]:
93
95
  yield val
94
96
 
95
97
 
96
- def normalize_range(ranges: SoftRange, /) -> Iterable[int]:
98
+ def normalize_range(ranges: SoftRange, /, exclusive: bool = False) -> Sequence[int]:
97
99
  """
98
100
  Normalize ranges represented by a tuple to an iterable of frame numbers.
99
101
 
100
102
  :param ranges: Ranges to normalize.
103
+ :param exclusive: Whether to use exclusive (Python-style) ranges.
104
+ Defaults to False.
101
105
 
102
106
  :return: List of positive frame ranges.
103
107
  """
@@ -109,12 +113,12 @@ def normalize_range(ranges: SoftRange, /) -> Iterable[int]:
109
113
  start, stop = ranges
110
114
  step = -1 if stop < start else 1
111
115
 
112
- return range(start, stop + step, step)
116
+ return range(start, stop + (not exclusive * step), step)
113
117
 
114
118
  return ranges
115
119
 
116
120
 
117
- def normalize_list_to_ranges(flist: Iterable[int], min_length: int = 0) -> list[StrictRange]:
121
+ def normalize_list_to_ranges(flist: Iterable[int], min_length: int = 0, exclusive: bool = False) -> list[StrictRange]:
118
122
  flist2 = list[list[int]]()
119
123
  flist3 = list[int]()
120
124
 
@@ -135,20 +139,26 @@ def normalize_list_to_ranges(flist: Iterable[int], min_length: int = 0) -> list[
135
139
 
136
140
  return list(zip(
137
141
  [i[0] for i in flist4],
138
- [i[-1] for j, i in enumerate(flist4)]
142
+ [i[-1] + exclusive for i in flist4]
139
143
  ))
140
144
 
141
145
 
142
- def normalize_ranges_to_list(ranges: Iterable[SoftRange]) -> list[int]:
146
+ def normalize_ranges_to_list(ranges: Iterable[SoftRange], exclusive: bool = False) -> list[int]:
143
147
  out = list[int]()
144
148
 
145
149
  for srange in ranges:
146
- out.extend(normalize_range(srange))
150
+ out.extend(normalize_range(srange, exclusive))
147
151
 
148
152
  return out
149
153
 
150
154
 
151
- def normalize_ranges(ranges: SoftRangeN | SoftRangesN, end: int) -> list[StrictRange]:
155
+ def normalize_ranges(
156
+ ranges: SoftRangeN | SoftRangesN,
157
+ length: int,
158
+ exclusive: bool = False,
159
+ *,
160
+ strict: bool = True
161
+ ) -> list[StrictRange]:
152
162
  """
153
163
  Normalize ranges to a list of positive ranges.
154
164
 
@@ -160,57 +170,104 @@ def normalize_ranges(ranges: SoftRangeN | SoftRangesN, end: int) -> list[StrictR
160
170
 
161
171
  .. code-block:: python
162
172
 
163
- >>> normalize_ranges((None, None), end=1000)
173
+ >>> normalize_ranges((None, None), length=1000)
164
174
  [(0, 999)]
165
- >>> normalize_ranges((24, -24), end=1000)
175
+ >>> normalize_ranges((24, -24), length=1000)
166
176
  [(24, 975)]
167
- >>> normalize_ranges([(24, 100), (80, 150)], end=1000)
177
+ >>> normalize_ranges([(24, 100), (80, 150)], length=1000)
168
178
  [(24, 150)]
169
179
 
170
180
 
171
- :param franges: Frame range or list of frame ranges.
172
- :param end: End number.
181
+ :param ranges: Frame range or list of frame ranges.
182
+ :param length: Number of frames.
183
+ :param exclusive: Whether to use exclusive (Python-style) ranges.
184
+ Defaults to False.
185
+ :param strict: Whether to enforce strict checking for out-of-range values.
173
186
 
174
187
  :return: List of positive frame ranges.
175
188
  """
189
+ from ..utils import clamp
176
190
 
177
191
  ranges = [ranges] if is_soft_range_n(ranges) else ranges
178
192
 
179
- out = []
193
+ out = list[tuple[int, int]]()
194
+ exceptions = list[Exception]()
180
195
 
181
196
  for r in ranges:
182
197
  if r is None:
183
198
  r = (None, None)
184
199
 
185
200
  if isinstance(r, tuple):
186
- start, endd = r
201
+ start, end = r
187
202
  if start is None:
188
203
  start = 0
189
- if endd is None:
190
- endd = end - 1
204
+ if end is None:
205
+ end = length - (not exclusive)
191
206
  else:
192
207
  start = r
193
- endd = r
208
+ end = r + exclusive
194
209
 
195
210
  if start < 0:
196
- start = end - 1 + start
211
+ start = length + start
212
+
213
+ if end < 0:
214
+ end = length + end - (not exclusive)
215
+
216
+ # Always throws an error if start and end are negative
217
+ # or higher than length
218
+ # or start is higher than end (likely mismatched)
219
+ if any([
220
+ start < 0 and end < 0,
221
+ start >= length and end - (not exclusive) > length,
222
+ start >= end + (not exclusive),
223
+ ]):
224
+ exception = CustomOverflowError(
225
+ f"Range `{r}` with length `{length}` could not be normalized!", normalize_ranges
226
+ )
227
+ exceptions.append(exception)
228
+ continue
229
+
230
+ if strict:
231
+ if start < 0:
232
+ exception = CustomOverflowError(
233
+ f"Start frame `{start}` in range `{r}` with length `{length}` could not be normalized!",
234
+ normalize_ranges
235
+ )
236
+ exceptions.append(exception)
237
+ continue
238
+ if end - (not exclusive) > length:
239
+ exception = CustomOverflowError(
240
+ f"End frame `{end}` in range `{r}` with length `{length}` could not be normalized!",
241
+ normalize_ranges
242
+ )
243
+ exceptions.append(exception)
244
+ continue
245
+ else:
246
+ start = clamp(start, 0, length - 1)
247
+ end = clamp(end, int(exclusive), length - (not exclusive))
248
+
249
+ out.append((start, end))
197
250
 
198
- if endd < 0:
199
- endd = end - 1 + endd
251
+ if exceptions:
252
+ if sys.version_info >= (3, 11):
253
+ raise ExceptionGroup("Multiple exceptions occurred!", exceptions) # noqa: F821
200
254
 
201
- out.append((start, endd))
255
+ raise Exception(exceptions)
202
256
 
203
- return normalize_list_to_ranges([
204
- x for start, endd in out for x in range(start, endd + 1)
205
- ])
257
+ return normalize_list_to_ranges(
258
+ [x for start, end in out for x in range(start, end + (not exclusive))],
259
+ exclusive=exclusive
260
+ )
206
261
 
207
262
 
208
- def invert_ranges(ranges: SoftRangeN | SoftRangesN, enda: int, endb: int | None) -> list[StrictRange]:
209
- norm_ranges = normalize_ranges(ranges, enda if endb is None else endb)
263
+ def invert_ranges(
264
+ ranges: SoftRangeN | SoftRangesN, lengtha: int, lengthb: int | None, exclusive: bool = False
265
+ ) -> list[StrictRange]:
266
+ norm_ranges = normalize_ranges(ranges, lengtha if lengthb is None else lengthb, exclusive)
210
267
 
211
- b_frames = {*normalize_ranges_to_list(norm_ranges)}
268
+ b_frames = {*normalize_ranges_to_list(norm_ranges, exclusive)}
212
269
 
213
- return normalize_list_to_ranges({*range(enda)} - b_frames)
270
+ return normalize_list_to_ranges({*range(lengtha)} - b_frames, exclusive=exclusive)
214
271
 
215
272
 
216
273
  def norm_func_name(func_name: SupportsString | Callable[..., Any]) -> str:
@@ -2,8 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import fnmatch
4
4
  import shutil
5
-
6
- from os import PathLike, listdir, path, walk
5
+ from os import X_OK, PathLike, access, listdir, path, walk
7
6
  from pathlib import Path
8
7
  from sys import version_info
9
8
  from typing import TYPE_CHECKING, Any, Callable, Iterable, Literal, TypeAlias, Union
@@ -26,6 +25,7 @@ __all__ = [
26
25
  'SPath', 'SPathLike'
27
26
  ]
28
27
 
28
+
29
29
  FileDescriptor: TypeAlias = int
30
30
 
31
31
  FilePathType: TypeAlias = str | bytes | PathLike[str] | PathLike[bytes]
@@ -60,6 +60,7 @@ OpenBinaryMode: TypeAlias = OpenBinaryModeUpdating | OpenBinaryModeReading | Ope
60
60
 
61
61
  class SPath(Path):
62
62
  """Modified version of pathlib.Path"""
63
+
63
64
  if version_info < (3, 12):
64
65
  _flavour = type(Path())._flavour # type: ignore
65
66
 
@@ -195,5 +196,10 @@ class SPath(Path):
195
196
 
196
197
  return sum(f.stat().st_size for f in self.rglob('*') if f.is_file())
197
198
 
199
+ def is_executable(self) -> bool:
200
+ """Check if the path is executable."""
201
+
202
+ return access(self.to_str(), X_OK)
203
+
198
204
 
199
205
  SPathLike = Union[str, PathLike[str], Path, SPath]
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  from functools import partial, wraps
4
4
  from inspect import Signature
5
5
  from inspect import _empty as empty_param
6
- from inspect import isclass
7
6
  from typing import (
8
7
  TYPE_CHECKING, Any, Callable, Concatenate, Generator, Generic, Iterable, Iterator, Mapping, NoReturn, Protocol,
9
8
  Sequence, TypeVar, cast, overload
@@ -411,89 +410,55 @@ def get_subclasses(family: type[T], exclude: Sequence[type[T]] = []) -> list[typ
411
410
  return list(set(_subclasses(family)))
412
411
 
413
412
 
414
- class classproperty(Generic[P, R, T, T0, P0]):
413
+ class classproperty(Generic[T, R]):
415
414
  """
416
415
  Make a class property. A combination between classmethod and property.
417
416
  """
418
417
 
419
418
  __isabstractmethod__: bool = False
420
419
 
421
- class metaclass(type):
422
- """This must be set for the decorator to work."""
423
-
424
- def __setattr__(self, key: str, value: Any) -> None:
425
- if key in self.__dict__:
426
- obj = self.__dict__.get(key)
427
-
428
- if obj and isinstance(obj, classproperty):
429
- obj.__set__(self, value)
430
- return
431
-
432
- super(classproperty.metaclass, self).__setattr__(key, value)
433
-
434
420
  def __init__(
435
421
  self,
436
- fget: classmethod[T, P, R] | Callable[P, R],
437
- fset: classmethod[T, P, None] | Callable[[T, T0], None] | None = None,
438
- fdel: classmethod[T, P1, None] | Callable[P1, None] | None = None,
422
+ fget: Callable[[type[T]], R] | classmethod[T, ..., R],
423
+ fset: Callable[[type[T], R], None] | classmethod[T, Concatenate[R, ...], None] | None = None,
424
+ fdel: Callable[[type[T]], None] | classmethod[T, ..., None] | None = None,
439
425
  doc: str | None = None,
440
426
  ) -> None:
441
427
  self.fget = self._wrap(fget)
442
428
  self.fset = self._wrap(fset) if fset is not None else fset
443
429
  self.fdel = self._wrap(fdel) if fdel is not None else fdel
444
430
 
445
- self.doc = doc
431
+ self.__doc__ = doc
446
432
 
447
- def _wrap(self, func: classmethod[T1, P1, R1] | Callable[P1, R1] | Callable[..., R1]) -> classmethod[T1, P1, R1]:
448
- if not isinstance(func, (classmethod, staticmethod)):
449
- func = classmethod(func) # type: ignore
433
+ def _wrap(self, func: Callable[..., R1] | classmethod[T, P1, R1]) -> classmethod[T, P1, R1]:
434
+ if not isinstance(func, classmethod):
435
+ func = classmethod(func)
450
436
 
451
- return func # type: ignore
437
+ return func
452
438
 
453
- def getter(self, __fget: classmethod[T, P, R] | Callable[P1, R1]) -> classproperty[P1, R1, T, T0, P0]:
454
- self.fget = self._wrap(__fget) # type: ignore
455
- return self # type: ignore
456
-
457
- def setter(self, __fset: classmethod[T1, P, None] | Callable[[T1, T2], None]) -> classproperty[P, R, T1, T2, P0]:
458
- self.fset = self._wrap(__fset) # type: ignore
459
- return self # type: ignore
460
-
461
- def deleter(self, __fdel: classmethod[T1, P1, None] | Callable[P1, None]) -> classproperty[P, R, T, T0, P1]:
462
- self.fdel = self._wrap(__fdel) # type: ignore
463
- return self # type: ignore
464
-
465
- def __get__(self, __obj: Any, __type: type | None = None) -> R:
439
+ def __get__(self, __obj: T | None, __type: type | None = None) -> R:
466
440
  if __type is None:
467
441
  __type = type(__obj)
468
442
 
469
- return self.fget.__get__(__obj, __type)() # type: ignore
470
-
471
- def __set__(self, __obj: Any, __value: Any) -> None:
472
- from ..exceptions import CustomError
443
+ return self.fget.__get__(__obj, __type)()
473
444
 
445
+ def __set__(self, __obj: T, __value: Any) -> None:
474
446
  if not self.fset:
475
- raise CustomError[AttributeError]("Can't set attribute")
476
-
477
- if isclass(__obj):
478
- type_, __obj = __obj, None
479
- else:
480
- type_ = type(__obj)
481
-
482
- return self.fset.__get__(__obj, type_)(__value) # type: ignore[call-arg]
447
+ raise AttributeError(
448
+ f'classproperty with getter "{self.__name__}" of "{__obj.__class__.__name__}" object has no setter.'
449
+ )
483
450
 
484
- def __delete__(self, __obj: Any) -> None:
485
- from ..exceptions import CustomError
451
+ self.fset.__get__(None, type(__obj))(__value)
486
452
 
453
+ def __delete__(self, __obj: T) -> None:
487
454
  if not self.fdel:
488
- raise CustomError[AttributeError]("Can't delete attribute")
489
-
490
- if isclass(__obj):
491
- type_, __obj = __obj, None
492
- else:
493
- type_ = type(__obj)
455
+ raise AttributeError(
456
+ f'classproperty with getter "{self.__name__}" of "{__obj.__class__.__name__}" object has no deleter.'
457
+ )
494
458
 
495
- return self.fdel.__delete__(__obj, type_)(__obj) # type: ignore
459
+ self.fdel.__get__(None, type(__obj))()
496
460
 
461
+ @property
497
462
  def __name__(self) -> str:
498
463
  return self.fget.__name__
499
464
 
@@ -132,6 +132,8 @@ def spline_coeff(
132
132
  for k in range(i, length + 1):
133
133
  matrix[j][k] -= a * matrix[i][k]
134
134
 
135
+ i = 0
136
+
135
137
  for i in range(length + 1):
136
138
  if x >= px[i] and x <= px[i + 1]:
137
139
  break
@@ -148,7 +150,7 @@ def spline_coeff(
148
150
  s += (py[j] / h - h * matrix[j][length] / 6) * (x - px[i])
149
151
  s -= (py[i] / h - h * matrix[i][length] / 6) * (x - px[j])
150
152
 
151
- return s
153
+ return float(s)
152
154
 
153
155
 
154
156
  def ndigits(num: Nb) -> int:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jetpytools
3
- Version: 1.3.0
3
+ Version: 1.5.0
4
4
  Summary: Collection of stuff that's useful in general python programming
5
5
  Author: Jaded Encoding Thaumaturgy
6
6
  Author-email: jaded.encoding.thaumaturgy@gmail.com
@@ -35,4 +35,6 @@ jetpytools/utils/__init__.py
35
35
  jetpytools/utils/file.py
36
36
  jetpytools/utils/funcs.py
37
37
  jetpytools/utils/math.py
38
- jetpytools/utils/ranges.py
38
+ jetpytools/utils/ranges.py
39
+ tests/test_funcs.py
40
+ tests/test_normalize.py
@@ -7,6 +7,7 @@ show-source = True
7
7
  statistics = True
8
8
 
9
9
  [mypy]
10
+ python_version = 3.10
10
11
  ignore_missing_imports = False
11
12
  disallow_any_generics = True
12
13
  disallow_untyped_defs = True
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ from jetpytools import fallback, iterate
4
+
5
+
6
+ def test_iterate() -> None:
7
+ assert iterate(5, lambda x: x * 2, 2) == 20
8
+
9
+
10
+ def test_fallback() -> None:
11
+ assert fallback(5, 6) == 5
12
+ assert fallback(None, 6) == 6
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+ import sys
3
+
4
+ import pytest
5
+
6
+ from jetpytools import CustomOverflowError, normalize_ranges
7
+
8
+
9
+ if sys.version_info < (3, 11):
10
+ ExceptionGroup = Exception
11
+
12
+
13
+ def assert_excinfo_group_contains(
14
+ excinfo: pytest.ExceptionInfo, exception: type[Exception] # type: ignore
15
+ ) -> None:
16
+ if sys.version_info < (3, 11):
17
+ assert isinstance(excinfo.value, Exception)
18
+ else:
19
+ assert excinfo.group_contains(exception)
20
+
21
+
22
+ def test_normalize_ranges() -> None:
23
+ # Inclusive ranges
24
+ assert normalize_ranges((None, None), length=1000, exclusive=False) == [(0, 999)]
25
+ assert normalize_ranges((24, -24), length=1000, exclusive=False) == [(24, 975)]
26
+ assert normalize_ranges((-100, 950), length=1000, exclusive=False) == [(900, 950)]
27
+ assert normalize_ranges([(24, 100), (80, 150)], length=1000, exclusive=False) == [(24, 150)]
28
+ assert normalize_ranges([500], length=1000, exclusive=False) == [(500, 500)]
29
+
30
+ # Exclusive ranges
31
+ assert normalize_ranges((None, None), length=1000, exclusive=True) == [(0, 1000)]
32
+ assert normalize_ranges((24, -24), length=1000, exclusive=True) == [(24, 976)]
33
+ assert normalize_ranges((-100, 950), length=1000, exclusive=True) == [(900, 950)]
34
+ assert normalize_ranges([(24, 100), (80, 150)], length=1000, exclusive=True) == [(24, 150)]
35
+ assert normalize_ranges([500], length=1000, exclusive=True) == [(500, 501)]
36
+
37
+ # Overflow
38
+ with pytest.raises(ExceptionGroup) as excinfo:
39
+ normalize_ranges((500, 1500), length=1000)
40
+ assert_excinfo_group_contains(excinfo, CustomOverflowError)
41
+
42
+ with pytest.raises(ExceptionGroup) as excinfo:
43
+ normalize_ranges((-2000, 500), length=1000)
44
+ assert_excinfo_group_contains(excinfo, CustomOverflowError)
45
+
46
+ with pytest.raises(ExceptionGroup) as excinfo:
47
+ normalize_ranges((-2000, 2000), length=1000)
48
+ assert_excinfo_group_contains(excinfo, CustomOverflowError)
49
+
50
+ assert normalize_ranges((500, 1500), length=1000, exclusive=False, strict=False) == [(500, 999)]
51
+ assert normalize_ranges((500, 1500), length=1000, exclusive=True, strict=False) == [(500, 1000)]
52
+
53
+ assert normalize_ranges((-1500, 500), length=1000, exclusive=False, strict=False) == [(0, 500)]
54
+ assert normalize_ranges((-1500, 500), length=1000, exclusive=True, strict=False) == [(0, 500)]
55
+
56
+ assert normalize_ranges((-2000, 2000), length=1000, exclusive=False, strict=False) == [(0, 999)]
57
+ assert normalize_ranges((-2000, 2000), length=1000, exclusive=True, strict=False) == [(0, 1000)]
58
+
59
+ assert normalize_ranges((0, 0), length=1000, exclusive=False, strict=True) == [(0, 0)]
60
+
61
+ with pytest.raises(ExceptionGroup) as excinfo:
62
+ normalize_ranges((0, 0), length=1000, exclusive=True, strict=True)
63
+ assert_excinfo_group_contains(excinfo, CustomOverflowError)
File without changes
File without changes
File without changes