dycw-utilities 0.163.1__py3-none-any.whl → 0.164.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.163.1
3
+ Version: 0.164.1
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=H6bv_6LaATx2o2hoGm4VlmfBe0l3DJHbz7hkL80krDQ,60
1
+ utilities/__init__.py,sha256=MvCrVmUm07-n3CLSgsov6yvmEZo6n9Jy9H_ppTFtrsI,60
2
2
  utilities/aeventkit.py,sha256=ddoleSwW9zdc2tjX5Ge0pMKtYwV_JMxhHYOxnWX2AGM,12609
3
3
  utilities/altair.py,sha256=92E2lCdyHY4Zb-vCw6rEJIsWdKipuu-Tu2ab1ufUfAk,9079
4
4
  utilities/asyncio.py,sha256=PUedzQ5deqlSECQ33sam9cRzI9TnygHz3FdOqWJWPTM,15288
@@ -11,12 +11,12 @@ utilities/contextlib.py,sha256=m2D5bwvtCZLJcJ3IwVqyErYODuwJ1gLrT2UfATAQl-w,7435
11
11
  utilities/contextvars.py,sha256=J8OhC7jqozAGYOCe2KUWysbPXNGe5JYz3HfaY_mIs08,883
12
12
  utilities/cryptography.py,sha256=5PFrzsNUGHay91dFgYnDKwYprXxahrBqztmUqViRzBk,956
13
13
  utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
14
- utilities/dataclasses.py,sha256=wGfQtopYZCwvUxNxLOlVNFlhK8Q0_aQbKJ0rWfT-fEc,32482
14
+ utilities/dataclasses.py,sha256=xbU3QN1GFy7RC6hIJRZIeUZm7YRlodrgEWmahWG6k2g,32465
15
15
  utilities/enum.py,sha256=5l6pwZD1cjSlVW4ss-zBPspWvrbrYrdtJWcg6f5_J5w,5781
16
16
  utilities/errors.py,sha256=mFlDGSM0LI1jZ1pbqwLAH3ttLZ2JVIxyZLojw8tGVZU,1479
17
17
  utilities/fastapi.py,sha256=TqyKvBjiMS594sXPjrz-KRTLMb3l3D3rZ1zAYV7GfOk,1454
18
18
  utilities/fpdf2.py,sha256=dSiYz0FJTD2sQuxpxqFWwwIe2-p6Y7oTB9Tv0Jajit0,1866
19
- utilities/functions.py,sha256=RNVAoLeT_sl-gXaBv2VI_U_EB-d-nSVosYR4gTeeojE,28261
19
+ utilities/functions.py,sha256=82qCAaPIB0JmZ5wsQurA3MTYl7fh8LHcoBFkxPs7Zeg,21478
20
20
  utilities/functools.py,sha256=I00ru2gQPakZw2SHVeKIKXfTv741655s6HI0lUoE0D4,1552
21
21
  utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
22
22
  utilities/gzip.py,sha256=fkGP3KdsBfXlstodT4wtlp-PwNyUsogpbDCVVVGdsm4,781
@@ -26,27 +26,27 @@ utilities/hypothesis.py,sha256=eoFAFnM2SdvzH6DWD5oCs8cH57LsEEXsRy8vMZDW6i8,44874
26
26
  utilities/importlib.py,sha256=mV1xT_O_zt_GnZZ36tl3xOmMaN_3jErDWY54fX39F6Y,429
27
27
  utilities/inflect.py,sha256=v7YkOWSu8NAmVghPcf4F3YBZQoJCS47_DLf9jbfWIs0,581
28
28
  utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
29
- utilities/iterables.py,sha256=fM7s5072m9ram33zMk4nYQpOSxW8Zt6_XX2GCD5BMY0,43013
29
+ utilities/iterables.py,sha256=8_dljtugLjxL8GCO_udMoH5uldvxdAmsN6aZ3EdnAiA,42564
30
30
  utilities/json.py,sha256=-WcGtSsCr9Y42wHZzAMnfvU6ihAfVftylFfRUORaDFo,2102
31
31
  utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
32
32
  utilities/libcst.py,sha256=TKgKN4bNmtBNEE-TUfhTyd1BrTncfsl_7tTuhpesGYY,5585
33
33
  utilities/lightweight_charts.py,sha256=YM3ojBvJxuCSUBu_KrhFBmaMCvRPvupKC3qkm-UVZq4,2751
34
- utilities/logging.py,sha256=uJ-hhtkrx4xnLUzZmE1soV4O8wQzNPUB1f_YJJe6I30,18089
34
+ utilities/logging.py,sha256=W3d8Vby0mmqGWvTNlGtcfrmORDTt7abCuqjkIyCPIg8,18914
35
35
  utilities/math.py,sha256=cevB-YyEYAzJTWtkAr7qeeu-hbxorDI3gMznXlmNQkw,26897
36
36
  utilities/memory_profiler.py,sha256=XzN56jDCa5aqXS_DxEjb_K4L6aIWh_5zyKi6OhcIxw0,853
37
37
  utilities/modules.py,sha256=iuvLluJya-hvl1Q25-Jk3dLgx2Es3ck4SjJiEkAlVTs,3195
38
38
  utilities/more_itertools.py,sha256=syfIPhQF_WS-YiicdGe2h5F1G-Ld12Q2XsVduL2hA40,10908
39
39
  utilities/numpy.py,sha256=Xn23sA2ZbVNqwUYEgNJD3XBYH6IbCri_WkHSNhg3NkY,26122
40
- utilities/operator.py,sha256=nhxn5q6CFNzUm1wpTwWPCu9JGCqVHSlaJf0o1-efoII,3616
40
+ utilities/operator.py,sha256=C3NylZWGTVWRpwYHOPVhaLgRhw0DfpS4_XQ8KfPhBLQ,3613
41
41
  utilities/optuna.py,sha256=C-fhWYiXHVPo1l8QctYkFJ4DyhbSrGorzP1dJb_qvd8,1933
42
- utilities/orjson.py,sha256=pOCsldgiuxTFQJQAxh6vzEZnkDGuOCx_Nn1t5ziNFhY,41970
42
+ utilities/orjson.py,sha256=rII_rINPcH9X0CfL_WeFjwHgKNkKAF-TVS5rnpLHjzc,41998
43
43
  utilities/os.py,sha256=8TjFLVWlGhhEpzZ0X_vNAyhYntjeVL5WTwaQcdTaNVw,3934
44
44
  utilities/parse.py,sha256=JcJn5yXKhIWXBCwgBdPsyu7Hvcuw6kyEdqvaebCaI9k,17951
45
45
  utilities/pathlib.py,sha256=qGuU8XPmdgGpy8tOMUgelfXx3kxI8h9IaV3TI_06QGE,8428
46
46
  utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
47
47
  utilities/platform.py,sha256=pTn7gw6N4T6LdKrf0virwarof_mze9WtoQlrGMzhGVI,2798
48
- utilities/polars.py,sha256=T-7GZtiw_LBhm3na0jyb8PBpgKGpszZ4zCbJhgc4w2M,79781
49
- utilities/polars_ols.py,sha256=Uc9V5kvlWZ5cU93lKZ-cfAKdVFFw81tqwLW9PxtUvMs,5618
48
+ utilities/polars.py,sha256=oV63oc7qI100dHoZUu8rfnr5-bEd7iY0O79iEFtDmj4,79772
49
+ utilities/polars_ols.py,sha256=LNTFNLPuYW7fcAHymlbnams_DhitToblYvib3mhKbwI,5615
50
50
  utilities/postgres.py,sha256=ynCTTaF-bVEOSW-KEAR-dlLh_hYjeVVjm__-4pEU8Zk,12269
51
51
  utilities/pottery.py,sha256=ggMN72Y7wx7Js8VN6eyNyodpm8TIYqZHGghkDPXIVWk,3949
52
52
  utilities/pqdm.py,sha256=idv2seRVP2f6NeSfpeEnT5A-tQezaHZKDyeu16g2-0E,3091
@@ -57,14 +57,14 @@ utilities/pytest.py,sha256=M-Om6b3hpF9W_bEB7UFY2IzBCubSxzVQleGrgRXHtxY,7741
57
57
  utilities/pytest_regressions.py,sha256=ocjHTtfOeiGfQAKIei8pKNd61sxN9dawrJJ9gPt2wzA,4097
58
58
  utilities/random.py,sha256=hZlH4gnAtoaofWswuJYjcygejrY8db4CzP-z_adO2Mo,4165
59
59
  utilities/re.py,sha256=S4h-DLL6ScMPqjboZ_uQ1BVTJajrqV06r_81D--_HCE,4573
60
- utilities/redis.py,sha256=BT4QGNgaoIg5sXK8CN8mX0weCzLtcyD7rgf9fSbXfm8,30471
60
+ utilities/redis.py,sha256=f16zuOabP_rFDFtan7x1S-d8KOjBK93pS8OEyp7T9Is,30156
61
61
  utilities/reprlib.py,sha256=ssYTcBW-TeRh3fhCJv57sopTZHF5FrPyyUg9yp5XBlo,3953
62
62
  utilities/scipy.py,sha256=wZJM7fEgBAkLSYYvSmsg5ac-QuwAI0BGqHVetw1_Hb0,947
63
63
  utilities/sentinel.py,sha256=A_p5jX2K0Yc5XBfoYHyBLqHsEWzE1ByOdDuzzA2pZnE,1434
64
64
  utilities/shelve.py,sha256=4OzjQI6kGuUbJciqf535rwnao-_IBv66gsT6tRGiUt0,759
65
65
  utilities/slack_sdk.py,sha256=76-DYtcGiUhEvl-voMamc5OjfF7Y7nCq54Bys1arqzw,2233
66
66
  utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
67
- utilities/sqlalchemy.py,sha256=xAVU92MTphMx2xAdYZpQX5xGnAACI5UvJIPG95infjE,36395
67
+ utilities/sqlalchemy.py,sha256=gCd7rBn7mDt1ZepxMtW4j0QSUo-zwCC3VvzH8P0LcZI,36411
68
68
  utilities/sqlalchemy_polars.py,sha256=JCGhB37raSR7fqeWV5dTsciRTMVzIdVT9YSqKT0piT0,13370
69
69
  utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
70
70
  utilities/string.py,sha256=shmBK87zZwzGyixuNuXCiUbqzfeZ9xlrFwz6JTaRvDk,582
@@ -76,7 +76,7 @@ utilities/timer.py,sha256=oXfTii6ymu57niP0BDGZjFD55LEHi2a19kqZKiTgaFQ,2588
76
76
  utilities/traceback.py,sha256=b1nSvlyrGmI1MyZLkkoLVET3DQBSGt9qqIlAAQbyjEw,9629
77
77
  utilities/typed_settings.py,sha256=SFWqS3lAzV7IfNRwqFcTk0YynTcQ7BmrcW2mr_KUnos,4466
78
78
  utilities/types.py,sha256=oeH-hEC3-67Eja4nLz-Nj9WvK6Z9-3T1zobO_XJpuVg,18735
79
- utilities/typing.py,sha256=7ZgCNZwA6oaiwpSJIS9Rj3i3MbRBYHMqbC3jMe5KiNg,13992
79
+ utilities/typing.py,sha256=vPqxHE2G5_dbDEgWAyAaTSDbUQE0pmihIj0vdXogbRU,24185
80
80
  utilities/tzdata.py,sha256=fgNVj66yUbCSI_-vrRVzSD3gtf-L_8IEJEPjP_Jel5Y,266
81
81
  utilities/tzlocal.py,sha256=KyCXEgCTjqGFx-389JdTuhMRUaT06U1RCMdWoED-qro,728
82
82
  utilities/uuid.py,sha256=nQZs6tFX4mqtc2Ku3KqjloYCqwpTKeTj8eKwQwh3FQI,1572
@@ -88,8 +88,8 @@ utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
88
88
  utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
89
89
  utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
90
90
  utilities/pytest_plugins/pytest_regressions.py,sha256=9v8kAXDM2ycIXJBimoiF4EgrwbUvxTycFWJiGR_GHhM,1466
91
- dycw_utilities-0.163.1.dist-info/METADATA,sha256=eaPtQYq94LvYpZRiDAsg5g8fVHJ2XV2QuJSEGnwaF2g,1696
92
- dycw_utilities-0.163.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
- dycw_utilities-0.163.1.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
94
- dycw_utilities-0.163.1.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
95
- dycw_utilities-0.163.1.dist-info/RECORD,,
91
+ dycw_utilities-0.164.1.dist-info/METADATA,sha256=4-ckCdR3FFMcVm2AWHdzIEhR72r_NvE6YImrElaIkRY,1696
92
+ dycw_utilities-0.164.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
+ dycw_utilities-0.164.1.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
94
+ dycw_utilities-0.164.1.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
95
+ dycw_utilities-0.164.1.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.163.1"
3
+ __version__ = "0.164.1"
utilities/dataclasses.py CHANGED
@@ -6,11 +6,7 @@ from dataclasses import MISSING, dataclass, field, fields, replace
6
6
  from typing import TYPE_CHECKING, Any, Literal, assert_never, overload, override
7
7
 
8
8
  from utilities.errors import ImpossibleCaseError
9
- from utilities.functions import (
10
- get_class_name,
11
- is_dataclass_class,
12
- is_dataclass_instance,
13
- )
9
+ from utilities.functions import get_class_name
14
10
  from utilities.iterables import (
15
11
  OneStrEmptyError,
16
12
  OneStrNonUniqueError,
@@ -35,7 +31,7 @@ from utilities.text import (
35
31
  split_key_value_pairs,
36
32
  )
37
33
  from utilities.types import MaybeType, SupportsLT
38
- from utilities.typing import get_type_hints
34
+ from utilities.typing import get_type_hints, is_dataclass_class, is_dataclass_instance
39
35
 
40
36
  if TYPE_CHECKING:
41
37
  from collections.abc import Callable, Iterable, Iterator
utilities/functions.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Callable, Iterable, Iterator, Sequence
4
- from dataclasses import asdict, dataclass, is_dataclass
3
+ from collections.abc import Callable, Iterable, Iterator
4
+ from dataclasses import asdict, dataclass
5
5
  from functools import _lru_cache_wrapper, cached_property, partial, reduce, wraps
6
6
  from inspect import getattr_static
7
7
  from pathlib import Path
@@ -20,17 +20,10 @@ from whenever import Date, PlainDateTime, Time, TimeDelta, ZonedDateTime
20
20
 
21
21
  from utilities.reprlib import get_repr, get_repr_and_class
22
22
  from utilities.sentinel import Sentinel, is_sentinel, sentinel
23
- from utilities.types import (
24
- Dataclass,
25
- Number,
26
- StrMapping,
27
- SupportsRichComparison,
28
- TupleOrStrMapping,
29
- TypeLike,
30
- )
23
+ from utilities.types import Dataclass, Number, SupportsRichComparison, TypeLike
31
24
 
32
25
  if TYPE_CHECKING:
33
- from collections.abc import Container, Hashable, Sized
26
+ from collections.abc import Container
34
27
 
35
28
 
36
29
  def apply_decorators[F1: Callable, F2: Callable](
@@ -230,25 +223,6 @@ class EnsureFloatError(Exception):
230
223
  ##
231
224
 
232
225
 
233
- def ensure_hashable(obj: Any, /) -> Hashable:
234
- """Ensure an object is hashable."""
235
- if is_hashable(obj):
236
- return obj
237
- raise EnsureHashableError(obj=obj)
238
-
239
-
240
- @dataclass(kw_only=True, slots=True)
241
- class EnsureHashableError(Exception):
242
- obj: Any
243
-
244
- @override
245
- def __str__(self) -> str:
246
- return _make_error_msg(self.obj, "hashable")
247
-
248
-
249
- ##
250
-
251
-
252
226
  @overload
253
227
  def ensure_int(obj: Any, /, *, nullable: bool) -> int | None: ...
254
228
  @overload
@@ -405,44 +379,6 @@ class EnsurePlainDateTimeError(Exception):
405
379
  ##
406
380
 
407
381
 
408
- def ensure_sized(obj: Any, /) -> Sized:
409
- """Ensure an object is sized."""
410
- if is_sized(obj):
411
- return obj
412
- raise EnsureSizedError(obj=obj)
413
-
414
-
415
- @dataclass(kw_only=True, slots=True)
416
- class EnsureSizedError(Exception):
417
- obj: Any
418
-
419
- @override
420
- def __str__(self) -> str:
421
- return _make_error_msg(self.obj, "sized")
422
-
423
-
424
- ##
425
-
426
-
427
- def ensure_sized_not_str(obj: Any, /) -> Sized:
428
- """Ensure an object is sized, but not a string."""
429
- if is_sized_not_str(obj):
430
- return obj
431
- raise EnsureSizedNotStrError(obj=obj)
432
-
433
-
434
- @dataclass(kw_only=True, slots=True)
435
- class EnsureSizedNotStrError(Exception):
436
- obj: Any
437
-
438
- @override
439
- def __str__(self) -> str:
440
- return _make_error_msg(self.obj, "sized and not a string")
441
-
442
-
443
- ##
444
-
445
-
446
382
  @overload
447
383
  def ensure_str(obj: Any, /, *, nullable: bool) -> str | None: ...
448
384
  @overload
@@ -633,67 +569,7 @@ def identity[T](obj: T, /) -> T:
633
569
  ##
634
570
 
635
571
 
636
- def is_dataclass_class(obj: Any, /) -> TypeGuard[type[Dataclass]]:
637
- """Check if an object is a dataclass."""
638
- return isinstance(obj, type) and is_dataclass(obj)
639
-
640
-
641
- ##
642
-
643
-
644
- def is_dataclass_instance(obj: Any, /) -> TypeGuard[Dataclass]:
645
- """Check if an object is an instance of a dataclass."""
646
- return (not isinstance(obj, type)) and is_dataclass(obj)
647
-
648
-
649
- ##
650
-
651
-
652
- def is_hashable(obj: Any, /) -> TypeGuard[Hashable]:
653
- """Check if an object is hashable."""
654
- try:
655
- _ = hash(obj)
656
- except TypeError:
657
- return False
658
- return True
659
-
660
-
661
- ##
662
-
663
-
664
- @overload
665
- def is_iterable_of[T](obj: Any, cls: type[T], /) -> TypeGuard[Iterable[T]]: ...
666
- @overload
667
- def is_iterable_of[T1](
668
- obj: Any, cls: tuple[type[T1]], /
669
- ) -> TypeGuard[Iterable[T1]]: ...
670
- @overload
671
- def is_iterable_of[T1, T2](
672
- obj: Any, cls: tuple[type[T1], type[T2]], /
673
- ) -> TypeGuard[Iterable[T1 | T2]]: ...
674
- @overload
675
- def is_iterable_of[T1, T2, T3](
676
- obj: Any, cls: tuple[type[T1], type[T2], type[T3]], /
677
- ) -> TypeGuard[Iterable[T1 | T2 | T3]]: ...
678
- @overload
679
- def is_iterable_of[T1, T2, T3, T4](
680
- obj: Any, cls: tuple[type[T1], type[T2], type[T3], type[T4]], /
681
- ) -> TypeGuard[Iterable[T1 | T2 | T3 | T4]]: ...
682
- @overload
683
- def is_iterable_of[T1, T2, T3, T4, T5](
684
- obj: Any, cls: tuple[type[T1], type[T2], type[T3], type[T4], type[T5]], /
685
- ) -> TypeGuard[Iterable[T1 | T2 | T3 | T4 | T5]]: ...
686
- @overload
687
- def is_iterable_of[T](obj: Any, cls: TypeLike[T], /) -> TypeGuard[Iterable[T]]: ...
688
- def is_iterable_of[T](obj: Any, cls: TypeLike[T], /) -> TypeGuard[Iterable[T]]:
689
- """Check if an object is a iterable of tuple or string mappings."""
690
- return isinstance(obj, Iterable) and all(map(make_isinstance(cls), obj))
691
-
692
-
693
- ##
694
-
695
-
696
- def is_none(obj: Any, /) -> bool:
572
+ def is_none(obj: Any, /) -> TypeGuard[None]:
697
573
  """Check if an object is `None`."""
698
574
  return obj is None
699
575
 
@@ -709,126 +585,6 @@ def is_not_none(obj: Any, /) -> bool:
709
585
  ##
710
586
 
711
587
 
712
- @overload
713
- def is_sequence_of[T](obj: Any, cls: type[T], /) -> TypeGuard[Sequence[T]]: ...
714
- @overload
715
- def is_sequence_of[T1](
716
- obj: Any, cls: tuple[type[T1]], /
717
- ) -> TypeGuard[Sequence[T1]]: ...
718
- @overload
719
- def is_sequence_of[T1, T2](
720
- obj: Any, cls: tuple[type[T1], type[T2]], /
721
- ) -> TypeGuard[Sequence[T1 | T2]]: ...
722
- @overload
723
- def is_sequence_of[T1, T2, T3](
724
- obj: Any, cls: tuple[type[T1], type[T2], type[T3]], /
725
- ) -> TypeGuard[Sequence[T1 | T2 | T3]]: ...
726
- @overload
727
- def is_sequence_of[T1, T2, T3, T4](
728
- obj: Any, cls: tuple[type[T1], type[T2], type[T3], type[T4]], /
729
- ) -> TypeGuard[Sequence[T1 | T2 | T3 | T4]]: ...
730
- @overload
731
- def is_sequence_of[T1, T2, T3, T4, T5](
732
- obj: Any, cls: tuple[type[T1], type[T2], type[T3], type[T4], type[T5]], /
733
- ) -> TypeGuard[Sequence[T1 | T2 | T3 | T4 | T5]]: ...
734
- @overload
735
- def is_sequence_of[T](obj: Any, cls: TypeLike[T], /) -> TypeGuard[Sequence[T]]: ...
736
- def is_sequence_of[T](obj: Any, cls: TypeLike[T], /) -> TypeGuard[Sequence[T]]:
737
- """Check if an object is a sequence of tuple or string mappings."""
738
- return isinstance(obj, Sequence) and is_iterable_of(obj, cls)
739
-
740
-
741
- ##
742
-
743
-
744
- def is_sequence_of_tuple_or_str_mapping(
745
- obj: Any, /
746
- ) -> TypeGuard[Sequence[TupleOrStrMapping]]:
747
- """Check if an object is a sequence of tuple or string mappings."""
748
- return isinstance(obj, Sequence) and all(map(is_tuple_or_str_mapping, obj))
749
-
750
-
751
- ##
752
-
753
-
754
- def is_sized(obj: Any, /) -> TypeGuard[Sized]:
755
- """Check if an object is sized."""
756
- try:
757
- _ = len(obj)
758
- except TypeError:
759
- return False
760
- return True
761
-
762
-
763
- ##
764
-
765
-
766
- def is_sized_not_str(obj: Any, /) -> TypeGuard[Sized]:
767
- """Check if an object is sized, but not a string."""
768
- return is_sized(obj) and not isinstance(obj, str)
769
-
770
-
771
- ##
772
-
773
-
774
- def is_string_mapping(obj: Any, /) -> TypeGuard[StrMapping]:
775
- """Check if an object is a string mapping."""
776
- return isinstance(obj, dict) and is_iterable_of(obj, str)
777
-
778
-
779
- ##
780
-
781
-
782
- def is_tuple(obj: Any, /) -> TypeGuard[tuple[Any, ...]]:
783
- """Check if an object is a tuple or string mapping."""
784
- return make_isinstance(tuple)(obj)
785
-
786
-
787
- ##
788
-
789
-
790
- def is_tuple_or_str_mapping(obj: Any, /) -> TypeGuard[TupleOrStrMapping]:
791
- """Check if an object is a tuple or string mapping."""
792
- return is_tuple(obj) or is_string_mapping(obj)
793
-
794
-
795
- ##
796
-
797
-
798
- @overload
799
- def make_isinstance[T](cls: type[T], /) -> Callable[[Any], TypeGuard[T]]: ...
800
- @overload
801
- def make_isinstance[T1](cls: tuple[type[T1]], /) -> Callable[[Any], TypeGuard[T1]]: ...
802
- @overload
803
- def make_isinstance[T1, T2](
804
- cls: tuple[type[T1], type[T2]], /
805
- ) -> Callable[[Any], TypeGuard[T1 | T2]]: ...
806
- @overload
807
- def make_isinstance[T1, T2, T3](
808
- cls: tuple[type[T1], type[T2], type[T3]], /
809
- ) -> Callable[[Any], TypeGuard[T1 | T2 | T3]]: ...
810
- @overload
811
- def make_isinstance[T1, T2, T3, T4](
812
- cls: tuple[type[T1], type[T2], type[T3], type[T4]], /
813
- ) -> Callable[[Any], TypeGuard[T1 | T2 | T3 | T4]]: ...
814
- @overload
815
- def make_isinstance[T1, T2, T3, T4, T5](
816
- cls: tuple[type[T1], type[T2], type[T3], type[T4], type[T5]], /
817
- ) -> Callable[[Any], TypeGuard[T1 | T2 | T3 | T4 | T5]]: ...
818
- @overload
819
- def make_isinstance[T](cls: TypeLike[T], /) -> Callable[[Any], TypeGuard[T]]: ...
820
- def make_isinstance[T](cls: TypeLike[T], /) -> Callable[[Any], TypeGuard[T]]:
821
- """Make a curried `isinstance` function."""
822
- return partial(_make_instance_core, cls=cls)
823
-
824
-
825
- def _make_instance_core[T](obj: Any, /, *, cls: TypeLike[T]) -> TypeGuard[T]:
826
- return isinstance(obj, cls)
827
-
828
-
829
- ##
830
-
831
-
832
588
  def map_object[T](
833
589
  func: Callable[[Any], Any], obj: T, /, *, before: Callable[[Any], Any] | None = None
834
590
  ) -> T:
@@ -1000,15 +756,12 @@ __all__ = [
1000
756
  "EnsureClassError",
1001
757
  "EnsureDateError",
1002
758
  "EnsureFloatError",
1003
- "EnsureHashableError",
1004
759
  "EnsureIntError",
1005
760
  "EnsureMemberError",
1006
761
  "EnsureNotNoneError",
1007
762
  "EnsureNumberError",
1008
763
  "EnsurePathError",
1009
764
  "EnsurePlainDateTimeError",
1010
- "EnsureSizedError",
1011
- "EnsureSizedNotStrError",
1012
765
  "EnsureStrError",
1013
766
  "EnsureTimeDeltaError",
1014
767
  "EnsureTimeError",
@@ -1021,15 +774,12 @@ __all__ = [
1021
774
  "ensure_class",
1022
775
  "ensure_date",
1023
776
  "ensure_float",
1024
- "ensure_hashable",
1025
777
  "ensure_int",
1026
778
  "ensure_member",
1027
779
  "ensure_not_none",
1028
780
  "ensure_number",
1029
781
  "ensure_path",
1030
782
  "ensure_plain_date_time",
1031
- "ensure_sized",
1032
- "ensure_sized_not_str",
1033
783
  "ensure_str",
1034
784
  "ensure_time",
1035
785
  "ensure_time_delta",
@@ -1040,19 +790,8 @@ __all__ = [
1040
790
  "get_func_name",
1041
791
  "get_func_qualname",
1042
792
  "identity",
1043
- "is_dataclass_class",
1044
- "is_dataclass_instance",
1045
- "is_hashable",
1046
- "is_iterable_of",
1047
793
  "is_none",
1048
794
  "is_not_none",
1049
- "is_sequence_of_tuple_or_str_mapping",
1050
- "is_sized",
1051
- "is_sized_not_str",
1052
- "is_string_mapping",
1053
- "is_tuple",
1054
- "is_tuple_or_str_mapping",
1055
- "make_isinstance",
1056
795
  "map_object",
1057
796
  "max_nullable",
1058
797
  "min_nullable",
utilities/iterables.py CHANGED
@@ -31,7 +31,6 @@ from typing import (
31
31
  )
32
32
 
33
33
  from utilities.errors import ImpossibleCaseError
34
- from utilities.functions import ensure_hashable, ensure_str
35
34
  from utilities.math import (
36
35
  _CheckIntegerEqualError,
37
36
  _CheckIntegerEqualOrApproxError,
@@ -236,8 +235,7 @@ def check_iterables_equal(left: Iterable[Any], right: Iterable[Any], /) -> None:
236
235
  if lv != rv:
237
236
  errors.append((i, lv, rv))
238
237
  except ValueError as error:
239
- msg = ensure_str(one(error.args))
240
- match msg:
238
+ match one(error.args):
241
239
  case "zip() argument 2 is longer than argument 1":
242
240
  state = "right_longer"
243
241
  case "zip() argument 2 is shorter than argument 1":
@@ -695,18 +693,6 @@ def chunked[T](iterable: Iterable[T], n: int, /) -> Iterator[Sequence[T]]:
695
693
  ##
696
694
 
697
695
 
698
- def ensure_hashables(
699
- *args: Any, **kwargs: Any
700
- ) -> tuple[list[Hashable], dict[str, Hashable]]:
701
- """Ensure a set of positional & keyword arguments are all hashable."""
702
- hash_args = list(map(ensure_hashable, args))
703
- hash_kwargs = {k: ensure_hashable(v) for k, v in kwargs.items()}
704
- return hash_args, hash_kwargs
705
-
706
-
707
- ##
708
-
709
-
710
696
  def ensure_iterable(obj: Any, /) -> Iterable[Any]:
711
697
  """Ensure an object is iterable."""
712
698
  if is_iterable(obj):
@@ -1480,7 +1466,6 @@ __all__ = [
1480
1466
  "check_unique_modulo_case",
1481
1467
  "chunked",
1482
1468
  "cmp_nullable",
1483
- "ensure_hashables",
1484
1469
  "ensure_iterable",
1485
1470
  "ensure_iterable_not_str",
1486
1471
  "enumerate_with_edge",
utilities/logging.py CHANGED
@@ -8,6 +8,7 @@ from logging import (
8
8
  Formatter,
9
9
  Handler,
10
10
  Logger,
11
+ LoggerAdapter,
11
12
  LogRecord,
12
13
  StreamHandler,
13
14
  basicConfig,
@@ -22,6 +23,7 @@ from socket import gethostname
22
23
  from typing import (
23
24
  TYPE_CHECKING,
24
25
  Any,
26
+ Concatenate,
25
27
  Literal,
26
28
  NotRequired,
27
29
  Self,
@@ -53,7 +55,7 @@ from utilities.whenever import (
53
55
  )
54
56
 
55
57
  if TYPE_CHECKING:
56
- from collections.abc import Iterable, Mapping
58
+ from collections.abc import Callable, Iterable, Mapping, MutableMapping
57
59
  from datetime import time
58
60
  from logging import _FilterType
59
61
 
@@ -63,6 +65,7 @@ if TYPE_CHECKING:
63
65
  MaybeCallablePathLike,
64
66
  MaybeIterable,
65
67
  PathLike,
68
+ StrMapping,
66
69
  )
67
70
 
68
71
 
@@ -75,6 +78,35 @@ _DEFAULT_WHEN: _When = "D"
75
78
  ##
76
79
 
77
80
 
81
+ def add_adapter[**P](
82
+ logger: Logger,
83
+ process: Callable[Concatenate[str, P], str],
84
+ /,
85
+ *args: P.args,
86
+ **kwargs: P.kwargs,
87
+ ) -> LoggerAdapter:
88
+ """Add an adapter to a logger."""
89
+
90
+ class CustomAdapter(LoggerAdapter):
91
+ @override
92
+ def process(
93
+ self, msg: str, kwargs: MutableMapping[str, Any]
94
+ ) -> tuple[str, MutableMapping[str, Any]]:
95
+ extra = cast("_ArgsAndKwargs", self.extra)
96
+ new_msg = process(msg, *extra["args"], **extra["kwargs"])
97
+ return new_msg, kwargs
98
+
99
+ return CustomAdapter(logger, extra=_ArgsAndKwargs(args=args, kwargs=kwargs))
100
+
101
+
102
+ class _ArgsAndKwargs(TypedDict):
103
+ args: tuple[Any, ...]
104
+ kwargs: StrMapping
105
+
106
+
107
+ ##
108
+
109
+
78
110
  def add_filters(handler: Handler, /, *filters: _FilterType) -> None:
79
111
  """Add a set of filters to a handler."""
80
112
  for filter_i in filters:
@@ -589,6 +621,7 @@ def to_logger(logger: LoggerLike | None = None, /) -> Logger:
589
621
  __all__ = [
590
622
  "GetLoggingLevelNumberError",
591
623
  "SizeAndTimeRotatingFileHandler",
624
+ "add_adapter",
592
625
  "add_filters",
593
626
  "basic_config",
594
627
  "get_format_str",
utilities/operator.py CHANGED
@@ -6,9 +6,9 @@ from dataclasses import asdict, dataclass
6
6
  from typing import TYPE_CHECKING, Any, cast, override
7
7
 
8
8
  import utilities.math
9
- from utilities.functions import is_dataclass_instance
10
9
  from utilities.iterables import SortIterableError, sort_iterable
11
10
  from utilities.reprlib import get_repr
11
+ from utilities.typing import is_dataclass_instance
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from utilities.types import Dataclass, Number
utilities/orjson.py CHANGED
@@ -38,7 +38,7 @@ from whenever import (
38
38
 
39
39
  from utilities.concurrent import concurrent_map
40
40
  from utilities.dataclasses import dataclass_to_dict
41
- from utilities.functions import ensure_class, is_string_mapping
41
+ from utilities.functions import ensure_class
42
42
  from utilities.gzip import read_binary
43
43
  from utilities.iterables import (
44
44
  OneEmptyError,
@@ -51,6 +51,7 @@ from utilities.json import write_formatted_json
51
51
  from utilities.logging import get_logging_level_number
52
52
  from utilities.math import MAX_INT64, MIN_INT64
53
53
  from utilities.types import Dataclass, LogLevel, MaybeIterable, PathLike, StrMapping
54
+ from utilities.typing import is_string_mapping
54
55
  from utilities.tzlocal import LOCAL_TIME_ZONE
55
56
  from utilities.version import Version, parse_version
56
57
  from utilities.whenever import (
utilities/polars.py CHANGED
@@ -56,12 +56,7 @@ from whenever import DateDelta, DateTimeDelta, PlainDateTime, TimeDelta, ZonedDa
56
56
  import utilities.math
57
57
  from utilities.dataclasses import yield_fields
58
58
  from utilities.errors import ImpossibleCaseError
59
- from utilities.functions import (
60
- get_class_name,
61
- is_dataclass_class,
62
- is_dataclass_instance,
63
- make_isinstance,
64
- )
59
+ from utilities.functions import get_class_name
65
60
  from utilities.gzip import read_binary
66
61
  from utilities.iterables import (
67
62
  CheckIterablesEqualError,
@@ -91,11 +86,14 @@ from utilities.reprlib import get_repr
91
86
  from utilities.types import MaybeStr, Number, PathLike, WeekDay
92
87
  from utilities.typing import (
93
88
  get_args,
89
+ is_dataclass_class,
90
+ is_dataclass_instance,
94
91
  is_frozenset_type,
95
92
  is_list_type,
96
93
  is_literal_type,
97
94
  is_optional_type,
98
95
  is_set_type,
96
+ make_isinstance,
99
97
  )
100
98
  from utilities.warnings import suppress_warnings
101
99
  from utilities.whenever import (
utilities/polars_ols.py CHANGED
@@ -6,8 +6,8 @@ from polars import Expr, Series, struct
6
6
  from polars_ols import RollingKwargs, compute_rolling_least_squares
7
7
 
8
8
  from utilities.errors import ImpossibleCaseError
9
- from utilities.functions import is_sequence_of
10
9
  from utilities.polars import concat_series, ensure_expr_or_series
10
+ from utilities.typing import is_sequence_of
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from polars._typing import IntoExprColumn
utilities/redis.py CHANGED
@@ -26,6 +26,7 @@ from utilities.errors import ImpossibleCaseError
26
26
  from utilities.functions import ensure_int, identity
27
27
  from utilities.iterables import always_iterable, one
28
28
  from utilities.os import is_pytest
29
+ from utilities.typing import is_instance_gen
29
30
  from utilities.whenever import MILLISECOND, SECOND, to_milliseconds, to_seconds
30
31
 
31
32
  if TYPE_CHECKING:
@@ -788,17 +789,7 @@ async def _subscribe_core[T](
788
789
  def _is_message(
789
790
  message: Any, /, *, channels: Collection[bytes]
790
791
  ) -> TypeGuard[_RedisMessage]:
791
- return (
792
- isinstance(message, Mapping)
793
- and ("type" in message)
794
- and (message["type"] in {"subscribe", "psubscribe", "message", "pmessage"})
795
- and ("pattern" in message)
796
- and ((message["pattern"] is None) or isinstance(message["pattern"], str))
797
- and ("channel" in message)
798
- and (message["channel"] in channels)
799
- and ("data" in message)
800
- and isinstance(message["data"], bytes)
801
- )
792
+ return is_instance_gen(message, _RedisMessage) and (message["channel"] in channels)
802
793
 
803
794
 
804
795
  def _handle_message[T](
utilities/sqlalchemy.py CHANGED
@@ -66,15 +66,7 @@ from sqlalchemy.orm.exc import UnmappedClassError
66
66
  from sqlalchemy.pool import NullPool, Pool
67
67
 
68
68
  from utilities.asyncio import timeout_td
69
- from utilities.functions import (
70
- ensure_str,
71
- get_class_name,
72
- is_sequence_of_tuple_or_str_mapping,
73
- is_string_mapping,
74
- is_tuple,
75
- is_tuple_or_str_mapping,
76
- yield_object_attributes,
77
- )
69
+ from utilities.functions import ensure_str, get_class_name, yield_object_attributes
78
70
  from utilities.iterables import (
79
71
  CheckLengthError,
80
72
  CheckSubSetError,
@@ -97,6 +89,12 @@ from utilities.types import (
97
89
  StrMapping,
98
90
  TupleOrStrMapping,
99
91
  )
92
+ from utilities.typing import (
93
+ is_sequence_of_tuple_or_str_mapping,
94
+ is_string_mapping,
95
+ is_tuple,
96
+ is_tuple_or_str_mapping,
97
+ )
100
98
 
101
99
  if TYPE_CHECKING:
102
100
  from enum import Enum, StrEnum
utilities/typing.py CHANGED
@@ -1,19 +1,23 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import datetime as dt
4
- from collections.abc import Mapping, Sequence
5
- from dataclasses import dataclass
4
+ from collections.abc import Callable, Iterable, Mapping, Sequence
5
+ from dataclasses import dataclass, is_dataclass
6
+ from functools import partial
6
7
  from itertools import chain
7
8
  from pathlib import Path
8
9
  from types import NoneType, UnionType
9
10
  from typing import (
10
11
  Any,
12
+ ForwardRef,
11
13
  Literal,
12
14
  NamedTuple,
13
15
  Optional, # pyright: ignore[reportDeprecated]
14
16
  TypeAliasType,
15
17
  TypeGuard,
16
18
  Union, # pyright: ignore[reportDeprecated]
19
+ _TypedDictMeta, # pyright: ignore[reportAttributeAccessIssue]
20
+ cast,
17
21
  get_origin,
18
22
  overload,
19
23
  override,
@@ -36,7 +40,7 @@ from whenever import (
36
40
 
37
41
  from utilities.iterables import unique_everseen
38
42
  from utilities.sentinel import Sentinel
39
- from utilities.types import StrMapping
43
+ from utilities.types import Dataclass, StrMapping, TupleOrStrMapping, TypeLike
40
44
 
41
45
 
42
46
  def get_args(obj: Any, /, *, optional_drop_none: bool = False) -> tuple[Any, ...]:
@@ -147,6 +151,10 @@ def get_type_hints(
147
151
  globalns_use = globals() | ({} if globalns is None else dict(globalns))
148
152
  localns_use = {} if localns is None else dict(localns)
149
153
  result: dict[str, Any] = obj.__annotations__
154
+ result = {
155
+ k: v.__forward_arg__ if isinstance(v, ForwardRef) else v
156
+ for k, v in result.items()
157
+ }
150
158
  try:
151
159
  hints = _get_type_hints(obj, globalns=globalns_use, localns=localns_use)
152
160
  except NameError as error:
@@ -202,6 +210,22 @@ class _GetUnionTypeClassesInternalTypeError(GetUnionTypeClassesError):
202
210
  ##
203
211
 
204
212
 
213
+ def is_dataclass_class(obj: Any, /) -> TypeGuard[type[Dataclass]]:
214
+ """Check if an object is a dataclass."""
215
+ return isinstance(obj, type) and is_dataclass(obj)
216
+
217
+
218
+ ##
219
+
220
+
221
+ def is_dataclass_instance(obj: Any, /) -> TypeGuard[Dataclass]:
222
+ """Check if an object is an instance of a dataclass."""
223
+ return (not isinstance(obj, type)) and is_dataclass(obj)
224
+
225
+
226
+ ##
227
+
228
+
205
229
  def is_dict_type(obj: Any, /) -> bool:
206
230
  """Check if an object is a dict type annotation."""
207
231
  return _is_annotation_of_type(obj, dict)
@@ -219,50 +243,170 @@ def is_frozenset_type(obj: Any, /) -> bool:
219
243
 
220
244
 
221
245
  @overload
222
- def is_instance_gen[T](obj: Any, type_: type[T], /) -> TypeGuard[T]: ...
246
+ def is_instance_gen[T](
247
+ obj: Any,
248
+ type_: type[T],
249
+ /,
250
+ *,
251
+ globalns: StrMapping | None = None,
252
+ localns: StrMapping | None = None,
253
+ warn_name_errors: bool = False,
254
+ ) -> TypeGuard[T]: ...
223
255
  @overload
224
- def is_instance_gen[T1](obj: Any, type_: tuple[T1], /) -> TypeGuard[T1]: ...
256
+ def is_instance_gen[T1](
257
+ obj: Any,
258
+ type_: tuple[T1],
259
+ /,
260
+ *,
261
+ globalns: StrMapping | None = None,
262
+ localns: StrMapping | None = None,
263
+ warn_name_errors: bool = False,
264
+ ) -> TypeGuard[T1]: ...
225
265
  @overload
226
266
  def is_instance_gen[T1, T2](
227
- obj: Any, type_: tuple[T1, T2], /
267
+ obj: Any,
268
+ type_: tuple[T1, T2],
269
+ /,
270
+ *,
271
+ globalns: StrMapping | None = None,
272
+ localns: StrMapping | None = None,
273
+ warn_name_errors: bool = False,
228
274
  ) -> TypeGuard[T1 | T2]: ...
229
275
  @overload
230
276
  def is_instance_gen[T1, T2, T3](
231
- obj: Any, type_: tuple[T1, T2, T3], /
277
+ obj: Any,
278
+ type_: tuple[T1, T2, T3],
279
+ /,
280
+ *,
281
+ globalns: StrMapping | None = None,
282
+ localns: StrMapping | None = None,
283
+ warn_name_errors: bool = False,
232
284
  ) -> TypeGuard[T1 | T2 | T3]: ...
233
285
  @overload
234
286
  def is_instance_gen[T1, T2, T3, T4](
235
- obj: Any, type_: tuple[T1, T2, T3, T4], /
287
+ obj: Any,
288
+ type_: tuple[T1, T2, T3, T4],
289
+ /,
290
+ *,
291
+ globalns: StrMapping | None = None,
292
+ localns: StrMapping | None = None,
293
+ warn_name_errors: bool = False,
236
294
  ) -> TypeGuard[T1 | T2 | T3 | T4]: ...
237
295
  @overload
238
296
  def is_instance_gen[T1, T2, T3, T4, T5](
239
- obj: Any, type_: tuple[T1, T2, T3, T4, T5], /
297
+ obj: Any,
298
+ type_: tuple[T1, T2, T3, T4, T5],
299
+ /,
300
+ *,
301
+ globalns: StrMapping | None = None,
302
+ localns: StrMapping | None = None,
303
+ warn_name_errors: bool = False,
240
304
  ) -> TypeGuard[T1 | T2 | T3 | T4 | T5]: ...
241
305
  @overload
242
- def is_instance_gen(obj: Any, type_: Any, /) -> bool: ...
243
- def is_instance_gen(obj: Any, type_: Any, /) -> bool:
306
+ def is_instance_gen(
307
+ obj: Any,
308
+ type_: Any,
309
+ /,
310
+ *,
311
+ globalns: StrMapping | None = None,
312
+ localns: StrMapping | None = None,
313
+ warn_name_errors: bool = False,
314
+ ) -> bool: ...
315
+ def is_instance_gen(
316
+ obj: Any,
317
+ type_: Any,
318
+ /,
319
+ *,
320
+ globalns: StrMapping | None = None,
321
+ localns: StrMapping | None = None,
322
+ warn_name_errors: bool = False,
323
+ ) -> bool:
244
324
  """Check if an instance relationship holds, except bool<int."""
245
325
  # parent
246
326
  if isinstance(type_, tuple):
247
- return any(is_instance_gen(obj, t) for t in type_) # skipif-ci-and-not-windows
327
+ return any(
328
+ is_instance_gen(
329
+ obj,
330
+ t,
331
+ globalns=globalns,
332
+ localns=localns,
333
+ warn_name_errors=warn_name_errors,
334
+ )
335
+ for t in type_
336
+ )
248
337
  if is_literal_type(type_):
249
338
  return obj in get_args(type_)
250
339
  if is_union_type(type_):
251
- return any(is_instance_gen(obj, t) for t in get_args(type_))
340
+ return any(
341
+ is_instance_gen(
342
+ obj,
343
+ t,
344
+ globalns=globalns,
345
+ localns=localns,
346
+ warn_name_errors=warn_name_errors,
347
+ )
348
+ for t in get_args(type_)
349
+ )
252
350
  # tuple vs tuple
253
351
  if isinstance(obj, tuple) and is_tuple_type(type_):
254
352
  type_args = get_args(type_)
255
353
  return (len(obj) == len(type_args)) and all(
256
- is_instance_gen(o, t) for o, t in zip(obj, type_args, strict=True)
354
+ is_instance_gen(
355
+ o,
356
+ t,
357
+ globalns=globalns,
358
+ localns=localns,
359
+ warn_name_errors=warn_name_errors,
360
+ )
361
+ for o, t in zip(obj, type_args, strict=True)
257
362
  )
258
363
  if isinstance(obj, tuple) is not is_tuple_type(type_):
259
364
  return False
260
365
  # basic
366
+ if isinstance(type_, _TypedDictMeta):
367
+ return _is_instance_typed_dict(
368
+ obj,
369
+ type_,
370
+ globalns=globalns,
371
+ localns=localns,
372
+ warn_name_errors=warn_name_errors,
373
+ )
261
374
  if isinstance(type_, type):
262
375
  return any(_is_instance_gen_type(obj, t) for t in get_type_classes(type_))
263
376
  raise IsInstanceGenError(obj=obj, type_=type_)
264
377
 
265
378
 
379
+ def _is_instance_typed_dict[T: _TypedDictMeta](
380
+ obj: Any,
381
+ type_: type[T],
382
+ /,
383
+ *,
384
+ globalns: StrMapping | None = None,
385
+ localns: StrMapping | None = None,
386
+ warn_name_errors: bool = False,
387
+ ) -> TypeGuard[T]:
388
+ if not isinstance(obj, dict):
389
+ return False
390
+ if not all(isinstance(k, str) for k in obj):
391
+ return False
392
+ obj = cast("dict[str, Any]", obj)
393
+ hints = get_type_hints(
394
+ type_, globalns=globalns, localns=localns, warn_name_errors=warn_name_errors
395
+ )
396
+ if not set(obj).issuperset(hints):
397
+ return False
398
+ return all(
399
+ is_instance_gen(
400
+ obj[k],
401
+ hints[k],
402
+ globalns=globalns,
403
+ localns=localns,
404
+ warn_name_errors=warn_name_errors,
405
+ )
406
+ for k in hints
407
+ )
408
+
409
+
266
410
  def _is_instance_gen_type[T](obj: Any, type_: type[T], /) -> TypeGuard[T]:
267
411
  return (
268
412
  isinstance(obj, type_)
@@ -292,6 +436,101 @@ class IsInstanceGenError(Exception):
292
436
  ##
293
437
 
294
438
 
439
+ @overload
440
+ def is_iterable_of[T](
441
+ obj: Any,
442
+ cls: type[T],
443
+ /,
444
+ *,
445
+ globalns: StrMapping | None = None,
446
+ localns: StrMapping | None = None,
447
+ warn_name_errors: bool = False,
448
+ ) -> TypeGuard[Iterable[T]]: ...
449
+ @overload
450
+ def is_iterable_of[T1](
451
+ obj: Any,
452
+ cls: tuple[type[T1]],
453
+ /,
454
+ *,
455
+ globalns: StrMapping | None = None,
456
+ localns: StrMapping | None = None,
457
+ warn_name_errors: bool = False,
458
+ ) -> TypeGuard[Iterable[T1]]: ...
459
+ @overload
460
+ def is_iterable_of[T1, T2](
461
+ obj: Any,
462
+ cls: tuple[type[T1], type[T2]],
463
+ /,
464
+ *,
465
+ globalns: StrMapping | None = None,
466
+ localns: StrMapping | None = None,
467
+ warn_name_errors: bool = False,
468
+ ) -> TypeGuard[Iterable[T1 | T2]]: ...
469
+ @overload
470
+ def is_iterable_of[T1, T2, T3](
471
+ obj: Any,
472
+ cls: tuple[type[T1], type[T2], type[T3]],
473
+ /,
474
+ *,
475
+ globalns: StrMapping | None = None,
476
+ localns: StrMapping | None = None,
477
+ warn_name_errors: bool = False,
478
+ ) -> TypeGuard[Iterable[T1 | T2 | T3]]: ...
479
+ @overload
480
+ def is_iterable_of[T1, T2, T3, T4](
481
+ obj: Any,
482
+ cls: tuple[type[T1], type[T2], type[T3], type[T4]],
483
+ /,
484
+ *,
485
+ globalns: StrMapping | None = None,
486
+ localns: StrMapping | None = None,
487
+ warn_name_errors: bool = False,
488
+ ) -> TypeGuard[Iterable[T1 | T2 | T3 | T4]]: ...
489
+ @overload
490
+ def is_iterable_of[T1, T2, T3, T4, T5](
491
+ obj: Any,
492
+ cls: tuple[type[T1], type[T2], type[T3], type[T4], type[T5]],
493
+ /,
494
+ *,
495
+ globalns: StrMapping | None = None,
496
+ localns: StrMapping | None = None,
497
+ warn_name_errors: bool = False,
498
+ ) -> TypeGuard[Iterable[T1 | T2 | T3 | T4 | T5]]: ...
499
+ @overload
500
+ def is_iterable_of[T](
501
+ obj: Any,
502
+ cls: TypeLike[T],
503
+ /,
504
+ *,
505
+ globalns: StrMapping | None = None,
506
+ localns: StrMapping | None = None,
507
+ warn_name_errors: bool = False,
508
+ ) -> TypeGuard[Iterable[T]]: ...
509
+ def is_iterable_of[T](
510
+ obj: Any,
511
+ cls: TypeLike[T],
512
+ /,
513
+ *,
514
+ globalns: StrMapping | None = None,
515
+ localns: StrMapping | None = None,
516
+ warn_name_errors: bool = False,
517
+ ) -> TypeGuard[Iterable[T]]:
518
+ """Check if an object is a iterable of tuple or string mappings."""
519
+ return isinstance(obj, Iterable) and all(
520
+ is_instance_gen(
521
+ o,
522
+ cls,
523
+ globalns=globalns,
524
+ localns=localns,
525
+ warn_name_errors=warn_name_errors,
526
+ )
527
+ for o in obj
528
+ )
529
+
530
+
531
+ ##
532
+
533
+
295
534
  def is_list_type(obj: Any, /) -> bool:
296
535
  """Check if an object is a list type annotation."""
297
536
  return _is_annotation_of_type(obj, list)
@@ -349,6 +588,104 @@ def is_optional_type(obj: Any, /) -> bool:
349
588
  ##
350
589
 
351
590
 
591
+ @overload
592
+ def is_sequence_of[T](
593
+ obj: Any,
594
+ cls: type[T],
595
+ /,
596
+ *,
597
+ globalns: StrMapping | None = None,
598
+ localns: StrMapping | None = None,
599
+ warn_name_errors: bool = False,
600
+ ) -> TypeGuard[Sequence[T]]: ...
601
+ @overload
602
+ def is_sequence_of[T1](
603
+ obj: Any,
604
+ cls: tuple[type[T1]],
605
+ /,
606
+ *,
607
+ globalns: StrMapping | None = None,
608
+ localns: StrMapping | None = None,
609
+ warn_name_errors: bool = False,
610
+ ) -> TypeGuard[Sequence[T1]]: ...
611
+ @overload
612
+ def is_sequence_of[T1, T2](
613
+ obj: Any,
614
+ cls: tuple[type[T1], type[T2]],
615
+ /,
616
+ *,
617
+ globalns: StrMapping | None = None,
618
+ localns: StrMapping | None = None,
619
+ warn_name_errors: bool = False,
620
+ ) -> TypeGuard[Sequence[T1 | T2]]: ...
621
+ @overload
622
+ def is_sequence_of[T1, T2, T3](
623
+ obj: Any,
624
+ cls: tuple[type[T1], type[T2], type[T3]],
625
+ /,
626
+ *,
627
+ globalns: StrMapping | None = None,
628
+ localns: StrMapping | None = None,
629
+ warn_name_errors: bool = False,
630
+ ) -> TypeGuard[Sequence[T1 | T2 | T3]]: ...
631
+ @overload
632
+ def is_sequence_of[T1, T2, T3, T4](
633
+ obj: Any,
634
+ cls: tuple[type[T1], type[T2], type[T3], type[T4]],
635
+ /,
636
+ *,
637
+ globalns: StrMapping | None = None,
638
+ localns: StrMapping | None = None,
639
+ warn_name_errors: bool = False,
640
+ ) -> TypeGuard[Sequence[T1 | T2 | T3 | T4]]: ...
641
+ @overload
642
+ def is_sequence_of[T1, T2, T3, T4, T5](
643
+ obj: Any,
644
+ cls: tuple[type[T1], type[T2], type[T3], type[T4], type[T5]],
645
+ /,
646
+ *,
647
+ globalns: StrMapping | None = None,
648
+ localns: StrMapping | None = None,
649
+ warn_name_errors: bool = False,
650
+ ) -> TypeGuard[Sequence[T1 | T2 | T3 | T4 | T5]]: ...
651
+ @overload
652
+ def is_sequence_of[T](
653
+ obj: Any,
654
+ cls: TypeLike[T],
655
+ /,
656
+ *,
657
+ globalns: StrMapping | None = None,
658
+ localns: StrMapping | None = None,
659
+ warn_name_errors: bool = False,
660
+ ) -> TypeGuard[Sequence[T]]: ...
661
+ def is_sequence_of[T](
662
+ obj: Any,
663
+ cls: TypeLike[T],
664
+ /,
665
+ *,
666
+ globalns: StrMapping | None = None,
667
+ localns: StrMapping | None = None,
668
+ warn_name_errors: bool = False,
669
+ ) -> TypeGuard[Sequence[T]]:
670
+ """Check if an object is a sequence of tuple or string mappings."""
671
+ return isinstance(obj, Sequence) and is_iterable_of(
672
+ obj, cls, globalns=globalns, localns=localns, warn_name_errors=warn_name_errors
673
+ )
674
+
675
+
676
+ ##
677
+
678
+
679
+ def is_sequence_of_tuple_or_str_mapping(
680
+ obj: Any, /
681
+ ) -> TypeGuard[Sequence[TupleOrStrMapping]]:
682
+ """Check if an object is a sequence of tuple or string mappings."""
683
+ return isinstance(obj, Sequence) and all(map(is_tuple_or_str_mapping, obj))
684
+
685
+
686
+ ##
687
+
688
+
352
689
  def is_sequence_type(obj: Any, /) -> bool:
353
690
  """Check if an object is a sequence type annotation."""
354
691
  return _is_annotation_of_type(obj, Sequence)
@@ -365,6 +702,14 @@ def is_set_type(obj: Any, /) -> bool:
365
702
  ##
366
703
 
367
704
 
705
+ def is_string_mapping(obj: Any, /) -> TypeGuard[StrMapping]:
706
+ """Check if an object is a string mapping."""
707
+ return isinstance(obj, Mapping) and is_iterable_of(obj, str)
708
+
709
+
710
+ ##
711
+
712
+
368
713
  @overload
369
714
  def is_subclass_gen[T](cls: type[Any], parent: type[T], /) -> TypeGuard[type[T]]: ...
370
715
  @overload
@@ -450,6 +795,22 @@ class IsSubclassGenError(Exception):
450
795
  ##
451
796
 
452
797
 
798
+ def is_tuple(obj: Any, /) -> TypeGuard[tuple[Any, ...]]:
799
+ """Check if an object is a tuple."""
800
+ return isinstance(obj, tuple)
801
+
802
+
803
+ ##
804
+
805
+
806
+ def is_tuple_or_str_mapping(obj: Any, /) -> TypeGuard[TupleOrStrMapping]:
807
+ """Check if an object is a tuple or string mapping."""
808
+ return is_tuple(obj) or is_string_mapping(obj)
809
+
810
+
811
+ ##
812
+
813
+
453
814
  def is_tuple_type(obj: Any, /) -> bool:
454
815
  """Check if an object is a tuple type annotation."""
455
816
  return _is_annotation_of_type(obj, tuple)
@@ -474,6 +835,40 @@ def _is_annotation_of_type(obj: Any, origin: Any, /) -> bool:
474
835
  )
475
836
 
476
837
 
838
+ ##
839
+
840
+
841
+ @overload
842
+ def make_isinstance[T](cls: type[T], /) -> Callable[[Any], TypeGuard[T]]: ...
843
+ @overload
844
+ def make_isinstance[T1](cls: tuple[type[T1]], /) -> Callable[[Any], TypeGuard[T1]]: ...
845
+ @overload
846
+ def make_isinstance[T1, T2](
847
+ cls: tuple[type[T1], type[T2]], /
848
+ ) -> Callable[[Any], TypeGuard[T1 | T2]]: ...
849
+ @overload
850
+ def make_isinstance[T1, T2, T3](
851
+ cls: tuple[type[T1], type[T2], type[T3]], /
852
+ ) -> Callable[[Any], TypeGuard[T1 | T2 | T3]]: ...
853
+ @overload
854
+ def make_isinstance[T1, T2, T3, T4](
855
+ cls: tuple[type[T1], type[T2], type[T3], type[T4]], /
856
+ ) -> Callable[[Any], TypeGuard[T1 | T2 | T3 | T4]]: ...
857
+ @overload
858
+ def make_isinstance[T1, T2, T3, T4, T5](
859
+ cls: tuple[type[T1], type[T2], type[T3], type[T4], type[T5]], /
860
+ ) -> Callable[[Any], TypeGuard[T1 | T2 | T3 | T4 | T5]]: ...
861
+ @overload
862
+ def make_isinstance[T](cls: TypeLike[T], /) -> Callable[[Any], TypeGuard[T]]: ...
863
+ def make_isinstance[T](cls: TypeLike[T], /) -> Callable[[Any], TypeGuard[T]]:
864
+ """Make a curried `isinstance` function."""
865
+ return partial(_make_instance_core, cls=cls)
866
+
867
+
868
+ def _make_instance_core[T](obj: Any, /, *, cls: TypeLike[T]) -> TypeGuard[T]:
869
+ return is_instance_gen(obj, cls)
870
+
871
+
477
872
  __all__ = [
478
873
  "GetTypeClassesError",
479
874
  "GetUnionTypeClassesError",
@@ -483,18 +878,27 @@ __all__ = [
483
878
  "get_type_classes",
484
879
  "get_type_hints",
485
880
  "get_union_type_classes",
881
+ "is_dataclass_class",
882
+ "is_dataclass_instance",
486
883
  "is_dict_type",
487
884
  "is_frozenset_type",
488
885
  "is_instance_gen",
886
+ "is_iterable_of",
489
887
  "is_list_type",
490
888
  "is_literal_type",
491
889
  "is_mapping_type",
492
890
  "is_namedtuple_class",
493
891
  "is_namedtuple_instance",
494
892
  "is_optional_type",
893
+ "is_sequence_of",
894
+ "is_sequence_of_tuple_or_str_mapping",
495
895
  "is_sequence_type",
496
896
  "is_set_type",
897
+ "is_string_mapping",
497
898
  "is_subclass_gen",
899
+ "is_tuple",
900
+ "is_tuple_or_str_mapping",
498
901
  "is_tuple_type",
499
902
  "is_union_type",
903
+ "make_isinstance",
500
904
  ]