dycw-utilities 0.110.6__py3-none-any.whl → 0.110.8__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.110.6
3
+ Version: 0.110.8
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=ob4l7mBwFaS3Yaje03_AcerZOVTXkba6PSattZmooZA,60
1
+ utilities/__init__.py,sha256=USEDB4wi-U2inTRQAJuZ7L5i_LnX5kcGM17wMf2dOck,60
2
2
  utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
3
3
  utilities/astor.py,sha256=xuDUkjq0-b6fhtwjhbnebzbqQZAjMSHR1IIS5uOodVg,777
4
4
  utilities/asyncio.py,sha256=41oQUurWMvadFK5gFnaG21hMM0Vmfn2WS6OpC0R9mas,14757
@@ -18,7 +18,7 @@ utilities/errors.py,sha256=BtSNP0JC3ik536ddPyTerLomCRJV9f6kdMe6POz0QHM,361
18
18
  utilities/eventkit.py,sha256=6M5Xu1SzN-juk9PqBHwy5dS-ta7T0qA6SMpDsakOJ0E,13039
19
19
  utilities/fastapi.py,sha256=uwqOGbGwzIbP-lfm-ApG1ZEN3BA_TDsaiuTghhLmxb8,2413
20
20
  utilities/fpdf2.py,sha256=zM3gwOYcAfv7P4qhbyvzPmRY4PPAiAQ-ZnPC6I9SZ1M,1832
21
- utilities/functions.py,sha256=yAoJoopaxf9ip6mJC5Ui7b0GyYhE-gvl0kDkoI8agZw,27667
21
+ utilities/functions.py,sha256=lAJeERNrmcpwon3drcTIlizLVRd8D-gdXojxtKFN0LM,28736
22
22
  utilities/functools.py,sha256=WrpHt7NLNWSUn9A1Q_ZIWlNaYZOEI4IFKyBG9HO3BC4,1643
23
23
  utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
24
24
  utilities/git.py,sha256=wpt5dZ5Oi5931pN24_VLZYaQOvmR0OcQuVtgHzFUN1k,2359
@@ -41,7 +41,7 @@ utilities/operator.py,sha256=0M2yZJ0PODH47ogFEnkGMBe_cfxwZR02T_92LZVZvHo,3715
41
41
  utilities/optuna.py,sha256=loyJGWTzljgdJaoLhP09PT8Jz6o_pwBOwehY33lHkhw,1923
42
42
  utilities/orjson.py,sha256=Wj5pzG_VdgoAy14a7Luhem-BgYrRtRFvvl_POiszRd0,36930
43
43
  utilities/os.py,sha256=D_FyyT-6TtqiN9KSS7c9g1fnUtgxmyMtzAjmYLkk46A,3587
44
- utilities/parse.py,sha256=zfvVczg4wQUvjgCEa_y36LSeY9WFbsdibI3n4S7_gzA,19076
44
+ utilities/parse.py,sha256=fki2mnPGa1Ex-aeWiXDkUBHWb7FEk4F6AzMiHjqHXdw,19081
45
45
  utilities/pathlib.py,sha256=31WPMXdLIyXgYOMMl_HOI2wlo66MGSE-cgeelk-Lias,1410
46
46
  utilities/period.py,sha256=ikHXsWtDLr553cfH6p9mMaiCnIAP69B7q84ckWV3HaA,10884
47
47
  utilities/pickle.py,sha256=Bhvd7cZl-zQKQDFjUerqGuSKlHvnW1K2QXeU5UZibtg,657
@@ -78,7 +78,7 @@ utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
78
78
  utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
79
79
  utilities/traceback.py,sha256=KwHPLdEbdj0fFhXo8MBfxcvem8A-VXYDwFMNJ6f0cTM,27328
80
80
  utilities/types.py,sha256=kVY71hZkcnyYNIlYSse0mLm8yeP3OBkzhDPMME6jXxo,18126
81
- utilities/typing.py,sha256=gLg4EbE1FX52fJ1d3ji4i08qolwu9qgWt8w_w_Y5DTk,5512
81
+ utilities/typing.py,sha256=WE3UWaVYO6nTkpEo8Ke3xUuxllsJtfOxsZ0-mSH69Nw,6668
82
82
  utilities/tzdata.py,sha256=2ZsPmhTVM9Ptrxb4QrWKtKOB9RiH8IOO-A1u7ULdVbg,176
83
83
  utilities/tzlocal.py,sha256=42BCquGF54oIqIKe5RGziP4K8Nbm3Ey7uqcNn6m5ge8,534
84
84
  utilities/uuid.py,sha256=jJTFxz-CWgltqNuzmythB7iEQ-Q1mCwPevUfKthZT3c,611
@@ -87,7 +87,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
87
87
  utilities/whenever.py,sha256=TjoTAJ1R27-rKXiXzdE4GzPidmYqm0W58XydDXp-QZM,17786
88
88
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
89
89
  utilities/zoneinfo.py,sha256=-DQz5a0Ikw9jfSZtL0BEQkXOMC9yGn_xiJYNCLMiqEc,1989
90
- dycw_utilities-0.110.6.dist-info/METADATA,sha256=yWIsOFSUfhENwHLyGfluM7z4Mu196QFRVefbcAqROf4,13004
91
- dycw_utilities-0.110.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
- dycw_utilities-0.110.6.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
- dycw_utilities-0.110.6.dist-info/RECORD,,
90
+ dycw_utilities-0.110.8.dist-info/METADATA,sha256=ZVWP8hzsSisjUrY3uoJqCIoh5j7pVhSy0PwDigCaJRw,13004
91
+ dycw_utilities-0.110.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
+ dycw_utilities-0.110.8.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
+ dycw_utilities-0.110.8.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.110.6"
3
+ __version__ = "0.110.8"
utilities/functions.py CHANGED
@@ -22,6 +22,7 @@ from typing import (
22
22
  Literal,
23
23
  TypeGuard,
24
24
  TypeVar,
25
+ assert_never,
25
26
  cast,
26
27
  overload,
27
28
  override,
@@ -182,6 +183,8 @@ def ensure_class(
182
183
  *,
183
184
  nullable: Literal[False] = False,
184
185
  ) -> _T1 | _T2 | _T3 | _T4 | _T5: ...
186
+ @overload
187
+ def ensure_class(obj: Any, cls: TypeLike[_T], /, *, nullable: bool = False) -> Any: ...
185
188
  def ensure_class(obj: Any, cls: TypeLike[_T], /, *, nullable: bool = False) -> Any:
186
189
  """Ensure an object is of the required class."""
187
190
  if isinstance(obj, cls) or ((obj is None) and nullable):
@@ -657,9 +660,23 @@ def is_hashable(obj: Any, /) -> TypeGuard[Hashable]:
657
660
  ##
658
661
 
659
662
 
660
- def is_instance_int_not_bool(obj: Any, /) -> TypeGuard[int]:
661
- """Check if an object is an integer, and not a boolean."""
662
- return isinstance(obj, int) and not isinstance(obj, bool)
663
+ def is_instance_not_bool_int(
664
+ obj: Any, class_or_tuple: TypeLike[Any], /
665
+ ) -> TypeGuard[int]:
666
+ """Check if an instance relationship holds, except bool<int."""
667
+ match class_or_tuple:
668
+ case type() as type_:
669
+ return _is_instance_not_bool_int(obj, type_)
670
+ case tuple() as types:
671
+ return any(_is_instance_not_bool_int(obj, p) for p in types)
672
+ case _ as never:
673
+ assert_never(never)
674
+
675
+
676
+ def _is_instance_not_bool_int(obj: Any, type_: type[Any], /) -> bool:
677
+ return isinstance(obj, type_) and not (
678
+ isinstance(obj, bool) and issubclass(type_, int) and not issubclass(type_, bool)
679
+ )
663
680
 
664
681
 
665
682
  ##
@@ -779,9 +796,23 @@ def is_string_mapping(obj: Any, /) -> TypeGuard[StrMapping]:
779
796
  ##
780
797
 
781
798
 
782
- def is_subclass_int_not_bool(cls: type[Any], /) -> TypeGuard[type[int]]:
783
- """Check if a class is an integer, and not a boolean."""
784
- return issubclass(cls, int) and not issubclass(cls, bool)
799
+ def is_subclass_not_bool_int(cls: type[Any], class_or_tuple: TypeLike[Any], /) -> bool:
800
+ """Check if a subclass relationship holds, except bool<int."""
801
+ match class_or_tuple:
802
+ case type() as parent:
803
+ return _is_subclass_int_not_bool_one(cls, parent)
804
+ case tuple() as parents:
805
+ return any(_is_subclass_int_not_bool_one(cls, p) for p in parents)
806
+ case _ as never:
807
+ assert_never(never)
808
+
809
+
810
+ def _is_subclass_int_not_bool_one(cls: type[Any], parent: type[Any], /) -> bool:
811
+ return issubclass(cls, parent) and not (
812
+ issubclass(cls, bool)
813
+ and issubclass(parent, int)
814
+ and not issubclass(parent, bool)
815
+ )
785
816
 
786
817
 
787
818
  ##
@@ -1044,7 +1075,7 @@ __all__ = [
1044
1075
  "is_dataclass_class",
1045
1076
  "is_dataclass_instance",
1046
1077
  "is_hashable",
1047
- "is_instance_int_not_bool",
1078
+ "is_instance_not_bool_int",
1048
1079
  "is_iterable_of",
1049
1080
  "is_none",
1050
1081
  "is_not_none",
@@ -1052,7 +1083,7 @@ __all__ = [
1052
1083
  "is_sized",
1053
1084
  "is_sized_not_str",
1054
1085
  "is_string_mapping",
1055
- "is_subclass_int_not_bool",
1086
+ "is_subclass_not_bool_int",
1056
1087
  "is_tuple",
1057
1088
  "is_tuple_or_str_mapping",
1058
1089
  "make_isinstance",
utilities/parse.py CHANGED
@@ -14,7 +14,7 @@ from utilities.datetime import (
14
14
  is_subclass_date_not_datetime,
15
15
  )
16
16
  from utilities.enum import ParseEnumError, parse_enum
17
- from utilities.functions import is_subclass_int_not_bool
17
+ from utilities.functions import is_subclass_not_bool_int
18
18
  from utilities.iterables import OneEmptyError, OneNonUniqueError, one, one_str
19
19
  from utilities.math import ParseNumberError, parse_number
20
20
  from utilities.re import ExtractGroupError, extract_group
@@ -174,7 +174,7 @@ def _parse_object_type(
174
174
  return parse_bool(text)
175
175
  except ParseBoolError:
176
176
  raise _ParseObjectParseError(type_=cls, text=text) from None
177
- if is_subclass_int_not_bool(cls):
177
+ if is_subclass_not_bool_int(cls, int):
178
178
  try:
179
179
  return int(text)
180
180
  except ValueError:
utilities/typing.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import datetime as dt
4
4
  from collections.abc import Mapping, Sequence
5
+ from dataclasses import dataclass
5
6
  from itertools import chain
6
7
  from pathlib import Path
7
8
  from types import NoneType, UnionType
@@ -15,6 +16,7 @@ from typing import (
15
16
  TypeGuard,
16
17
  Union, # pyright: ignore[reportDeprecated]
17
18
  get_origin,
19
+ override,
18
20
  )
19
21
  from typing import get_args as _get_args
20
22
  from typing import get_type_hints as _get_type_hints
@@ -64,7 +66,7 @@ def _get_literal_elements_inner(obj: Any, /) -> list[Any]:
64
66
 
65
67
 
66
68
  def get_type_hints(
67
- cls: Any,
69
+ obj: Any,
68
70
  /,
69
71
  *,
70
72
  globalns: StrMapping | None = None,
@@ -72,15 +74,15 @@ def get_type_hints(
72
74
  warn_name_errors: bool = False,
73
75
  ) -> dict[str, Any]:
74
76
  """Get the type hints of an object."""
75
- result: dict[str, Any] = cls.__annotations__
77
+ result: dict[str, Any] = obj.__annotations__
76
78
  _ = {Literal, Path, Sentinel, StrMapping, UUID, dt}
77
79
  globalns_use = globals() | ({} if globalns is None else dict(globalns))
78
80
  localns_use = {} if localns is None else dict(localns)
79
81
  try:
80
- hints = _get_type_hints(cls, globalns=globalns_use, localns=localns_use)
82
+ hints = _get_type_hints(obj, globalns=globalns_use, localns=localns_use)
81
83
  except NameError as error:
82
84
  if warn_name_errors:
83
- warn(f"Error getting type hints for {cls!r}; {error}", stacklevel=2)
85
+ warn(f"Error getting type hints for {obj!r}; {error}", stacklevel=2)
84
86
  else:
85
87
  result.update({
86
88
  key: value
@@ -93,6 +95,44 @@ def get_type_hints(
93
95
  ##
94
96
 
95
97
 
98
+ def get_union_type_classes(obj: Any, /) -> tuple[type[Any], ...]:
99
+ if not is_union_type(obj):
100
+ raise _GetUnionTypeClassesNotAUnionTypeError(obj=obj)
101
+ types_: Sequence[type[Any]] = []
102
+ for arg in get_args(obj):
103
+ if isinstance(arg, type):
104
+ types_.append(arg)
105
+ elif is_union_type(arg):
106
+ types_.extend(get_union_type_classes(arg))
107
+ else:
108
+ raise _GetUnionTypeClassesNotATypeError(obj=obj, inner=arg)
109
+ return tuple(types_)
110
+
111
+
112
+ @dataclass(kw_only=True, slots=True)
113
+ class GetUnionTypeClassesError(Exception):
114
+ obj: Any
115
+
116
+
117
+ @dataclass(kw_only=True, slots=True)
118
+ class _GetUnionTypeClassesNotAUnionTypeError(GetUnionTypeClassesError):
119
+ @override
120
+ def __str__(self) -> str:
121
+ return f"Object must be a Union type; got {self.obj}"
122
+
123
+
124
+ @dataclass(kw_only=True, slots=True)
125
+ class _GetUnionTypeClassesNotATypeError(GetUnionTypeClassesError):
126
+ inner: Any
127
+
128
+ @override
129
+ def __str__(self) -> str:
130
+ return f"Union type must contain types only; got {self.inner}"
131
+
132
+
133
+ ##
134
+
135
+
96
136
  def is_dict_type(obj: Any, /) -> bool:
97
137
  """Check if an object is a dict type annotation."""
98
138
  return _is_annotation_of_type(obj, dict)
@@ -207,9 +247,11 @@ def _is_annotation_of_type(obj: Any, origin: Any, /) -> bool:
207
247
 
208
248
 
209
249
  __all__ = [
250
+ "GetUnionTypeClassesError",
210
251
  "contains_self",
211
252
  "get_literal_elements",
212
253
  "get_type_hints",
254
+ "get_union_type_classes",
213
255
  "is_dict_type",
214
256
  "is_frozenset_type",
215
257
  "is_list_type",