dycw-utilities 0.162.4__py3-none-any.whl → 0.162.6__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,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.162.4
3
+ Version: 0.162.6
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
7
7
  Requires-Dist: atomicwrites<1.5,>=1.4.1
8
8
  Requires-Dist: typing-extensions<4.15,>=4.14.0
9
9
  Requires-Dist: tzlocal<5.4,>=5.3.1
10
- Requires-Dist: whenever<0.9,>=0.8.7
10
+ Requires-Dist: whenever<0.9,>=0.8.8
11
11
  Provides-Extra: logging
12
12
  Requires-Dist: coloredlogs<15.1,>=15.0.1; extra == 'logging'
13
13
  Provides-Extra: test
@@ -25,6 +25,7 @@ Requires-Dist: pytest-rng<1.1,>=1.0.0; extra == 'test'
25
25
  Requires-Dist: pytest-timeout<2.5,>=2.4.0; extra == 'test'
26
26
  Requires-Dist: pytest-xdist<3.9,>=3.8.0; extra == 'test'
27
27
  Requires-Dist: pytest<8.5,>=8.4.1; extra == 'test'
28
+ Requires-Dist: testbook<0.5,>=0.4.2; extra == 'test'
28
29
  Description-Content-Type: text/markdown
29
30
 
30
31
  [![PyPI version](https://badge.fury.io/py/dycw-utilities.svg)](https://badge.fury.io/py/dycw-utilities)
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=o6nUgPOHpIQS5SGb2XacqWt_oRNjojYulv7Rb46uZ2o,60
1
+ utilities/__init__.py,sha256=gjPlIBqtxGUVmb6pZvmbsEfu_kDvff1JWcZUUza19AQ,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
@@ -22,7 +22,7 @@ utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
22
22
  utilities/gzip.py,sha256=fkGP3KdsBfXlstodT4wtlp-PwNyUsogpbDCVVVGdsm4,781
23
23
  utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
24
24
  utilities/http.py,sha256=TsavEfHlRtlLaeV21Z6KZh0qbPw-kvD1zsQdZ7Kep5Q,977
25
- utilities/hypothesis.py,sha256=1cy8YDOhoMzhwZ1a3mq25oeYsJQmryR_ZULJBMvE6K8,44738
25
+ utilities/hypothesis.py,sha256=1_rYNFUAkqRhZUrMM4h_eoRv1_ZCtyD6R87SeJbZpOQ,44758
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
@@ -69,7 +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/text.py,sha256=uwCDgpEunYruyh6sKMfNWK3Rp5H3ndpKRAkq86CBNys,13043
72
+ utilities/testbook.py,sha256=j1KmaVbrX9VrbeMgtPh5gk55myAsn3dyRUn7jGbPbRk,1294
73
+ utilities/text.py,sha256=oMARu9HA3lY-NNRxPsz0Ld7L1ki7VKO_hmWYARYt0xY,13476
73
74
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
74
75
  utilities/timer.py,sha256=oXfTii6ymu57niP0BDGZjFD55LEHi2a19kqZKiTgaFQ,2588
75
76
  utilities/traceback.py,sha256=1k5JgumSMaqAGLd0dZ36CtPS0EGaglxTr29r2Dz4D60,9457
@@ -87,8 +88,8 @@ utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
87
88
  utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
88
89
  utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
89
90
  utilities/pytest_plugins/pytest_regressions.py,sha256=9v8kAXDM2ycIXJBimoiF4EgrwbUvxTycFWJiGR_GHhM,1466
90
- dycw_utilities-0.162.4.dist-info/METADATA,sha256=IKECWa-zL_Rt2lFuNserGqVQ2QgXJh83tkoYOz5MEDM,1643
91
- dycw_utilities-0.162.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
- dycw_utilities-0.162.4.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
93
- dycw_utilities-0.162.4.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
- dycw_utilities-0.162.4.dist-info/RECORD,,
91
+ dycw_utilities-0.162.6.dist-info/METADATA,sha256=zXFVqk-andgkMkvkEddzxk1O5hfu6j0oe-b5iEeRVAE,1696
92
+ dycw_utilities-0.162.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
+ dycw_utilities-0.162.6.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
94
+ dycw_utilities-0.162.6.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
95
+ dycw_utilities-0.162.6.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.162.4"
3
+ __version__ = "0.162.6"
utilities/hypothesis.py CHANGED
@@ -1483,7 +1483,7 @@ def year_months(
1483
1483
  def zone_infos(draw: DrawFn, /) -> ZoneInfo:
1484
1484
  """Strategy for generating time-zones."""
1485
1485
  time_zone = draw(timezones())
1486
- if IS_LINUX:
1486
+ if IS_LINUX: # skipif-not-linux
1487
1487
  _ = assume(time_zone.key not in {"Etc/UTC", "localtime"})
1488
1488
  with assume_does_not_raise(TimeZoneNotFoundError):
1489
1489
  _ = get_now(time_zone)
utilities/testbook.py ADDED
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from testbook import testbook
7
+
8
+ from utilities.pytest import throttle
9
+ from utilities.text import pascal_case
10
+
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Callable
13
+
14
+ from utilities.types import Delta, PathLike
15
+
16
+
17
+ def build_notebook_tester(
18
+ path: PathLike, /, *, throttle: Delta | None = None, on_try: bool = False
19
+ ) -> type[Any]:
20
+ """Build the notebook tester class."""
21
+ path = Path(path)
22
+ name = f"Test{pascal_case(path.stem)}"
23
+ notebooks = [
24
+ path_i
25
+ for path_i in path.rglob("**/*.ipynb")
26
+ if all(p != ".ipynb_checkpoints" for p in path_i.parts)
27
+ ]
28
+ namespace = {
29
+ f"test_{p.stem.replace('-', '_')}": _build_test_method(
30
+ p, delta=throttle, on_try=on_try
31
+ )
32
+ for p in notebooks
33
+ }
34
+ return type(name, (), namespace)
35
+
36
+
37
+ def _build_test_method(
38
+ path: Path, /, *, delta: Delta | None = None, on_try: bool = False
39
+ ) -> Callable[..., Any]:
40
+ @testbook(path, execute=True)
41
+ def method(self: Any, tb: Any) -> None:
42
+ _ = (self, tb) # pragma: no cover
43
+
44
+ if delta is not None:
45
+ method = throttle(delta=delta, on_try=on_try)(method)
46
+
47
+ return method
48
+
49
+
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",