jetpytools 1.4.0__py3-none-any.whl → 1.5.0__py3-none-any.whl

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.

jetpytools/_metadata.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Collection of stuff that's useful in general python programming"""
2
2
 
3
- __version__ = '1.4.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__
@@ -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:
jetpytools/types/file.py CHANGED
@@ -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]
jetpytools/utils/math.py CHANGED
@@ -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.4.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,5 +1,5 @@
1
1
  jetpytools/__init__.py,sha256=FSVZdj69oy4mBXd6OXiRHrUhaSc4Exo1pQHBlXycV98,214
2
- jetpytools/_metadata.py,sha256=v1lmWTzb06HMM9cWuvZNc6UWkUsfNUm_rmuwcgdFavs,414
2
+ jetpytools/_metadata.py,sha256=fESIm2gU509vtKESX3rl2qKEyN-tUKshySopOq_jWVc,414
3
3
  jetpytools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  jetpytools/enums/__init__.py,sha256=5n6Cu8Yb9N6hIa_YTsyy_s0cCgCnh0vDb-NyXK2RwV0,81
5
5
  jetpytools/enums/base.py,sha256=m3jJ6rJwDm4vp2-qMsuRH-Ll2mQDXA-wkmhw4xaafTM,2003
@@ -11,13 +11,13 @@ jetpytools/exceptions/file.py,sha256=QwhUFAoG3NsFFYuPe5O_I6K969CzlrTCv3RTrfzx8B0
11
11
  jetpytools/exceptions/generic.py,sha256=kMj5lR3ifHk3uNhpxN6Lu4Am0vi7E6TfQezkZulqhaQ,1482
12
12
  jetpytools/exceptions/module.py,sha256=drkcpa8hcE7Ee20N15j3qsX_grl8a3Jjv10XJ3xtDmE,1207
13
13
  jetpytools/functions/__init__.py,sha256=CeDfQrPCYqjiXyCoZ6jcbfM2d7KmRM11lBSxUK2wl4g,127
14
- jetpytools/functions/funcs.py,sha256=EMoJ_h2tl8Cgr1PXAsmRvgaOY5YL7AiEnGGo3-H76tk,3797
15
- jetpytools/functions/normalize.py,sha256=2BACok7TAN5ldANyYrbGhUpOkj84siApAYy6ndlhUfI,6316
14
+ jetpytools/functions/funcs.py,sha256=ZciVD2WbMo-21IzLkAjxv0WNnmikgIgTQ9O96JUzpa8,5359
15
+ jetpytools/functions/normalize.py,sha256=acn13f_yt6zKhkSFNxiS_4ZQ6Dy4tm1iBhobLS2KpTY,8654
16
16
  jetpytools/functions/other.py,sha256=TRz91spvdYJUh9vKe3Kuw6xZfSEJvrQs1mZVg7SyYmY,413
17
17
  jetpytools/types/__init__.py,sha256=yDT-PYTTzH6DyHsQcKvOy1jrCPmUQRKrjCM3apd0kNw,294
18
18
  jetpytools/types/builtins.py,sha256=hndNUoxBeFx9kgSj-6Lm8rjQH0a9lkrqRqPDObr0pbk,1969
19
19
  jetpytools/types/check.py,sha256=Ivf_JkVLG9OgiKXYjq-8azoROLjJvhNNqPq_KDIiOkI,971
20
- jetpytools/types/file.py,sha256=j46SCbdhus2wtxddAGSCn6V4nJdWWwpnAfW1K3P3ewg,6205
20
+ jetpytools/types/file.py,sha256=PpeKPVKu6NDZexmy-3UeCVIdaPPwMUkhhgtaezG20H4,6349
21
21
  jetpytools/types/funcs.py,sha256=9qONnDWdpqvRj7vL3W9BLwWeGyQipXQgxOaPjqpQ1M4,3119
22
22
  jetpytools/types/generic.py,sha256=sAyBwGVG5FZ-6HVpfRuczov_6zQ_Uyoi0QWnR2iMm9Q,1128
23
23
  jetpytools/types/supports.py,sha256=--VZ-iCUiv-a6K8n-H8-6hSxHjrvdYg9mCLhr_lRplo,3051
@@ -25,10 +25,10 @@ jetpytools/types/utils.py,sha256=eT3yoFUEQy4vKTyLDMp_6Ixy7woyF4afWgiLhS-Vr38,210
25
25
  jetpytools/utils/__init__.py,sha256=v5Bkl43-OBWlXx9OWpZoGH-QMaYNsPIi4vfuhC13ZLI,163
26
26
  jetpytools/utils/file.py,sha256=9GhMGJ5D7CpvUFnvnwPFMloYTeIX-AJ7aKYKdoJQ374,10455
27
27
  jetpytools/utils/funcs.py,sha256=ZuLz63kveBY1CLlBexEqmrQiGC67dY4gaXdNU3CeBMw,898
28
- jetpytools/utils/math.py,sha256=lNTriLtTXgqN82DVSXJ5KUbhsWgxXoJ5eNGIZ0-_r_c,3335
28
+ jetpytools/utils/math.py,sha256=r60dD7tWx3cgMEdPQDfvRH48JMKhf_XK0xA1kpnfQi4,3353
29
29
  jetpytools/utils/ranges.py,sha256=dL3WG235Pz9sk5su8A0VdwVf_oSt6obR7R_JNwLyCjQ,2572
30
- jetpytools-1.4.0.dist-info/licenses/LICENSE,sha256=l0PN-qDtXcgOB5aXP_nSUsvCK5V3o9pQCGsTzyZhKL0,1071
31
- jetpytools-1.4.0.dist-info/METADATA,sha256=vNcju4cgq0KyWoml7dd6q6QWq8KCvUIQT-DciIXtYe8,1485
32
- jetpytools-1.4.0.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
33
- jetpytools-1.4.0.dist-info/top_level.txt,sha256=Iy4HjIta33ADJxN9Nyt5t5jRIfotEkZkQcOSw4eG8Cs,11
34
- jetpytools-1.4.0.dist-info/RECORD,,
30
+ jetpytools-1.5.0.dist-info/licenses/LICENSE,sha256=l0PN-qDtXcgOB5aXP_nSUsvCK5V3o9pQCGsTzyZhKL0,1071
31
+ jetpytools-1.5.0.dist-info/METADATA,sha256=SvNo1h_KF5ng6ZkVQlaagXRiRmPVarF8l1OtQ80rvNM,1485
32
+ jetpytools-1.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
+ jetpytools-1.5.0.dist-info/top_level.txt,sha256=Iy4HjIta33ADJxN9Nyt5t5jRIfotEkZkQcOSw4eG8Cs,11
34
+ jetpytools-1.5.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5