dycw-utilities 0.109.2__py3-none-any.whl → 0.109.4__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.109.2
3
+ Version: 0.109.4
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=BFspQsIuZJOmuE31YyhwkR4P62-Ad5dFurosyyABzLA,60
1
+ utilities/__init__.py,sha256=TfQi7UYijWAmuZ8wsQ7kQKbgWiSQWgaXE80fBUn8qx8,60
2
2
  utilities/altair.py,sha256=NSyDsm8QlkAGmsGdxVwCkHnPxt_35yJBa9Lg7bz9Ays,9054
3
3
  utilities/astor.py,sha256=xuDUkjq0-b6fhtwjhbnebzbqQZAjMSHR1IIS5uOodVg,777
4
4
  utilities/asyncio.py,sha256=41oQUurWMvadFK5gFnaG21hMM0Vmfn2WS6OpC0R9mas,14757
@@ -40,7 +40,7 @@ utilities/operator.py,sha256=0M2yZJ0PODH47ogFEnkGMBe_cfxwZR02T_92LZVZvHo,3715
40
40
  utilities/optuna.py,sha256=loyJGWTzljgdJaoLhP09PT8Jz6o_pwBOwehY33lHkhw,1923
41
41
  utilities/orjson.py,sha256=Wj5pzG_VdgoAy14a7Luhem-BgYrRtRFvvl_POiszRd0,36930
42
42
  utilities/os.py,sha256=D_FyyT-6TtqiN9KSS7c9g1fnUtgxmyMtzAjmYLkk46A,3587
43
- utilities/parse.py,sha256=yLLH51VNwmcWbEvwqh6M-weWt7NIayd7No67Oe80S3k,4585
43
+ utilities/parse.py,sha256=q1A1-bsXltdAh8We5Acy-lPuVIClWTxsRXQWq5wYyKk,6382
44
44
  utilities/pathlib.py,sha256=31WPMXdLIyXgYOMMl_HOI2wlo66MGSE-cgeelk-Lias,1410
45
45
  utilities/period.py,sha256=ikHXsWtDLr553cfH6p9mMaiCnIAP69B7q84ckWV3HaA,10884
46
46
  utilities/pickle.py,sha256=Bhvd7cZl-zQKQDFjUerqGuSKlHvnW1K2QXeU5UZibtg,657
@@ -75,7 +75,7 @@ utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
75
75
  utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
76
76
  utilities/traceback.py,sha256=KwHPLdEbdj0fFhXo8MBfxcvem8A-VXYDwFMNJ6f0cTM,27328
77
77
  utilities/types.py,sha256=QK8kgH80TJdh_vktaZHrCEk7f1f8kHiDr8dJlK8aSac,17814
78
- utilities/typing.py,sha256=h1vt82GUs-3ww7yFbZ3BWjdUM4NBPHaptcJHSKuRa5E,5341
78
+ utilities/typing.py,sha256=gLg4EbE1FX52fJ1d3ji4i08qolwu9qgWt8w_w_Y5DTk,5512
79
79
  utilities/tzdata.py,sha256=2ZsPmhTVM9Ptrxb4QrWKtKOB9RiH8IOO-A1u7ULdVbg,176
80
80
  utilities/tzlocal.py,sha256=42BCquGF54oIqIKe5RGziP4K8Nbm3Ey7uqcNn6m5ge8,534
81
81
  utilities/uuid.py,sha256=jJTFxz-CWgltqNuzmythB7iEQ-Q1mCwPevUfKthZT3c,611
@@ -84,7 +84,7 @@ utilities/warnings.py,sha256=yUgjnmkCRf6QhdyAXzl7u0qQFejhQG3PrjoSwxpbHrs,1819
84
84
  utilities/whenever.py,sha256=5x2t47VJmJRWcd_NLFy54NkB3uom-XQYxEbLtEfL1bs,17775
85
85
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
86
86
  utilities/zoneinfo.py,sha256=-DQz5a0Ikw9jfSZtL0BEQkXOMC9yGn_xiJYNCLMiqEc,1989
87
- dycw_utilities-0.109.2.dist-info/METADATA,sha256=9cmTxKEFxwqlG4VNbAnM88UGivDzayFQlB0ntOvtBno,13004
88
- dycw_utilities-0.109.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
89
- dycw_utilities-0.109.2.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
90
- dycw_utilities-0.109.2.dist-info/RECORD,,
87
+ dycw_utilities-0.109.4.dist-info/METADATA,sha256=-fU_tSw6cCv59Fg6s0I0pUlV3YzXeuq15kfirsKUmik,13004
88
+ dycw_utilities-0.109.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
89
+ dycw_utilities-0.109.4.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
90
+ dycw_utilities-0.109.4.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.109.2"
3
+ __version__ = "0.109.4"
utilities/parse.py CHANGED
@@ -5,30 +5,44 @@ from contextlib import suppress
5
5
  from dataclasses import dataclass
6
6
  from enum import Enum
7
7
  from pathlib import Path
8
+ from re import DOTALL
8
9
  from types import NoneType
9
- from typing import Any, override
10
+ from typing import TYPE_CHECKING, Any, TypeVar, override
10
11
 
11
12
  from utilities.datetime import is_subclass_date_not_datetime
12
13
  from utilities.enum import ParseEnumError, parse_enum
13
14
  from utilities.functions import is_subclass_int_not_bool
14
- from utilities.iterables import one, one_str
15
+ from utilities.iterables import OneEmptyError, OneNonUniqueError, one, one_str
16
+ from utilities.re import ExtractGroupError, extract_group
15
17
  from utilities.sentinel import ParseSentinelError, Sentinel, parse_sentinel
16
18
  from utilities.text import ParseBoolError, ParseNoneError, parse_bool, parse_none
17
- from utilities.typing import get_args, is_literal_type, is_optional_type
19
+ from utilities.typing import get_args, is_literal_type, is_optional_type, is_tuple_type
18
20
  from utilities.version import ParseVersionError, Version, parse_version
19
21
 
22
+ if TYPE_CHECKING:
23
+ from collections.abc import Callable, Mapping
24
+
25
+
26
+ _T = TypeVar("_T")
27
+
20
28
 
21
29
  def parse_text(
22
- obj: Any, text: str, /, *, case_sensitive: bool = False, head: bool = False
30
+ obj: Any,
31
+ text: str,
32
+ /,
33
+ *,
34
+ case_sensitive: bool = False,
35
+ head: bool = False,
36
+ extra: Mapping[type[_T], Callable[[str], _T]] | None = None,
23
37
  ) -> Any:
24
38
  """Parse text."""
25
39
  if obj is None:
26
40
  try:
27
41
  return parse_none(text)
28
42
  except ParseNoneError:
29
- raise ParseTextError(obj=obj, text=text) from None
43
+ raise _ParseTextParseError(obj=obj, text=text) from None
30
44
  if isinstance(obj, type):
31
- return _parse_text_type(obj, text, case_sensitive=case_sensitive)
45
+ return _parse_text_type(obj, text, case_sensitive=case_sensitive, extra=extra)
32
46
  if is_literal_type(obj):
33
47
  return one_str(get_args(obj), text, head=head, case_sensitive=case_sensitive)
34
48
  if is_optional_type(obj):
@@ -40,83 +54,111 @@ def parse_text(
40
54
  ):
41
55
  try:
42
56
  return _parse_text_type(inner, text, case_sensitive=case_sensitive)
43
- except ParseTextError:
44
- raise ParseTextError(obj=obj, text=text) from None
45
- raise ParseTextError(obj=obj, text=text) from None
57
+ except _ParseTextParseError:
58
+ raise _ParseTextParseError(obj=obj, text=text) from None
59
+ if is_tuple_type(obj):
60
+ args = get_args(obj)
61
+ try:
62
+ texts = extract_group(r"^\((.*)\)$", text, flags=DOTALL).split(", ")
63
+ except ExtractGroupError:
64
+ raise _ParseTextParseError(obj=obj, text=text) from None
65
+ if len(args) != len(texts):
66
+ raise _ParseTextParseError(obj=obj, text=text)
67
+ return tuple(
68
+ parse_text(arg, text, case_sensitive=case_sensitive, head=head)
69
+ for arg, text in zip(args, texts, strict=True)
70
+ )
71
+ raise _ParseTextParseError(obj=obj, text=text) from None
46
72
 
47
73
 
48
74
  def _parse_text_type(
49
- cls: type[Any], text: str, /, *, case_sensitive: bool = False
75
+ cls: type[Any],
76
+ text: str,
77
+ /,
78
+ *,
79
+ case_sensitive: bool = False,
80
+ extra: Mapping[type[_T], Callable[[str], _T]] | None = None,
50
81
  ) -> Any:
51
82
  """Parse text."""
52
83
  if issubclass(cls, NoneType):
53
84
  try:
54
85
  return parse_none(text)
55
86
  except ParseNoneError:
56
- raise ParseTextError(obj=cls, text=text) from None
87
+ raise _ParseTextParseError(obj=cls, text=text) from None
57
88
  if issubclass(cls, str):
58
89
  return text
59
90
  if issubclass(cls, bool):
60
91
  try:
61
92
  return parse_bool(text)
62
93
  except ParseBoolError:
63
- raise ParseTextError(obj=cls, text=text) from None
94
+ raise _ParseTextParseError(obj=cls, text=text) from None
64
95
  if is_subclass_int_not_bool(cls):
65
96
  try:
66
97
  return int(text)
67
98
  except ValueError:
68
- raise ParseTextError(obj=cls, text=text) from None
99
+ raise _ParseTextParseError(obj=cls, text=text) from None
69
100
  if issubclass(cls, float):
70
101
  try:
71
102
  return float(text)
72
103
  except ValueError:
73
- raise ParseTextError(obj=cls, text=text) from None
104
+ raise _ParseTextParseError(obj=cls, text=text) from None
74
105
  if issubclass(cls, Enum):
75
106
  try:
76
107
  return parse_enum(text, cls, case_sensitive=case_sensitive)
77
108
  except ParseEnumError:
78
- raise ParseTextError(obj=cls, text=text) from None
109
+ raise _ParseTextParseError(obj=cls, text=text) from None
79
110
  if issubclass(cls, Path):
80
111
  return Path(text).expanduser()
81
112
  if issubclass(cls, Sentinel):
82
113
  try:
83
114
  return parse_sentinel(text)
84
115
  except ParseSentinelError:
85
- raise ParseTextError(obj=cls, text=text) from None
116
+ raise _ParseTextParseError(obj=cls, text=text) from None
86
117
  if issubclass(cls, Version):
87
118
  try:
88
119
  return parse_version(text)
89
120
  except ParseVersionError:
90
- raise ParseTextError(obj=cls, text=text) from None
121
+ raise _ParseTextParseError(obj=cls, text=text) from None
91
122
  if is_subclass_date_not_datetime(cls):
92
123
  from utilities.whenever import ParseDateError, parse_date
93
124
 
94
125
  try:
95
126
  return parse_date(text)
96
127
  except ParseDateError:
97
- raise ParseTextError(obj=cls, text=text) from None
128
+ raise _ParseTextParseError(obj=cls, text=text) from None
98
129
  if issubclass(cls, dt.datetime):
99
130
  from utilities.whenever import ParseDateTimeError, parse_datetime
100
131
 
101
132
  try:
102
133
  return parse_datetime(text)
103
134
  except ParseDateTimeError:
104
- raise ParseTextError(obj=cls, text=text) from None
135
+ raise _ParseTextParseError(obj=cls, text=text) from None
105
136
  if issubclass(cls, dt.time):
106
137
  from utilities.whenever import ParseTimeError, parse_time
107
138
 
108
139
  try:
109
140
  return parse_time(text)
110
141
  except ParseTimeError:
111
- raise ParseTextError(obj=cls, text=text) from None
142
+ raise _ParseTextParseError(obj=cls, text=text) from None
112
143
  if issubclass(cls, dt.timedelta):
113
144
  from utilities.whenever import ParseTimedeltaError, parse_timedelta
114
145
 
115
146
  try:
116
147
  return parse_timedelta(text)
117
148
  except ParseTimedeltaError:
118
- raise ParseTextError(obj=cls, text=text) from None
119
- raise ParseTextError(obj=cls, text=text) from None
149
+ raise _ParseTextParseError(obj=cls, text=text) from None
150
+ if extra is not None:
151
+ try:
152
+ parser = one(p for c, p in extra.items() if issubclass(cls, c))
153
+ except OneEmptyError:
154
+ pass
155
+ except OneNonUniqueError as error:
156
+ raise _ParseTextExtraNonUniqueError(
157
+ obj=cls, text=text, first=error.first, second=error.second
158
+ ) from None
159
+ else:
160
+ return parser(text)
161
+ raise _ParseTextParseError(obj=cls, text=text) from None
120
162
 
121
163
 
122
164
  @dataclass
@@ -124,6 +166,19 @@ class ParseTextError(Exception):
124
166
  obj: Any
125
167
  text: str
126
168
 
169
+
170
+ @dataclass
171
+ class _ParseTextParseError(ParseTextError):
127
172
  @override
128
173
  def __str__(self) -> str:
129
174
  return f"Unable to parse {self.obj!r}; got {self.text!r}"
175
+
176
+
177
+ @dataclass
178
+ class _ParseTextExtraNonUniqueError(ParseTextError):
179
+ first: type[Any]
180
+ second: type[Any]
181
+
182
+ @override
183
+ def __str__(self) -> str:
184
+ return f"Unable to parse {self.obj!r} since `extra` must contain exactly one parent class; got {self.first!r}, {self.second!r} and perhaps more"
utilities/typing.py CHANGED
@@ -182,6 +182,14 @@ def is_set_type(obj: Any, /) -> bool:
182
182
  ##
183
183
 
184
184
 
185
+ def is_tuple_type(obj: Any, /) -> bool:
186
+ """Check if an object is a tuple type annotation."""
187
+ return _is_annotation_of_type(obj, tuple)
188
+
189
+
190
+ ##
191
+
192
+
185
193
  def is_union_type(obj: Any, /) -> bool:
186
194
  """Check if an object is a union type annotation."""
187
195
  is_old_union = _is_annotation_of_type(obj, Union) # pyright: ignore[reportDeprecated]
@@ -212,5 +220,6 @@ __all__ = [
212
220
  "is_optional_type",
213
221
  "is_sequence_type",
214
222
  "is_set_type",
223
+ "is_tuple_type",
215
224
  "is_union_type",
216
225
  ]