dycw-utilities 0.162.5__py3-none-any.whl → 0.162.7__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.162.5
3
+ Version: 0.162.7
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=jl4cIqGO8g8C1ZH4-qgjMre-gDVM9BArkr0mK4k-V8s,60
1
+ utilities/__init__.py,sha256=Dcz0z6bhNKqyHot7MCQfuWzE8TliQJMNyt--tNT0EAs,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
@@ -39,7 +39,7 @@ utilities/more_itertools.py,sha256=syfIPhQF_WS-YiicdGe2h5F1G-Ld12Q2XsVduL2hA40,1
39
39
  utilities/numpy.py,sha256=Xn23sA2ZbVNqwUYEgNJD3XBYH6IbCri_WkHSNhg3NkY,26122
40
40
  utilities/operator.py,sha256=nhxn5q6CFNzUm1wpTwWPCu9JGCqVHSlaJf0o1-efoII,3616
41
41
  utilities/optuna.py,sha256=C-fhWYiXHVPo1l8QctYkFJ4DyhbSrGorzP1dJb_qvd8,1933
42
- utilities/orjson.py,sha256=QKodAoG0tFEJ8l6TUVFq7ZB9PSlKtRek8H5fOnIgMBc,41541
42
+ utilities/orjson.py,sha256=pOCsldgiuxTFQJQAxh6vzEZnkDGuOCx_Nn1t5ziNFhY,41970
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
@@ -69,8 +69,8 @@ utilities/sqlalchemy_polars.py,sha256=JCGhB37raSR7fqeWV5dTsciRTMVzIdVT9YSqKT0piT
69
69
  utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
70
70
  utilities/string.py,sha256=shmBK87zZwzGyixuNuXCiUbqzfeZ9xlrFwz6JTaRvDk,582
71
71
  utilities/tempfile.py,sha256=HxB2BF28CcecDJLQ3Bx2Ej-Pb6RJc6W9ngSpB9CnP4k,2018
72
- utilities/testbook.py,sha256=iseyoMwMuV3h-9Xg_WCZ65o98dEbGT5pNtCe96eJHq4,1201
73
- utilities/text.py,sha256=uwCDgpEunYruyh6sKMfNWK3Rp5H3ndpKRAkq86CBNys,13043
72
+ utilities/testbook.py,sha256=j1KmaVbrX9VrbeMgtPh5gk55myAsn3dyRUn7jGbPbRk,1294
73
+ utilities/text.py,sha256=oMARu9HA3lY-NNRxPsz0Ld7L1ki7VKO_hmWYARYt0xY,13476
74
74
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
75
75
  utilities/timer.py,sha256=oXfTii6ymu57niP0BDGZjFD55LEHi2a19kqZKiTgaFQ,2588
76
76
  utilities/traceback.py,sha256=1k5JgumSMaqAGLd0dZ36CtPS0EGaglxTr29r2Dz4D60,9457
@@ -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.162.5.dist-info/METADATA,sha256=IVwIU9rQxFZIJfZvOPRwW2yV--KE-OM4Bkswz3lkYA0,1696
92
- dycw_utilities-0.162.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
- dycw_utilities-0.162.5.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
94
- dycw_utilities-0.162.5.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
95
- dycw_utilities-0.162.5.dist-info/RECORD,,
91
+ dycw_utilities-0.162.7.dist-info/METADATA,sha256=SbAYx4H3pjMZFKDEgFhfeTghXPl6wnH3GNC6ILRaIN4,1696
92
+ dycw_utilities-0.162.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
+ dycw_utilities-0.162.7.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
94
+ dycw_utilities-0.162.7.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
95
+ dycw_utilities-0.162.7.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.162.5"
3
+ __version__ = "0.162.7"
utilities/orjson.py CHANGED
@@ -20,6 +20,7 @@ from orjson import (
20
20
  OPT_PASSTHROUGH_DATACLASS,
21
21
  OPT_PASSTHROUGH_DATETIME,
22
22
  OPT_SORT_KEYS,
23
+ JSONDecodeError,
23
24
  dumps,
24
25
  loads,
25
26
  )
@@ -371,8 +372,12 @@ def deserialize(
371
372
  redirects: Mapping[str, type[Any]] | None = None,
372
373
  ) -> Any:
373
374
  """Deserialize an object."""
375
+ try:
376
+ obj = loads(data)
377
+ except JSONDecodeError:
378
+ raise _DeserializeInvalidJSONError(data=data) from None
374
379
  return _object_hook(
375
- loads(data),
380
+ obj,
376
381
  data=data,
377
382
  dataclass_hook=dataclass_hook,
378
383
  objects=objects,
@@ -380,6 +385,11 @@ def deserialize(
380
385
  )
381
386
 
382
387
 
388
+ @dataclass(kw_only=True, slots=True)
389
+ class DeerializeError(Exception):
390
+ obj: Any
391
+
392
+
383
393
  (
384
394
  _DATE_PATTERN,
385
395
  _DATE_DELTA_PATTERN,
@@ -739,11 +749,19 @@ def _object_hook_get_object(
739
749
  @dataclass(kw_only=True, slots=True)
740
750
  class DeserializeError(Exception):
741
751
  data: bytes
742
- qualname: str
752
+
753
+
754
+ @dataclass(kw_only=True, slots=True)
755
+ class _DeserializeInvalidJSONError(DeserializeError):
756
+ @override
757
+ def __str__(self) -> str:
758
+ return f"Invalid JSON: {self.data!r}"
743
759
 
744
760
 
745
761
  @dataclass(kw_only=True, slots=True)
746
762
  class _DeserializeNoObjectsError(DeserializeError):
763
+ qualname: str
764
+
747
765
  @override
748
766
  def __str__(self) -> str:
749
767
  return f"Objects required to deserialize {self.qualname!r} from {self.data!r}"
@@ -751,6 +769,8 @@ class _DeserializeNoObjectsError(DeserializeError):
751
769
 
752
770
  @dataclass(kw_only=True, slots=True)
753
771
  class _DeserializeObjectNotFoundError(DeserializeError):
772
+ qualname: str
773
+
754
774
  @override
755
775
  def __str__(self) -> str:
756
776
  return (
utilities/testbook.py CHANGED
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any
6
6
  from testbook import testbook
7
7
 
8
8
  from utilities.pytest import throttle
9
+ from utilities.text import pascal_case
9
10
 
10
11
  if TYPE_CHECKING:
11
12
  from collections.abc import Callable
@@ -15,16 +16,19 @@ if TYPE_CHECKING:
15
16
 
16
17
  def build_notebook_tester(
17
18
  path: PathLike, /, *, throttle: Delta | None = None, on_try: bool = False
18
- ) -> object:
19
- """Build the notebook test class."""
20
- name = f"Test{Path(path).stem}"
19
+ ) -> type[Any]:
20
+ """Build the notebook tester class."""
21
+ path = Path(path)
22
+ name = f"Test{pascal_case(path.stem)}"
21
23
  notebooks = [
22
24
  path_i
23
- for path_i in Path(path).rglob("**/*.ipynb")
25
+ for path_i in path.rglob("**/*.ipynb")
24
26
  if all(p != ".ipynb_checkpoints" for p in path_i.parts)
25
27
  ]
26
28
  namespace = {
27
- f"test_{p.stem}": _build_test_method(p, delta=throttle, on_try=on_try)
29
+ f"test_{p.stem.replace('-', '_')}": _build_test_method(
30
+ p, delta=throttle, on_try=on_try
31
+ )
28
32
  for p in notebooks
29
33
  }
30
34
  return type(name, (), namespace)
@@ -33,14 +37,14 @@ def build_notebook_tester(
33
37
  def _build_test_method(
34
38
  path: Path, /, *, delta: Delta | None = None, on_try: bool = False
35
39
  ) -> Callable[..., Any]:
40
+ @testbook(path, execute=True)
36
41
  def method(self: Any, tb: Any) -> None:
37
42
  _ = (self, tb) # pragma: no cover
38
43
 
39
44
  if delta is not None:
40
45
  method = throttle(delta=delta, on_try=on_try)(method)
41
46
 
42
- tbook = testbook(path, execute=True)
43
- return tbook(method)
47
+ return method
44
48
 
45
49
 
46
50
  __all__ = ["build_notebook_tester"]
utilities/text.py CHANGED
@@ -6,7 +6,7 @@ from collections.abc import Callable
6
6
  from dataclasses import dataclass
7
7
  from itertools import chain
8
8
  from os import getpid
9
- from re import IGNORECASE, Match, escape, search
9
+ from re import IGNORECASE, VERBOSE, escape, search
10
10
  from textwrap import dedent
11
11
  from threading import get_ident
12
12
  from time import time_ns
@@ -77,6 +77,21 @@ class ParseNoneError(Exception):
77
77
  ##
78
78
 
79
79
 
80
+ def pascal_case(text: str, /) -> str:
81
+ """Convert text to pascal case."""
82
+ parts = _SPLIT_TEXT.findall(text)
83
+ parts = [p for p in parts if len(p) >= 1]
84
+ parts = list(map(_pascal_case_one, parts))
85
+ return "".join(parts)
86
+
87
+
88
+ def _pascal_case_one(text: str, /) -> str:
89
+ return text if text.isupper() else text.title()
90
+
91
+
92
+ ##
93
+
94
+
80
95
  def repr_encode(obj: Any, /) -> bytes:
81
96
  """Return the representation of the object encoded as bytes."""
82
97
  return repr(obj).encode()
@@ -85,25 +100,24 @@ def repr_encode(obj: Any, /) -> bytes:
85
100
  ##
86
101
 
87
102
 
88
- _ACRONYM_PATTERN = re.compile(r"([A-Z\d]+)(?=[A-Z\d]|$)")
89
- _SPACES_PATTERN = re.compile(r"\s+")
90
- _SPLIT_PATTERN = re.compile(r"([\-_]*[A-Z][^A-Z]*[\-_]*)")
91
-
92
-
93
103
  def snake_case(text: str, /) -> str:
94
104
  """Convert text into snake case."""
95
- text = _SPACES_PATTERN.sub("", text)
96
- if not text.isupper():
97
- text = _ACRONYM_PATTERN.sub(_snake_case_title, text)
98
- text = "_".join(s for s in _SPLIT_PATTERN.split(text) if s)
99
- while search("__", text):
100
- text = text.replace("__", "_")
101
- return text.lower()
102
-
103
-
104
- def _snake_case_title(match: Match[str], /) -> str:
105
- return match.group(0).title()
106
-
105
+ leading = bool(search(r"^_", text))
106
+ trailing = bool(search(r"_$", text))
107
+ parts = _SPLIT_TEXT.findall(text)
108
+ parts = (p for p in parts if len(p) >= 1)
109
+ parts = chain([""] if leading else [], parts, [""] if trailing else [])
110
+ return "_".join(parts).lower()
111
+
112
+
113
+ _SPLIT_TEXT = re.compile(
114
+ r"""
115
+ [A-Z]+(?=[A-Z][a-z0-9]) | # all caps followed by Upper+lower or digit (API in APIResponse2)
116
+ [A-Z]?[a-z]+[0-9]* | # normal words with optional trailing digits (Text123)
117
+ [A-Z]+[0-9]* | # consecutive caps with optional trailing digits (ID2)
118
+ """,
119
+ flags=VERBOSE,
120
+ )
107
121
 
108
122
  ##
109
123
 
@@ -503,6 +517,7 @@ __all__ = [
503
517
  "join_strs",
504
518
  "parse_bool",
505
519
  "parse_none",
520
+ "pascal_case",
506
521
  "repr_encode",
507
522
  "secret_str",
508
523
  "snake_case",