dycw-utilities 0.110.0__py3-none-any.whl → 0.110.2__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.0
3
+ Version: 0.110.2
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=H5Ma2FOh3z_dBcZTVaSY2VI14JUFHWspBQs7dYAHnjc,60
1
+ utilities/__init__.py,sha256=N5L_-mBy__dabTcP63GXeZ0UmhNEqKjJ0SR5XKVgg5Y,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
@@ -11,7 +11,7 @@ utilities/contextlib.py,sha256=OOIIEa5lXKGzFAnauaul40nlQnQko6Na4ryiMJcHkIg,478
11
11
  utilities/contextvars.py,sha256=RsSGGrbQqqZ67rOydnM7WWIsM2lIE31UHJLejnHJPWY,505
12
12
  utilities/cryptography.py,sha256=HyOewI20cl3uRXsKivhIaeLVDInQdzgXZGaly7hS5dE,771
13
13
  utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
14
- utilities/dataclasses.py,sha256=vkrcV-qnMS4qGHJ2z3kmcugNH_Fk20SPXfttXb3H5Bs,25950
14
+ utilities/dataclasses.py,sha256=e2YuCj1DF4eecFFxsu20MknghXJ4SNkWCmbIAOXXr18,25942
15
15
  utilities/datetime.py,sha256=GOs-MIEW_A49kzqa1yhIoeNeSqqPVgGO-h2AThtgTDk,37326
16
16
  utilities/enum.py,sha256=HoRwVCWzsnH0vpO9ZEcAAIZLMv0Sn2vJxxA4sYMQgDs,5793
17
17
  utilities/errors.py,sha256=BtSNP0JC3ik536ddPyTerLomCRJV9f6kdMe6POz0QHM,361
@@ -73,7 +73,7 @@ utilities/streamlit.py,sha256=U9PJBaKP1IdSykKhPZhIzSPTZsmLsnwbEPZWzNhJPKk,2955
73
73
  utilities/sys.py,sha256=h0Xr7Vj86wNalvwJVP1wj5Y0kD_VWm1vzuXZ_jw94mE,2743
74
74
  utilities/tempfile.py,sha256=VqmZJAhTJ1OaVywFzk5eqROV8iJbW9XQ_QYAV0bpdRo,1384
75
75
  utilities/tenacity.py,sha256=1PUvODiBVgeqIh7G5TRt5WWMSqjLYkEqP53itT97WQc,4914
76
- utilities/text.py,sha256=Ax_n-nY80_onWxag9M0PkmbaAqwyut9AEA9tEMd5lBs,6694
76
+ utilities/text.py,sha256=VcNAJtjA2BOGEVY6yXnC9UIAsgECgiWCQ_QnpilZV2o,10089
77
77
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
78
78
  utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
79
79
  utilities/traceback.py,sha256=KwHPLdEbdj0fFhXo8MBfxcvem8A-VXYDwFMNJ6f0cTM,27328
@@ -87,7 +87,7 @@ utilities/warnings.py,sha256=yUgjnmkCRf6QhdyAXzl7u0qQFejhQG3PrjoSwxpbHrs,1819
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.0.dist-info/METADATA,sha256=z9nJyYO-djaRWCX7iT7S0vdy40gXfUedyu_jhw3O-Cs,13004
91
- dycw_utilities-0.110.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
- dycw_utilities-0.110.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
- dycw_utilities-0.110.0.dist-info/RECORD,,
90
+ dycw_utilities-0.110.2.dist-info/METADATA,sha256=TK9SINDsRCnIKjF2YyzOvlQ5mTpD1hjJJ6SD_aNKFI4,13004
91
+ dycw_utilities-0.110.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
+ dycw_utilities-0.110.2.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
+ dycw_utilities-0.110.2.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.110.0"
3
+ __version__ = "0.110.2"
utilities/dataclasses.py CHANGED
@@ -439,7 +439,7 @@ def serialize_dataclass(
439
439
 
440
440
 
441
441
  def parse_dataclass(
442
- text_or_mapping: str | Mapping[str, str],
442
+ text_or_mapping: str | StrStrMapping,
443
443
  cls: type[TDataclass],
444
444
  /,
445
445
  *,
@@ -505,7 +505,7 @@ def _parse_dataclass_split_key_value_pairs(
505
505
  *,
506
506
  list_separator: str = LIST_SEPARATOR,
507
507
  pair_separator: str = PAIR_SEPARATOR,
508
- ) -> Mapping[str, str]:
508
+ ) -> StrStrMapping:
509
509
  try:
510
510
  return split_key_value_pairs(
511
511
  text,
utilities/text.py CHANGED
@@ -1,12 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
+ from collections import deque
4
5
  from dataclasses import dataclass
5
- from re import IGNORECASE, Match, search
6
+ from itertools import chain
7
+ from re import IGNORECASE, Match, escape, search
6
8
  from textwrap import dedent
7
9
  from typing import TYPE_CHECKING, Any, Literal, overload, override
8
10
 
9
- from utilities.iterables import CheckDuplicatesError, check_duplicates
11
+ from utilities.iterables import CheckDuplicatesError, check_duplicates, transpose
10
12
  from utilities.reprlib import get_repr
11
13
 
12
14
  if TYPE_CHECKING:
@@ -167,47 +169,134 @@ class _SplitKeyValuePairsDuplicateKeysError(SplitKeyValuePairsError):
167
169
 
168
170
 
169
171
  @overload
170
- def split_str(text: str, /, *, separator: str = ",", n: Literal[1]) -> tuple[str]: ...
172
+ def split_str(
173
+ text: str,
174
+ /,
175
+ *,
176
+ separator: str = ",",
177
+ brackets: Iterable[tuple[str, str]] | None = None,
178
+ n: Literal[1],
179
+ ) -> tuple[str]: ...
171
180
  @overload
172
181
  def split_str(
173
- text: str, /, *, separator: str = ",", n: Literal[2]
182
+ text: str,
183
+ /,
184
+ *,
185
+ separator: str = ",",
186
+ brackets: Iterable[tuple[str, str]] | None = None,
187
+ n: Literal[2],
174
188
  ) -> tuple[str, str]: ...
175
189
  @overload
176
190
  def split_str(
177
- text: str, /, *, separator: str = ",", n: Literal[3]
191
+ text: str,
192
+ /,
193
+ *,
194
+ separator: str = ",",
195
+ brackets: Iterable[tuple[str, str]] | None = None,
196
+ n: Literal[3],
178
197
  ) -> tuple[str, str, str]: ...
179
198
  @overload
180
199
  def split_str(
181
- text: str, /, *, separator: str = ",", n: Literal[4]
200
+ text: str,
201
+ /,
202
+ *,
203
+ separator: str = ",",
204
+ brackets: Iterable[tuple[str, str]] | None = None,
205
+ n: Literal[4],
182
206
  ) -> tuple[str, str, str, str]: ...
183
207
  @overload
184
208
  def split_str(
185
- text: str, /, *, separator: str = ",", n: Literal[5]
209
+ text: str,
210
+ /,
211
+ *,
212
+ separator: str = ",",
213
+ brackets: Iterable[tuple[str, str]] | None = None,
214
+ n: Literal[5],
186
215
  ) -> tuple[str, str, str, str, str]: ...
187
216
  @overload
188
217
  def split_str(
189
- text: str, /, *, separator: str = ",", n: int | None = None
218
+ text: str,
219
+ /,
220
+ *,
221
+ separator: str = ",",
222
+ brackets: Iterable[tuple[str, str]] | None = None,
223
+ n: int | None = None,
190
224
  ) -> Sequence[str]: ...
191
225
  def split_str(
192
- text: str, /, *, separator: str = ",", n: int | None = None
226
+ text: str,
227
+ /,
228
+ *,
229
+ separator: str = ",",
230
+ brackets: Iterable[tuple[str, str]] | None = None,
231
+ n: int | None = None,
193
232
  ) -> Sequence[str]:
194
233
  """Split a string, with a special provision for the empty string."""
195
234
  if text == "":
196
235
  texts = []
197
236
  elif text == _escape_separator(separator=separator):
198
237
  texts = [""]
199
- else:
238
+ elif brackets is None:
200
239
  texts = text.split(separator)
240
+ else:
241
+ texts = _split_str_brackets(text, brackets, separator=separator)
201
242
  if n is None:
202
243
  return texts
203
244
  if len(texts) != n:
204
- raise SplitStrError(text=text, n=n, texts=texts)
245
+ raise _SplitStrCountError(text=text, n=n, texts=texts)
205
246
  return tuple(texts)
206
247
 
207
248
 
249
+ def _split_str_brackets(
250
+ text: str, brackets: Iterable[tuple[str, str]], /, *, separator: str = ","
251
+ ) -> Sequence[str]:
252
+ brackets = list(brackets)
253
+ opens, closes = transpose(brackets)
254
+ close_to_open = {close: open_ for open_, close in brackets}
255
+
256
+ escapes = map(escape, chain(chain.from_iterable(brackets), [separator]))
257
+ pattern = re.compile("|".join(escapes))
258
+
259
+ results: Sequence[str] = []
260
+ stack: deque[tuple[str, int]] = deque()
261
+ last = 0
262
+
263
+ for match in pattern.finditer(text):
264
+ token, position = match.group(), match.start()
265
+ if token in opens:
266
+ stack.append((token, position))
267
+ elif token in closes:
268
+ if len(stack) == 0:
269
+ raise _SplitStrClosingBracketUnmatchedError(
270
+ text=text, token=token, position=position
271
+ )
272
+ open_token, open_position = stack.pop()
273
+ if open_token != close_to_open[token]:
274
+ raise _SplitStrClosingBracketMismatchedError(
275
+ text=text,
276
+ opening_token=open_token,
277
+ opening_position=open_position,
278
+ closing_token=token,
279
+ closing_position=position,
280
+ )
281
+ elif (token == separator) and (len(stack) == 0):
282
+ results.append(text[last:position].strip())
283
+ last = position + 1
284
+ results.append(text[last:].strip())
285
+ if len(stack) >= 1:
286
+ token, position = stack.pop()
287
+ raise _SplitStrOpeningBracketUnmatchedError(
288
+ text=text, token=token, position=position
289
+ )
290
+ return results
291
+
292
+
208
293
  @dataclass(kw_only=True, slots=True)
209
294
  class SplitStrError(Exception):
210
295
  text: str
296
+
297
+
298
+ @dataclass(kw_only=True, slots=True)
299
+ class _SplitStrCountError(SplitStrError):
211
300
  n: int
212
301
  texts: Sequence[str]
213
302
 
@@ -216,6 +305,38 @@ class SplitStrError(Exception):
216
305
  return f"Unable to split {self.text!r} into {self.n} part(s); got {len(self.texts)}"
217
306
 
218
307
 
308
+ @dataclass(kw_only=True, slots=True)
309
+ class _SplitStrClosingBracketMismatchedError(SplitStrError):
310
+ opening_token: str
311
+ opening_position: int
312
+ closing_token: str
313
+ closing_position: int
314
+
315
+ @override
316
+ def __str__(self) -> str:
317
+ return f"Unable to split {self.text!r}; got mismatched {self.opening_token!r} at position {self.opening_position} and {self.closing_token!r} at position {self.closing_position}"
318
+
319
+
320
+ @dataclass(kw_only=True, slots=True)
321
+ class _SplitStrClosingBracketUnmatchedError(SplitStrError):
322
+ token: str
323
+ position: int
324
+
325
+ @override
326
+ def __str__(self) -> str:
327
+ return f"Unable to split {self.text!r}; got unmatched {self.token!r} at position {self.position}"
328
+
329
+
330
+ @dataclass(kw_only=True, slots=True)
331
+ class _SplitStrOpeningBracketUnmatchedError(SplitStrError):
332
+ token: str
333
+ position: int
334
+
335
+ @override
336
+ def __str__(self) -> str:
337
+ return f"Unable to split {self.text!r}; got unmatched {self.token!r} at position {self.position}"
338
+
339
+
219
340
  def join_strs(
220
341
  texts: Iterable[str], /, *, sort: bool = False, separator: str = ","
221
342
  ) -> str: