dycw-utilities 0.166.34__py3-none-any.whl → 0.166.36__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.

Potentially problematic release.


This version of dycw-utilities might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.166.34
3
+ Version: 0.166.36
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=mR10rgX7UKklBglgCOpVCV3EzZi3zOY6LmfPRqAYtVg,61
1
+ utilities/__init__.py,sha256=VioZbifSCOm0EkGF27DgQtAvEThNI6_JUJQ3jWhqrYI,61
2
2
  utilities/aeventkit.py,sha256=ddoleSwW9zdc2tjX5Ge0pMKtYwV_JMxhHYOxnWX2AGM,12609
3
3
  utilities/altair.py,sha256=nHdpWt8ZwdUwRQN970MvHd5bRWokNqzHcZQEdSHKRuE,9033
4
4
  utilities/asyncio.py,sha256=60l1IwjnRGeaVphAFiwDIHyfKoZYKY-XGpptUxGiU-M,17034
@@ -28,7 +28,7 @@ utilities/importlib.py,sha256=mV1xT_O_zt_GnZZ36tl3xOmMaN_3jErDWY54fX39F6Y,429
28
28
  utilities/inflect.py,sha256=v7YkOWSu8NAmVghPcf4F3YBZQoJCS47_DLf9jbfWIs0,581
29
29
  utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
30
30
  utilities/iterables.py,sha256=t2TsW-K3rVlS6y4_tqcc1fk9RwJV-bi7G_VwduMABK0,42558
31
- utilities/jinja2.py,sha256=Pbq29FLEaTkPxMICcRMYsHj94m5geBfJlP62hghCYKM,2704
31
+ utilities/jinja2.py,sha256=jk9GI03mfl0lbu3NFJPRbiZ5xTExGd9lIX-i5SZXiMU,3859
32
32
  utilities/json.py,sha256=-WcGtSsCr9Y42wHZzAMnfvU6ihAfVftylFfRUORaDFo,2102
33
33
  utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
34
34
  utilities/libcst.py,sha256=ngD4wxnR3Kh-RBVmU5l5ST7cuZLhMZwyMDjHZe5mhTs,5581
@@ -74,7 +74,7 @@ utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
74
74
  utilities/string.py,sha256=shmBK87zZwzGyixuNuXCiUbqzfeZ9xlrFwz6JTaRvDk,582
75
75
  utilities/tempfile.py,sha256=HxB2BF28CcecDJLQ3Bx2Ej-Pb6RJc6W9ngSpB9CnP4k,2018
76
76
  utilities/testbook.py,sha256=j1KmaVbrX9VrbeMgtPh5gk55myAsn3dyRUn7jGbPbRk,1294
77
- utilities/text.py,sha256=GkjXhJk1UbKEoh2VnMR6UfmunuCwCL4pa-2AmgKE2pU,13666
77
+ utilities/text.py,sha256=7SvwcSR2l_5cOrm1samGnR4C-ZI6qyFLHLzSpO1zeHQ,13958
78
78
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
79
79
  utilities/timer.py,sha256=oXfTii6ymu57niP0BDGZjFD55LEHi2a19kqZKiTgaFQ,2588
80
80
  utilities/traceback.py,sha256=-tgTUnryG7Bu2tOXjURIIpC4ohIVBzVAxIj6Kf3xxCM,9615
@@ -92,8 +92,8 @@ utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
92
92
  utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
93
93
  utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
94
94
  utilities/pytest_plugins/pytest_regressions.py,sha256=mnHYBfdprz50UGVkVzV1bZERZN5CFfoF8YbokGxdFwU,1639
95
- dycw_utilities-0.166.34.dist-info/METADATA,sha256=iO6583nzzeVrxxrnuotJiivMwbDLy4DNChzE1Mov6xs,1699
96
- dycw_utilities-0.166.34.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
97
- dycw_utilities-0.166.34.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
98
- dycw_utilities-0.166.34.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
99
- dycw_utilities-0.166.34.dist-info/RECORD,,
95
+ dycw_utilities-0.166.36.dist-info/METADATA,sha256=Qu3EXmhbVxbw3w5OODwArwNZdxgpLDMEDYf4-VJ24Nw,1699
96
+ dycw_utilities-0.166.36.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
97
+ dycw_utilities-0.166.36.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
98
+ dycw_utilities-0.166.36.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
99
+ dycw_utilities-0.166.36.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.166.34"
3
+ __version__ = "0.166.36"
utilities/jinja2.py CHANGED
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any, Literal, override
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Any, Literal, assert_never, override
4
5
 
5
- from jinja2 import BaseLoader, BytecodeCache, Environment, Undefined
6
+ from jinja2 import BaseLoader, BytecodeCache, Environment, FileSystemLoader, Undefined
6
7
  from jinja2.defaults import (
7
8
  BLOCK_END_STRING,
8
9
  BLOCK_START_STRING,
@@ -18,13 +19,17 @@ from jinja2.defaults import (
18
19
  VARIABLE_START_STRING,
19
20
  )
20
21
 
21
- from utilities.text import pascal_case, snake_case
22
+ from utilities.atomicwrites import writer
23
+ from utilities.text import kebab_case, pascal_case, snake_case
22
24
 
23
25
  if TYPE_CHECKING:
24
26
  from collections.abc import Callable, Sequence
27
+ from pathlib import Path
25
28
 
26
29
  from jinja2.ext import Extension
27
30
 
31
+ from utilities.types import StrMapping
32
+
28
33
 
29
34
  class EnhancedEnvironment(Environment):
30
35
  """Environment with enhanced features."""
@@ -79,8 +84,37 @@ class EnhancedEnvironment(Environment):
79
84
  bytecode_cache,
80
85
  enable_async,
81
86
  )
82
- self.filters["snake"] = snake_case
87
+ self.filters["kebab"] = kebab_case
83
88
  self.filters["pascal"] = pascal_case
89
+ self.filters["snake"] = snake_case
90
+
91
+
92
+ @dataclass(order=True, unsafe_hash=True, kw_only=True, slots=True)
93
+ class TemplateJob:
94
+ """A template with an associated rendering job."""
95
+
96
+ template: Path
97
+ kwargs: StrMapping
98
+ target: Path
99
+ mode: Literal["write", "append"] = "write"
100
+
101
+ def run(self) -> None:
102
+ """Run the job."""
103
+ match self.mode:
104
+ case "write":
105
+ with writer(self.target, overwrite=True) as temp:
106
+ _ = temp.write_text(self.rendered)
107
+ case "append":
108
+ with self.target.open(mode="a") as fh:
109
+ _ = fh.write(self.rendered)
110
+ case never:
111
+ assert_never(never)
112
+
113
+ @property
114
+ def rendered(self) -> str:
115
+ """The template, rendered."""
116
+ env = EnhancedEnvironment(loader=FileSystemLoader(self.template.parent))
117
+ return env.get_template(self.template.name).render(self.kwargs)
84
118
 
85
119
 
86
- __all__ = ["EnhancedEnvironment"]
120
+ __all__ = ["EnhancedEnvironment", "TemplateJob"]
utilities/text.py CHANGED
@@ -31,7 +31,15 @@ if TYPE_CHECKING:
31
31
  from utilities.types import MaybeCallableBoolLike, MaybeCallableStr, StrStrMapping
32
32
 
33
33
 
34
- DEFAULT_SEPARATOR = ","
34
+ _DEFAULT_SEPARATOR = ","
35
+
36
+
37
+ ##
38
+
39
+
40
+ def kebab_case(text: str, /) -> str:
41
+ """Convert text into kebab case."""
42
+ return _kebab_snake_case(text, "-")
35
43
 
36
44
 
37
45
  ##
@@ -110,27 +118,13 @@ def repr_encode(obj: Any, /) -> bytes:
110
118
 
111
119
  def snake_case(text: str, /) -> str:
112
120
  """Convert text into snake case."""
113
- leading = bool(search(r"^_", text))
114
- trailing = bool(search(r"_$", text))
115
- parts = _SPLIT_TEXT.findall(text)
116
- parts = (p for p in parts if len(p) >= 1)
117
- parts = chain([""] if leading else [], parts, [""] if trailing else [])
118
- return "_".join(parts).lower()
121
+ return _kebab_snake_case(text, "_")
119
122
 
120
123
 
121
- _SPLIT_TEXT = re.compile(
122
- r"""
123
- [A-Z]+(?=[A-Z][a-z0-9]) | # all caps followed by Upper+lower or digit (API in APIResponse2)
124
- [A-Z]?[a-z]+[0-9]* | # normal words with optional trailing digits (Text123)
125
- [A-Z]+[0-9]* | # consecutive caps with optional trailing digits (ID2)
126
- """,
127
- flags=VERBOSE,
128
- )
129
-
130
124
  ##
131
125
 
132
126
 
133
- LIST_SEPARATOR = DEFAULT_SEPARATOR
127
+ LIST_SEPARATOR = _DEFAULT_SEPARATOR
134
128
  PAIR_SEPARATOR = "="
135
129
  BRACKETS = [("(", ")"), ("[", "]"), ("{", "}")]
136
130
 
@@ -140,7 +134,7 @@ def split_key_value_pairs(
140
134
  text: str,
141
135
  /,
142
136
  *,
143
- list_separator: str = DEFAULT_SEPARATOR,
137
+ list_separator: str = _DEFAULT_SEPARATOR,
144
138
  pair_separator: str = PAIR_SEPARATOR,
145
139
  brackets: Iterable[tuple[str, str]] | None = BRACKETS,
146
140
  mapping: Literal[True],
@@ -150,7 +144,7 @@ def split_key_value_pairs(
150
144
  text: str,
151
145
  /,
152
146
  *,
153
- list_separator: str = DEFAULT_SEPARATOR,
147
+ list_separator: str = _DEFAULT_SEPARATOR,
154
148
  pair_separator: str = PAIR_SEPARATOR,
155
149
  brackets: Iterable[tuple[str, str]] | None = BRACKETS,
156
150
  mapping: Literal[False] = False,
@@ -160,7 +154,7 @@ def split_key_value_pairs(
160
154
  text: str,
161
155
  /,
162
156
  *,
163
- list_separator: str = DEFAULT_SEPARATOR,
157
+ list_separator: str = _DEFAULT_SEPARATOR,
164
158
  pair_separator: str = PAIR_SEPARATOR,
165
159
  brackets: Iterable[tuple[str, str]] | None = BRACKETS,
166
160
  mapping: bool = False,
@@ -169,7 +163,7 @@ def split_key_value_pairs(
169
163
  text: str,
170
164
  /,
171
165
  *,
172
- list_separator: str = DEFAULT_SEPARATOR,
166
+ list_separator: str = _DEFAULT_SEPARATOR,
173
167
  pair_separator: str = PAIR_SEPARATOR,
174
168
  brackets: Iterable[tuple[str, str]] | None = BRACKETS,
175
169
  mapping: bool = False,
@@ -228,7 +222,7 @@ def split_str(
228
222
  text: str,
229
223
  /,
230
224
  *,
231
- separator: str = DEFAULT_SEPARATOR,
225
+ separator: str = _DEFAULT_SEPARATOR,
232
226
  brackets: Iterable[tuple[str, str]] | None = None,
233
227
  n: Literal[1],
234
228
  ) -> tuple[str]: ...
@@ -237,7 +231,7 @@ def split_str(
237
231
  text: str,
238
232
  /,
239
233
  *,
240
- separator: str = DEFAULT_SEPARATOR,
234
+ separator: str = _DEFAULT_SEPARATOR,
241
235
  brackets: Iterable[tuple[str, str]] | None = None,
242
236
  n: Literal[2],
243
237
  ) -> tuple[str, str]: ...
@@ -246,7 +240,7 @@ def split_str(
246
240
  text: str,
247
241
  /,
248
242
  *,
249
- separator: str = DEFAULT_SEPARATOR,
243
+ separator: str = _DEFAULT_SEPARATOR,
250
244
  brackets: Iterable[tuple[str, str]] | None = None,
251
245
  n: Literal[3],
252
246
  ) -> tuple[str, str, str]: ...
@@ -255,7 +249,7 @@ def split_str(
255
249
  text: str,
256
250
  /,
257
251
  *,
258
- separator: str = DEFAULT_SEPARATOR,
252
+ separator: str = _DEFAULT_SEPARATOR,
259
253
  brackets: Iterable[tuple[str, str]] | None = None,
260
254
  n: Literal[4],
261
255
  ) -> tuple[str, str, str, str]: ...
@@ -264,7 +258,7 @@ def split_str(
264
258
  text: str,
265
259
  /,
266
260
  *,
267
- separator: str = DEFAULT_SEPARATOR,
261
+ separator: str = _DEFAULT_SEPARATOR,
268
262
  brackets: Iterable[tuple[str, str]] | None = None,
269
263
  n: Literal[5],
270
264
  ) -> tuple[str, str, str, str, str]: ...
@@ -273,7 +267,7 @@ def split_str(
273
267
  text: str,
274
268
  /,
275
269
  *,
276
- separator: str = DEFAULT_SEPARATOR,
270
+ separator: str = _DEFAULT_SEPARATOR,
277
271
  brackets: Iterable[tuple[str, str]] | None = None,
278
272
  n: int | None = None,
279
273
  ) -> tuple[str, ...]: ...
@@ -281,7 +275,7 @@ def split_str(
281
275
  text: str,
282
276
  /,
283
277
  *,
284
- separator: str = DEFAULT_SEPARATOR,
278
+ separator: str = _DEFAULT_SEPARATOR,
285
279
  brackets: Iterable[tuple[str, str]] | None = None,
286
280
  n: int | None = None,
287
281
  ) -> tuple[str, ...]:
@@ -306,7 +300,7 @@ def _split_str_brackets(
306
300
  brackets: Iterable[tuple[str, str]],
307
301
  /,
308
302
  *,
309
- separator: str = DEFAULT_SEPARATOR,
303
+ separator: str = _DEFAULT_SEPARATOR,
310
304
  ) -> list[str]:
311
305
  brackets = list(brackets)
312
306
  opens, closes = transpose(brackets)
@@ -397,7 +391,7 @@ class _SplitStrOpeningBracketUnmatchedError(SplitStrError):
397
391
 
398
392
 
399
393
  def join_strs(
400
- texts: Iterable[str], /, *, sort: bool = False, separator: str = DEFAULT_SEPARATOR
394
+ texts: Iterable[str], /, *, sort: bool = False, separator: str = _DEFAULT_SEPARATOR
401
395
  ) -> str:
402
396
  """Join a collection of strings, with a special provision for the empty list."""
403
397
  texts = list(texts)
@@ -410,7 +404,7 @@ def join_strs(
410
404
  return separator.join(texts)
411
405
 
412
406
 
413
- def _escape_separator(*, separator: str = DEFAULT_SEPARATOR) -> str:
407
+ def _escape_separator(*, separator: str = _DEFAULT_SEPARATOR) -> str:
414
408
  return f"\\{separator}"
415
409
 
416
410
 
@@ -513,9 +507,30 @@ def unique_str() -> str:
513
507
  return f"{now}_{pid}_{ident}_{key}"
514
508
 
515
509
 
510
+ ##
511
+
512
+
513
+ def _kebab_snake_case(text: str, separator: str, /) -> str:
514
+ """Convert text into kebab/snake case."""
515
+ leading = bool(search(r"^_", text))
516
+ trailing = bool(search(r"_$", text))
517
+ parts = _SPLIT_TEXT.findall(text)
518
+ parts = (p for p in parts if len(p) >= 1)
519
+ parts = chain([""] if leading else [], parts, [""] if trailing else [])
520
+ return separator.join(parts).lower()
521
+
522
+
523
+ _SPLIT_TEXT = re.compile(
524
+ r"""
525
+ [A-Z]+(?=[A-Z][a-z0-9]) | # all caps followed by Upper+lower or digit (API in APIResponse2)
526
+ [A-Z]?[a-z]+[0-9]* | # normal words with optional trailing digits (Text123)
527
+ [A-Z]+[0-9]* | # consecutive caps with optional trailing digits (ID2)
528
+ """,
529
+ flags=VERBOSE,
530
+ )
531
+
516
532
  __all__ = [
517
533
  "BRACKETS",
518
- "DEFAULT_SEPARATOR",
519
534
  "LIST_SEPARATOR",
520
535
  "PAIR_SEPARATOR",
521
536
  "ParseBoolError",
@@ -523,6 +538,7 @@ __all__ = [
523
538
  "SplitKeyValuePairsError",
524
539
  "SplitStrError",
525
540
  "join_strs",
541
+ "kebab_case",
526
542
  "parse_bool",
527
543
  "parse_none",
528
544
  "pascal_case",