dycw-utilities 0.129.10__py3-none-any.whl → 0.175.17__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.
Files changed (103) hide show
  1. dycw_utilities-0.175.17.dist-info/METADATA +34 -0
  2. dycw_utilities-0.175.17.dist-info/RECORD +103 -0
  3. dycw_utilities-0.175.17.dist-info/WHEEL +4 -0
  4. dycw_utilities-0.175.17.dist-info/entry_points.txt +4 -0
  5. utilities/__init__.py +1 -1
  6. utilities/altair.py +14 -14
  7. utilities/asyncio.py +350 -819
  8. utilities/atomicwrites.py +18 -6
  9. utilities/atools.py +77 -22
  10. utilities/cachetools.py +24 -29
  11. utilities/click.py +393 -237
  12. utilities/concurrent.py +8 -11
  13. utilities/contextlib.py +216 -17
  14. utilities/contextvars.py +20 -1
  15. utilities/cryptography.py +3 -3
  16. utilities/dataclasses.py +83 -118
  17. utilities/docker.py +293 -0
  18. utilities/enum.py +26 -23
  19. utilities/errors.py +17 -3
  20. utilities/fastapi.py +29 -65
  21. utilities/fpdf2.py +3 -3
  22. utilities/functions.py +169 -416
  23. utilities/functools.py +18 -19
  24. utilities/git.py +9 -30
  25. utilities/grp.py +28 -0
  26. utilities/gzip.py +31 -0
  27. utilities/http.py +3 -2
  28. utilities/hypothesis.py +738 -589
  29. utilities/importlib.py +17 -1
  30. utilities/inflect.py +25 -0
  31. utilities/iterables.py +194 -262
  32. utilities/jinja2.py +148 -0
  33. utilities/json.py +70 -0
  34. utilities/libcst.py +38 -17
  35. utilities/lightweight_charts.py +5 -9
  36. utilities/logging.py +345 -543
  37. utilities/math.py +18 -13
  38. utilities/memory_profiler.py +11 -15
  39. utilities/more_itertools.py +200 -131
  40. utilities/operator.py +33 -29
  41. utilities/optuna.py +6 -6
  42. utilities/orjson.py +272 -137
  43. utilities/os.py +61 -4
  44. utilities/parse.py +59 -61
  45. utilities/pathlib.py +281 -40
  46. utilities/permissions.py +298 -0
  47. utilities/pickle.py +2 -2
  48. utilities/platform.py +24 -5
  49. utilities/polars.py +1214 -430
  50. utilities/polars_ols.py +1 -1
  51. utilities/postgres.py +408 -0
  52. utilities/pottery.py +113 -26
  53. utilities/pqdm.py +10 -11
  54. utilities/psutil.py +6 -57
  55. utilities/pwd.py +28 -0
  56. utilities/pydantic.py +4 -54
  57. utilities/pydantic_settings.py +240 -0
  58. utilities/pydantic_settings_sops.py +76 -0
  59. utilities/pyinstrument.py +8 -10
  60. utilities/pytest.py +227 -121
  61. utilities/pytest_plugins/__init__.py +1 -0
  62. utilities/pytest_plugins/pytest_randomly.py +23 -0
  63. utilities/pytest_plugins/pytest_regressions.py +56 -0
  64. utilities/pytest_regressions.py +26 -46
  65. utilities/random.py +13 -9
  66. utilities/re.py +58 -28
  67. utilities/redis.py +401 -550
  68. utilities/scipy.py +1 -1
  69. utilities/sentinel.py +10 -0
  70. utilities/shelve.py +4 -1
  71. utilities/shutil.py +25 -0
  72. utilities/slack_sdk.py +36 -106
  73. utilities/sqlalchemy.py +502 -473
  74. utilities/sqlalchemy_polars.py +38 -94
  75. utilities/string.py +2 -3
  76. utilities/subprocess.py +1572 -0
  77. utilities/tempfile.py +86 -4
  78. utilities/testbook.py +50 -0
  79. utilities/text.py +165 -42
  80. utilities/timer.py +37 -65
  81. utilities/traceback.py +158 -929
  82. utilities/types.py +146 -116
  83. utilities/typing.py +531 -71
  84. utilities/tzdata.py +1 -53
  85. utilities/tzlocal.py +6 -23
  86. utilities/uuid.py +43 -5
  87. utilities/version.py +27 -26
  88. utilities/whenever.py +1776 -386
  89. utilities/zoneinfo.py +84 -22
  90. dycw_utilities-0.129.10.dist-info/METADATA +0 -241
  91. dycw_utilities-0.129.10.dist-info/RECORD +0 -96
  92. dycw_utilities-0.129.10.dist-info/WHEEL +0 -4
  93. dycw_utilities-0.129.10.dist-info/licenses/LICENSE +0 -21
  94. utilities/datetime.py +0 -1409
  95. utilities/eventkit.py +0 -402
  96. utilities/loguru.py +0 -144
  97. utilities/luigi.py +0 -228
  98. utilities/period.py +0 -324
  99. utilities/pyrsistent.py +0 -89
  100. utilities/python_dotenv.py +0 -105
  101. utilities/streamlit.py +0 -105
  102. utilities/sys.py +0 -87
  103. utilities/tenacity.py +0 -145
utilities/functions.py CHANGED
@@ -1,8 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- import datetime as dt
4
- from collections.abc import Callable, Iterable, Iterator, Sequence
5
- from dataclasses import asdict, dataclass, is_dataclass
3
+ from collections.abc import Callable, Iterable, Iterator
4
+ from dataclasses import asdict, dataclass
6
5
  from functools import _lru_cache_wrapper, cached_property, partial, reduce, wraps
7
6
  from inspect import getattr_static
8
7
  from pathlib import Path
@@ -15,59 +14,26 @@ from types import (
15
14
  MethodWrapperType,
16
15
  WrapperDescriptorType,
17
16
  )
18
- from typing import (
19
- TYPE_CHECKING,
20
- Any,
21
- Generic,
22
- Literal,
23
- TypeGuard,
24
- TypeVar,
25
- cast,
26
- overload,
27
- override,
28
- )
17
+ from typing import TYPE_CHECKING, Any, Literal, TypeGuard, cast, overload, override
29
18
 
30
- from typing_extensions import ParamSpec
19
+ from whenever import Date, PlainDateTime, Time, TimeDelta, ZonedDateTime
31
20
 
32
21
  from utilities.reprlib import get_repr, get_repr_and_class
33
- from utilities.sentinel import Sentinel, sentinel
34
- from utilities.types import (
35
- Dataclass,
36
- Number,
37
- StrMapping,
38
- TCallable,
39
- TCallable1,
40
- TCallable2,
41
- TSupportsRichComparison,
42
- TupleOrStrMapping,
43
- TypeLike,
44
- )
22
+ from utilities.sentinel import Sentinel, is_sentinel, sentinel
23
+ from utilities.types import Dataclass, Number, SupportsRichComparison, TypeLike
45
24
 
46
25
  if TYPE_CHECKING:
47
- from collections.abc import Container, Hashable, Sized
48
-
49
-
50
- _P = ParamSpec("_P")
51
- _T = TypeVar("_T")
52
- _T1 = TypeVar("_T1")
53
- _T2 = TypeVar("_T2")
54
- _T3 = TypeVar("_T3")
55
- _T4 = TypeVar("_T4")
56
- _T5 = TypeVar("_T5")
57
- _U = TypeVar("_U")
58
-
59
-
60
- ##
26
+ from collections.abc import Container
61
27
 
62
28
 
63
- def apply_decorators(
64
- func: TCallable1, /, *decorators: Callable[[TCallable2], TCallable2]
65
- ) -> TCallable1:
29
+ def apply_decorators[F1: Callable, F2: Callable](
30
+ func: F1, /, *decorators: Callable[[F2], F2]
31
+ ) -> F1:
66
32
  """Apply a set of decorators to a function."""
67
33
  return reduce(_apply_decorators_one, decorators, func)
68
34
 
69
35
 
70
- def _apply_decorators_one(acc: TCallable, el: Callable[[Any], Any], /) -> TCallable:
36
+ def _apply_decorators_one[F: Callable](acc: F, el: Callable[[Any], Any], /) -> F:
71
37
  return el(acc)
72
38
 
73
39
 
@@ -125,66 +91,64 @@ class EnsureBytesError(Exception):
125
91
 
126
92
 
127
93
  @overload
128
- def ensure_class(obj: Any, cls: type[_T], /, *, nullable: bool) -> _T | None: ...
94
+ def ensure_class[T](obj: Any, cls: type[T], /, *, nullable: bool) -> T | None: ...
129
95
  @overload
130
- def ensure_class(
131
- obj: Any, cls: type[_T], /, *, nullable: Literal[False] = False
132
- ) -> _T: ...
96
+ def ensure_class[T](
97
+ obj: Any, cls: type[T], /, *, nullable: Literal[False] = False
98
+ ) -> T: ...
133
99
  @overload
134
- def ensure_class(
135
- obj: Any, cls: tuple[type[_T1], type[_T2]], /, *, nullable: bool
136
- ) -> _T1 | _T2 | None: ...
100
+ def ensure_class[T1, T2](
101
+ obj: Any, cls: tuple[type[T1], type[T2]], /, *, nullable: bool
102
+ ) -> T1 | T2 | None: ...
137
103
  @overload
138
- def ensure_class(
139
- obj: Any, cls: tuple[type[_T1], type[_T2]], /, *, nullable: Literal[False] = False
140
- ) -> _T1 | _T2: ...
104
+ def ensure_class[T1, T2](
105
+ obj: Any, cls: tuple[type[T1], type[T2]], /, *, nullable: Literal[False] = False
106
+ ) -> T1 | T2: ...
141
107
  @overload
142
- def ensure_class(
143
- obj: Any, cls: tuple[type[_T1], type[_T2], type[_T3]], /, *, nullable: bool
144
- ) -> _T1 | _T2 | _T3 | None: ...
108
+ def ensure_class[T1, T2, T3](
109
+ obj: Any, cls: tuple[type[T1], type[T2], type[T3]], /, *, nullable: bool
110
+ ) -> T1 | T2 | T3 | None: ...
145
111
  @overload
146
- def ensure_class(
112
+ def ensure_class[T1, T2, T3](
147
113
  obj: Any,
148
- cls: tuple[type[_T1], type[_T2], type[_T3]],
114
+ cls: tuple[type[T1], type[T2], type[T3]],
149
115
  /,
150
116
  *,
151
117
  nullable: Literal[False] = False,
152
- ) -> _T1 | _T2 | _T3: ...
118
+ ) -> T1 | T2 | T3: ...
153
119
  @overload
154
- def ensure_class(
155
- obj: Any,
156
- cls: tuple[type[_T1], type[_T2], type[_T3], type[_T4]],
157
- /,
158
- *,
159
- nullable: bool,
160
- ) -> _T1 | _T2 | _T3 | _T4 | None: ...
120
+ def ensure_class[T1, T2, T3, T4](
121
+ obj: Any, cls: tuple[type[T1], type[T2], type[T3], type[T4]], /, *, nullable: bool
122
+ ) -> T1 | T2 | T3 | T4 | None: ...
161
123
  @overload
162
- def ensure_class(
124
+ def ensure_class[T1, T2, T3, T4](
163
125
  obj: Any,
164
- cls: tuple[type[_T1], type[_T2], type[_T3], type[_T4]],
126
+ cls: tuple[type[T1], type[T2], type[T3], type[T4]],
165
127
  /,
166
128
  *,
167
129
  nullable: Literal[False] = False,
168
- ) -> _T1 | _T2 | _T3 | _T4: ...
130
+ ) -> T1 | T2 | T3 | T4: ...
169
131
  @overload
170
- def ensure_class(
132
+ def ensure_class[T1, T2, T3, T4, T5](
171
133
  obj: Any,
172
- cls: tuple[type[_T1], type[_T2], type[_T3], type[_T4], type[_T5]],
134
+ cls: tuple[type[T1], type[T2], type[T3], type[T4], type[T5]],
173
135
  /,
174
136
  *,
175
137
  nullable: bool,
176
- ) -> _T1 | _T2 | _T3 | _T4 | _T5 | None: ...
138
+ ) -> T1 | T2 | T3 | T4 | T5 | None: ...
177
139
  @overload
178
- def ensure_class(
140
+ def ensure_class[T1, T2, T3, T4, T5](
179
141
  obj: Any,
180
- cls: tuple[type[_T1], type[_T2], type[_T3], type[_T4], type[_T5]],
142
+ cls: tuple[type[T1], type[T2], type[T3], type[T4], type[T5]],
181
143
  /,
182
144
  *,
183
145
  nullable: Literal[False] = False,
184
- ) -> _T1 | _T2 | _T3 | _T4 | _T5: ...
146
+ ) -> T1 | T2 | T3 | T4 | T5: ...
185
147
  @overload
186
- def ensure_class(obj: Any, cls: TypeLike[_T], /, *, nullable: bool = False) -> Any: ...
187
- def ensure_class(obj: Any, cls: TypeLike[_T], /, *, nullable: bool = False) -> Any:
148
+ def ensure_class[T](
149
+ obj: Any, cls: TypeLike[T], /, *, nullable: bool = False
150
+ ) -> Any: ...
151
+ def ensure_class[T](obj: Any, cls: TypeLike[T], /, *, nullable: bool = False) -> Any:
188
152
  """Ensure an object is of the required class."""
189
153
  if isinstance(obj, cls) or ((obj is None) and nullable):
190
154
  return obj
@@ -210,13 +174,13 @@ class EnsureClassError(Exception):
210
174
 
211
175
 
212
176
  @overload
213
- def ensure_date(obj: Any, /, *, nullable: bool) -> dt.date | None: ...
177
+ def ensure_date(obj: Any, /, *, nullable: bool) -> Date | None: ...
214
178
  @overload
215
- def ensure_date(obj: Any, /, *, nullable: Literal[False] = False) -> dt.date: ...
216
- def ensure_date(obj: Any, /, *, nullable: bool = False) -> dt.date | None:
179
+ def ensure_date(obj: Any, /, *, nullable: Literal[False] = False) -> Date: ...
180
+ def ensure_date(obj: Any, /, *, nullable: bool = False) -> Date | None:
217
181
  """Ensure an object is a date."""
218
182
  try:
219
- return ensure_class(obj, dt.date, nullable=nullable)
183
+ return ensure_class(obj, Date, nullable=nullable)
220
184
  except EnsureClassError as error:
221
185
  raise EnsureDateError(obj=error.obj, nullable=nullable) from None
222
186
 
@@ -234,33 +198,6 @@ class EnsureDateError(Exception):
234
198
  ##
235
199
 
236
200
 
237
- @overload
238
- def ensure_datetime(obj: Any, /, *, nullable: bool) -> dt.datetime | None: ...
239
- @overload
240
- def ensure_datetime(
241
- obj: Any, /, *, nullable: Literal[False] = False
242
- ) -> dt.datetime: ...
243
- def ensure_datetime(obj: Any, /, *, nullable: bool = False) -> dt.datetime | None:
244
- """Ensure an object is a datetime."""
245
- try:
246
- return ensure_class(obj, dt.datetime, nullable=nullable)
247
- except EnsureClassError as error:
248
- raise EnsureDateTimeError(obj=error.obj, nullable=nullable) from None
249
-
250
-
251
- @dataclass(kw_only=True, slots=True)
252
- class EnsureDateTimeError(Exception):
253
- obj: Any
254
- nullable: bool
255
-
256
- @override
257
- def __str__(self) -> str:
258
- return _make_error_msg(self.obj, "a datetime", nullable=self.nullable)
259
-
260
-
261
- ##
262
-
263
-
264
201
  @overload
265
202
  def ensure_float(obj: Any, /, *, nullable: bool) -> float | None: ...
266
203
  @overload
@@ -286,25 +223,6 @@ class EnsureFloatError(Exception):
286
223
  ##
287
224
 
288
225
 
289
- def ensure_hashable(obj: Any, /) -> Hashable:
290
- """Ensure an object is hashable."""
291
- if is_hashable(obj):
292
- return obj
293
- raise EnsureHashableError(obj=obj)
294
-
295
-
296
- @dataclass(kw_only=True, slots=True)
297
- class EnsureHashableError(Exception):
298
- obj: Any
299
-
300
- @override
301
- def __str__(self) -> str:
302
- return _make_error_msg(self.obj, "hashable")
303
-
304
-
305
- ##
306
-
307
-
308
226
  @overload
309
227
  def ensure_int(obj: Any, /, *, nullable: bool) -> int | None: ...
310
228
  @overload
@@ -331,16 +249,16 @@ class EnsureIntError(Exception):
331
249
 
332
250
 
333
251
  @overload
334
- def ensure_member(
335
- obj: Any, container: Container[_T], /, *, nullable: bool
336
- ) -> _T | None: ...
252
+ def ensure_member[T](
253
+ obj: Any, container: Container[T], /, *, nullable: bool
254
+ ) -> T | None: ...
337
255
  @overload
338
- def ensure_member(
339
- obj: Any, container: Container[_T], /, *, nullable: Literal[False] = False
340
- ) -> _T: ...
341
- def ensure_member(
342
- obj: Any, container: Container[_T], /, *, nullable: bool = False
343
- ) -> _T | None:
256
+ def ensure_member[T](
257
+ obj: Any, container: Container[T], /, *, nullable: Literal[False] = False
258
+ ) -> T: ...
259
+ def ensure_member[T](
260
+ obj: Any, container: Container[T], /, *, nullable: bool = False
261
+ ) -> T | None:
344
262
  """Ensure an object is a member of the container."""
345
263
  if (obj in container) or ((obj is None) and nullable):
346
264
  return obj
@@ -363,7 +281,7 @@ class EnsureMemberError(Exception):
363
281
  ##
364
282
 
365
283
 
366
- def ensure_not_none(obj: _T | None, /, *, desc: str = "Object") -> _T:
284
+ def ensure_not_none[T](obj: T | None, /, *, desc: str = "Object") -> T:
367
285
  """Ensure an object is not None."""
368
286
  if obj is None:
369
287
  raise EnsureNotNoneError(desc=desc)
@@ -432,39 +350,30 @@ class EnsurePathError(Exception):
432
350
  ##
433
351
 
434
352
 
435
- def ensure_sized(obj: Any, /) -> Sized:
436
- """Ensure an object is sized."""
437
- if is_sized(obj):
438
- return obj
439
- raise EnsureSizedError(obj=obj)
440
-
441
-
442
- @dataclass(kw_only=True, slots=True)
443
- class EnsureSizedError(Exception):
444
- obj: Any
445
-
446
- @override
447
- def __str__(self) -> str:
448
- return _make_error_msg(self.obj, "sized")
449
-
450
-
451
- ##
452
-
453
-
454
- def ensure_sized_not_str(obj: Any, /) -> Sized:
455
- """Ensure an object is sized, but not a string."""
456
- if is_sized_not_str(obj):
457
- return obj
458
- raise EnsureSizedNotStrError(obj=obj)
353
+ @overload
354
+ def ensure_plain_date_time(obj: Any, /, *, nullable: bool) -> PlainDateTime | None: ...
355
+ @overload
356
+ def ensure_plain_date_time(
357
+ obj: Any, /, *, nullable: Literal[False] = False
358
+ ) -> PlainDateTime: ...
359
+ def ensure_plain_date_time(
360
+ obj: Any, /, *, nullable: bool = False
361
+ ) -> PlainDateTime | None:
362
+ """Ensure an object is a plain date-time."""
363
+ try:
364
+ return ensure_class(obj, PlainDateTime, nullable=nullable)
365
+ except EnsureClassError as error:
366
+ raise EnsurePlainDateTimeError(obj=error.obj, nullable=nullable) from None
459
367
 
460
368
 
461
369
  @dataclass(kw_only=True, slots=True)
462
- class EnsureSizedNotStrError(Exception):
370
+ class EnsurePlainDateTimeError(Exception):
463
371
  obj: Any
372
+ nullable: bool
464
373
 
465
374
  @override
466
375
  def __str__(self) -> str:
467
- return _make_error_msg(self.obj, "sized and not a string")
376
+ return _make_error_msg(self.obj, "a plain date-time", nullable=self.nullable)
468
377
 
469
378
 
470
379
  ##
@@ -496,13 +405,13 @@ class EnsureStrError(Exception):
496
405
 
497
406
 
498
407
  @overload
499
- def ensure_time(obj: Any, /, *, nullable: bool) -> dt.time | None: ...
408
+ def ensure_time(obj: Any, /, *, nullable: bool) -> Time | None: ...
500
409
  @overload
501
- def ensure_time(obj: Any, /, *, nullable: Literal[False] = False) -> dt.time: ...
502
- def ensure_time(obj: Any, /, *, nullable: bool = False) -> dt.time | None:
410
+ def ensure_time(obj: Any, /, *, nullable: Literal[False] = False) -> Time: ...
411
+ def ensure_time(obj: Any, /, *, nullable: bool = False) -> Time | None:
503
412
  """Ensure an object is a time."""
504
413
  try:
505
- return ensure_class(obj, dt.time, nullable=nullable)
414
+ return ensure_class(obj, Time, nullable=nullable)
506
415
  except EnsureClassError as error:
507
416
  raise EnsureTimeError(obj=error.obj, nullable=nullable) from None
508
417
 
@@ -521,15 +430,15 @@ class EnsureTimeError(Exception):
521
430
 
522
431
 
523
432
  @overload
524
- def ensure_timedelta(obj: Any, /, *, nullable: bool) -> dt.timedelta | None: ...
433
+ def ensure_time_delta(obj: Any, /, *, nullable: bool) -> TimeDelta | None: ...
525
434
  @overload
526
- def ensure_timedelta(
435
+ def ensure_time_delta(
527
436
  obj: Any, /, *, nullable: Literal[False] = False
528
- ) -> dt.timedelta: ...
529
- def ensure_timedelta(obj: Any, /, *, nullable: bool = False) -> dt.timedelta | None:
437
+ ) -> TimeDelta: ...
438
+ def ensure_time_delta(obj: Any, /, *, nullable: bool = False) -> TimeDelta | None:
530
439
  """Ensure an object is a timedelta."""
531
440
  try:
532
- return ensure_class(obj, dt.timedelta, nullable=nullable)
441
+ return ensure_class(obj, TimeDelta, nullable=nullable)
533
442
  except EnsureClassError as error:
534
443
  raise EnsureTimeDeltaError(obj=error.obj, nullable=nullable) from None
535
444
 
@@ -541,13 +450,42 @@ class EnsureTimeDeltaError(Exception):
541
450
 
542
451
  @override
543
452
  def __str__(self) -> str:
544
- return _make_error_msg(self.obj, "a timedelta", nullable=self.nullable)
453
+ return _make_error_msg(self.obj, "a time-delta", nullable=self.nullable)
545
454
 
546
455
 
547
456
  ##
548
457
 
549
458
 
550
- def first(pair: tuple[_T, Any], /) -> _T:
459
+ @overload
460
+ def ensure_zoned_date_time(obj: Any, /, *, nullable: bool) -> ZonedDateTime | None: ...
461
+ @overload
462
+ def ensure_zoned_date_time(
463
+ obj: Any, /, *, nullable: Literal[False] = False
464
+ ) -> ZonedDateTime: ...
465
+ def ensure_zoned_date_time(
466
+ obj: Any, /, *, nullable: bool = False
467
+ ) -> ZonedDateTime | None:
468
+ """Ensure an object is a zoned date-time."""
469
+ try:
470
+ return ensure_class(obj, ZonedDateTime, nullable=nullable)
471
+ except EnsureClassError as error:
472
+ raise EnsureZonedDateTimeError(obj=error.obj, nullable=nullable) from None
473
+
474
+
475
+ @dataclass(kw_only=True, slots=True)
476
+ class EnsureZonedDateTimeError(Exception):
477
+ obj: Any
478
+ nullable: bool
479
+
480
+ @override
481
+ def __str__(self) -> str:
482
+ return _make_error_msg(self.obj, "a zoned date-time", nullable=self.nullable)
483
+
484
+
485
+ ##
486
+
487
+
488
+ def first[T](pair: tuple[T, Any], /) -> T:
551
489
  """Get the first element in a pair."""
552
490
  return pair[0]
553
491
 
@@ -556,10 +494,10 @@ def first(pair: tuple[_T, Any], /) -> _T:
556
494
 
557
495
 
558
496
  @overload
559
- def get_class(obj: type[_T], /) -> type[_T]: ...
497
+ def get_class[T](obj: type[T], /) -> type[T]: ...
560
498
  @overload
561
- def get_class(obj: _T, /) -> type[_T]: ...
562
- def get_class(obj: _T | type[_T], /) -> type[_T]:
499
+ def get_class[T](obj: T, /) -> type[T]: ...
500
+ def get_class[T](obj: T | type[T], /) -> type[T]:
563
501
  """Get the class of an object, unless it is already a class."""
564
502
  return obj if isinstance(obj, type) else type(obj)
565
503
 
@@ -623,7 +561,7 @@ def get_func_qualname(obj: Callable[..., Any], /) -> str:
623
561
  ##
624
562
 
625
563
 
626
- def identity(obj: _T, /) -> _T:
564
+ def identity[T](obj: T, /) -> T:
627
565
  """Return the object itself."""
628
566
  return obj
629
567
 
@@ -631,65 +569,7 @@ def identity(obj: _T, /) -> _T:
631
569
  ##
632
570
 
633
571
 
634
- def is_dataclass_class(obj: Any, /) -> TypeGuard[type[Dataclass]]:
635
- """Check if an object is a dataclass."""
636
- return isinstance(obj, type) and is_dataclass(obj)
637
-
638
-
639
- ##
640
-
641
-
642
- def is_dataclass_instance(obj: Any, /) -> TypeGuard[Dataclass]:
643
- """Check if an object is an instance of a dataclass."""
644
- return (not isinstance(obj, type)) and is_dataclass(obj)
645
-
646
-
647
- ##
648
-
649
-
650
- def is_hashable(obj: Any, /) -> TypeGuard[Hashable]:
651
- """Check if an object is hashable."""
652
- try:
653
- _ = hash(obj)
654
- except TypeError:
655
- return False
656
- return True
657
-
658
-
659
- ##
660
-
661
-
662
- @overload
663
- def is_iterable_of(obj: Any, cls: type[_T], /) -> TypeGuard[Iterable[_T]]: ...
664
- @overload
665
- def is_iterable_of(obj: Any, cls: tuple[type[_T1]], /) -> TypeGuard[Iterable[_T1]]: ...
666
- @overload
667
- def is_iterable_of(
668
- obj: Any, cls: tuple[type[_T1], type[_T2]], /
669
- ) -> TypeGuard[Iterable[_T1 | _T2]]: ...
670
- @overload
671
- def is_iterable_of(
672
- obj: Any, cls: tuple[type[_T1], type[_T2], type[_T3]], /
673
- ) -> TypeGuard[Iterable[_T1 | _T2 | _T3]]: ...
674
- @overload
675
- def is_iterable_of(
676
- obj: Any, cls: tuple[type[_T1], type[_T2], type[_T3], type[_T4]], /
677
- ) -> TypeGuard[Iterable[_T1 | _T2 | _T3 | _T4]]: ...
678
- @overload
679
- def is_iterable_of(
680
- obj: Any, cls: tuple[type[_T1], type[_T2], type[_T3], type[_T4], type[_T5]], /
681
- ) -> TypeGuard[Iterable[_T1 | _T2 | _T3 | _T4 | _T5]]: ...
682
- @overload
683
- def is_iterable_of(obj: Any, cls: TypeLike[_T], /) -> TypeGuard[Iterable[_T]]: ...
684
- def is_iterable_of(obj: Any, cls: TypeLike[_T], /) -> TypeGuard[Iterable[_T]]:
685
- """Check if an object is a iterable of tuple or string mappings."""
686
- return isinstance(obj, Iterable) and all(map(make_isinstance(cls), obj))
687
-
688
-
689
- ##
690
-
691
-
692
- def is_none(obj: Any, /) -> bool:
572
+ def is_none(obj: Any, /) -> TypeGuard[None]:
693
573
  """Check if an object is `None`."""
694
574
  return obj is None
695
575
 
@@ -705,131 +585,9 @@ def is_not_none(obj: Any, /) -> bool:
705
585
  ##
706
586
 
707
587
 
708
- @overload
709
- def is_sequence_of(obj: Any, cls: type[_T], /) -> TypeGuard[Sequence[_T]]: ...
710
- @overload
711
- def is_sequence_of(obj: Any, cls: tuple[type[_T1]], /) -> TypeGuard[Sequence[_T1]]: ...
712
- @overload
713
- def is_sequence_of(
714
- obj: Any, cls: tuple[type[_T1], type[_T2]], /
715
- ) -> TypeGuard[Sequence[_T1 | _T2]]: ...
716
- @overload
717
- def is_sequence_of(
718
- obj: Any, cls: tuple[type[_T1], type[_T2], type[_T3]], /
719
- ) -> TypeGuard[Sequence[_T1 | _T2 | _T3]]: ...
720
- @overload
721
- def is_sequence_of(
722
- obj: Any, cls: tuple[type[_T1], type[_T2], type[_T3], type[_T4]], /
723
- ) -> TypeGuard[Sequence[_T1 | _T2 | _T3 | _T4]]: ...
724
- @overload
725
- def is_sequence_of(
726
- obj: Any, cls: tuple[type[_T1], type[_T2], type[_T3], type[_T4], type[_T5]], /
727
- ) -> TypeGuard[Sequence[_T1 | _T2 | _T3 | _T4 | _T5]]: ...
728
- @overload
729
- def is_sequence_of(obj: Any, cls: TypeLike[_T], /) -> TypeGuard[Sequence[_T]]: ...
730
- def is_sequence_of(obj: Any, cls: TypeLike[_T], /) -> TypeGuard[Sequence[_T]]:
731
- """Check if an object is a sequence of tuple or string mappings."""
732
- return isinstance(obj, Sequence) and is_iterable_of(obj, cls)
733
-
734
-
735
- ##
736
-
737
-
738
- def is_sequence_of_tuple_or_str_mapping(
739
- obj: Any, /
740
- ) -> TypeGuard[Sequence[TupleOrStrMapping]]:
741
- """Check if an object is a sequence of tuple or string mappings."""
742
- return isinstance(obj, Sequence) and all(map(is_tuple_or_str_mapping, obj))
743
-
744
-
745
- ##
746
-
747
-
748
- def is_sized(obj: Any, /) -> TypeGuard[Sized]:
749
- """Check if an object is sized."""
750
- try:
751
- _ = len(obj)
752
- except TypeError:
753
- return False
754
- return True
755
-
756
-
757
- ##
758
-
759
-
760
- def is_sized_not_str(obj: Any, /) -> TypeGuard[Sized]:
761
- """Check if an object is sized, but not a string."""
762
- return is_sized(obj) and not isinstance(obj, str)
763
-
764
-
765
- ##
766
-
767
-
768
- def is_string_mapping(obj: Any, /) -> TypeGuard[StrMapping]:
769
- """Check if an object is a string mapping."""
770
- return isinstance(obj, dict) and is_iterable_of(obj, str)
771
-
772
-
773
- ##
774
-
775
-
776
- def is_tuple(obj: Any, /) -> TypeGuard[tuple[Any, ...]]:
777
- """Check if an object is a tuple or string mapping."""
778
- return make_isinstance(tuple)(obj)
779
-
780
-
781
- ##
782
-
783
-
784
- def is_tuple_or_str_mapping(obj: Any, /) -> TypeGuard[TupleOrStrMapping]:
785
- """Check if an object is a tuple or string mapping."""
786
- return is_tuple(obj) or is_string_mapping(obj)
787
-
788
-
789
- ##
790
-
791
-
792
- @overload
793
- def make_isinstance(cls: type[_T], /) -> Callable[[Any], TypeGuard[_T]]: ...
794
- @overload
795
- def make_isinstance(cls: tuple[type[_T1]], /) -> Callable[[Any], TypeGuard[_T1]]: ...
796
- @overload
797
- def make_isinstance(
798
- cls: tuple[type[_T1], type[_T2]], /
799
- ) -> Callable[[Any], TypeGuard[_T1 | _T2]]: ...
800
- @overload
801
- def make_isinstance(
802
- cls: tuple[type[_T1], type[_T2], type[_T3]], /
803
- ) -> Callable[[Any], TypeGuard[_T1 | _T2 | _T3]]: ...
804
- @overload
805
- def make_isinstance(
806
- cls: tuple[type[_T1], type[_T2], type[_T3], type[_T4]], /
807
- ) -> Callable[[Any], TypeGuard[_T1 | _T2 | _T3 | _T4]]: ...
808
- @overload
809
- def make_isinstance(
810
- cls: tuple[type[_T1], type[_T2], type[_T3], type[_T4], type[_T5]], /
811
- ) -> Callable[[Any], TypeGuard[_T1 | _T2 | _T3 | _T4 | _T5]]: ...
812
- @overload
813
- def make_isinstance(cls: TypeLike[_T], /) -> Callable[[Any], TypeGuard[_T]]: ...
814
- def make_isinstance(cls: TypeLike[_T], /) -> Callable[[Any], TypeGuard[_T]]:
815
- """Make a curried `isinstance` function."""
816
- return partial(_make_instance_core, cls=cls)
817
-
818
-
819
- def _make_instance_core(obj: Any, /, *, cls: TypeLike[_T]) -> TypeGuard[_T]:
820
- return isinstance(obj, cls)
821
-
822
-
823
- ##
824
-
825
-
826
- def map_object(
827
- func: Callable[[Any], Any],
828
- obj: _T,
829
- /,
830
- *,
831
- before: Callable[[Any], Any] | None = None,
832
- ) -> _T:
588
+ def map_object[T](
589
+ func: Callable[[Any], Any], obj: T, /, *, before: Callable[[Any], Any] | None = None
590
+ ) -> T:
833
591
  """Map a function over an object, across a variety of structures."""
834
592
  if before is not None:
835
593
  obj = before(obj)
@@ -850,22 +608,19 @@ def map_object(
850
608
 
851
609
 
852
610
  @overload
853
- def min_nullable(
854
- iterable: Iterable[TSupportsRichComparison | None], /, *, default: Sentinel = ...
855
- ) -> TSupportsRichComparison: ...
611
+ def min_nullable[T: SupportsRichComparison](
612
+ iterable: Iterable[T | None], /, *, default: Sentinel = ...
613
+ ) -> T: ...
856
614
  @overload
857
- def min_nullable(
858
- iterable: Iterable[TSupportsRichComparison | None], /, *, default: _U = ...
859
- ) -> TSupportsRichComparison | _U: ...
860
- def min_nullable(
861
- iterable: Iterable[TSupportsRichComparison | None],
862
- /,
863
- *,
864
- default: _U | Sentinel = sentinel,
865
- ) -> TSupportsRichComparison | _U:
615
+ def min_nullable[T: SupportsRichComparison, U](
616
+ iterable: Iterable[T | None], /, *, default: U = ...
617
+ ) -> T | U: ...
618
+ def min_nullable[T: SupportsRichComparison, U](
619
+ iterable: Iterable[T | None], /, *, default: U | Sentinel = sentinel
620
+ ) -> T | U:
866
621
  """Compute the minimum of a set of values; ignoring nulls."""
867
622
  values = (i for i in iterable if i is not None)
868
- if isinstance(default, Sentinel):
623
+ if is_sentinel(default):
869
624
  try:
870
625
  return min(values)
871
626
  except ValueError:
@@ -874,8 +629,8 @@ def min_nullable(
874
629
 
875
630
 
876
631
  @dataclass(kw_only=True, slots=True)
877
- class MinNullableError(Exception, Generic[TSupportsRichComparison]):
878
- values: Iterable[TSupportsRichComparison]
632
+ class MinNullableError[T: SupportsRichComparison](Exception):
633
+ values: Iterable[T]
879
634
 
880
635
  @override
881
636
  def __str__(self) -> str:
@@ -883,22 +638,19 @@ class MinNullableError(Exception, Generic[TSupportsRichComparison]):
883
638
 
884
639
 
885
640
  @overload
886
- def max_nullable(
887
- iterable: Iterable[TSupportsRichComparison | None], /, *, default: Sentinel = ...
888
- ) -> TSupportsRichComparison: ...
641
+ def max_nullable[T: SupportsRichComparison](
642
+ iterable: Iterable[T | None], /, *, default: Sentinel = ...
643
+ ) -> T: ...
889
644
  @overload
890
- def max_nullable(
891
- iterable: Iterable[TSupportsRichComparison | None], /, *, default: _U = ...
892
- ) -> TSupportsRichComparison | _U: ...
893
- def max_nullable(
894
- iterable: Iterable[TSupportsRichComparison | None],
895
- /,
896
- *,
897
- default: _U | Sentinel = sentinel,
898
- ) -> TSupportsRichComparison | _U:
645
+ def max_nullable[T: SupportsRichComparison, U](
646
+ iterable: Iterable[T | None], /, *, default: U = ...
647
+ ) -> T | U: ...
648
+ def max_nullable[T: SupportsRichComparison, U](
649
+ iterable: Iterable[T | None], /, *, default: U | Sentinel = sentinel
650
+ ) -> T | U:
899
651
  """Compute the maximum of a set of values; ignoring nulls."""
900
652
  values = (i for i in iterable if i is not None)
901
- if isinstance(default, Sentinel):
653
+ if is_sentinel(default):
902
654
  try:
903
655
  return max(values)
904
656
  except ValueError:
@@ -907,7 +659,7 @@ def max_nullable(
907
659
 
908
660
 
909
661
  @dataclass(kw_only=True, slots=True)
910
- class MaxNullableError(Exception, Generic[TSupportsRichComparison]):
662
+ class MaxNullableError[TSupportsRichComparison](Exception):
911
663
  values: Iterable[TSupportsRichComparison]
912
664
 
913
665
  @override
@@ -918,11 +670,11 @@ class MaxNullableError(Exception, Generic[TSupportsRichComparison]):
918
670
  ##
919
671
 
920
672
 
921
- def not_func(func: Callable[_P, bool], /) -> Callable[_P, bool]:
673
+ def not_func[**P](func: Callable[P, bool], /) -> Callable[P, bool]:
922
674
  """Lift a boolean-valued function to return its conjugation."""
923
675
 
924
676
  @wraps(func)
925
- def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> bool:
677
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> bool:
926
678
  return not func(*args, **kwargs)
927
679
 
928
680
  return wrapped
@@ -931,7 +683,7 @@ def not_func(func: Callable[_P, bool], /) -> Callable[_P, bool]:
931
683
  ##
932
684
 
933
685
 
934
- def second(pair: tuple[Any, _U], /) -> _U:
686
+ def second[U](pair: tuple[Any, U], /) -> U:
935
687
  """Get the second element in a pair."""
936
688
  return pair[1]
937
689
 
@@ -939,6 +691,21 @@ def second(pair: tuple[Any, _U], /) -> _U:
939
691
  ##
940
692
 
941
693
 
694
+ def skip_if_optimize[**P](func: Callable[P, None], /) -> Callable[P, None]:
695
+ """Skip a function if we are in the optimized mode."""
696
+ if __debug__: # pragma: no cover
697
+ return func
698
+
699
+ @wraps(func)
700
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> None:
701
+ _ = (args, kwargs)
702
+
703
+ return wrapped
704
+
705
+
706
+ ##
707
+
708
+
942
709
  def yield_object_attributes(
943
710
  obj: Any,
944
711
  /,
@@ -988,19 +755,17 @@ __all__ = [
988
755
  "EnsureBytesError",
989
756
  "EnsureClassError",
990
757
  "EnsureDateError",
991
- "EnsureDateTimeError",
992
758
  "EnsureFloatError",
993
- "EnsureHashableError",
994
759
  "EnsureIntError",
995
760
  "EnsureMemberError",
996
761
  "EnsureNotNoneError",
997
762
  "EnsureNumberError",
998
763
  "EnsurePathError",
999
- "EnsureSizedError",
1000
- "EnsureSizedNotStrError",
764
+ "EnsurePlainDateTimeError",
1001
765
  "EnsureStrError",
1002
766
  "EnsureTimeDeltaError",
1003
767
  "EnsureTimeError",
768
+ "EnsureZonedDateTimeError",
1004
769
  "MaxNullableError",
1005
770
  "MinNullableError",
1006
771
  "apply_decorators",
@@ -1008,43 +773,31 @@ __all__ = [
1008
773
  "ensure_bytes",
1009
774
  "ensure_class",
1010
775
  "ensure_date",
1011
- "ensure_datetime",
1012
776
  "ensure_float",
1013
- "ensure_hashable",
1014
777
  "ensure_int",
1015
778
  "ensure_member",
1016
779
  "ensure_not_none",
1017
780
  "ensure_number",
1018
781
  "ensure_path",
1019
- "ensure_sized",
1020
- "ensure_sized_not_str",
782
+ "ensure_plain_date_time",
1021
783
  "ensure_str",
1022
784
  "ensure_time",
1023
- "ensure_timedelta",
785
+ "ensure_time_delta",
786
+ "ensure_zoned_date_time",
1024
787
  "first",
1025
788
  "get_class",
1026
789
  "get_class_name",
1027
790
  "get_func_name",
1028
791
  "get_func_qualname",
1029
792
  "identity",
1030
- "is_dataclass_class",
1031
- "is_dataclass_instance",
1032
- "is_hashable",
1033
- "is_iterable_of",
1034
793
  "is_none",
1035
794
  "is_not_none",
1036
- "is_sequence_of_tuple_or_str_mapping",
1037
- "is_sized",
1038
- "is_sized_not_str",
1039
- "is_string_mapping",
1040
- "is_tuple",
1041
- "is_tuple_or_str_mapping",
1042
- "make_isinstance",
1043
795
  "map_object",
1044
796
  "max_nullable",
1045
797
  "min_nullable",
1046
798
  "not_func",
1047
799
  "second",
800
+ "skip_if_optimize",
1048
801
  "yield_object_attributes",
1049
802
  "yield_object_cached_properties",
1050
803
  "yield_object_properties",