dycw-utilities 0.148.5__py3-none-any.whl → 0.175.31__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.
- dycw_utilities-0.175.31.dist-info/METADATA +34 -0
- dycw_utilities-0.175.31.dist-info/RECORD +103 -0
- dycw_utilities-0.175.31.dist-info/WHEEL +4 -0
- {dycw_utilities-0.148.5.dist-info → dycw_utilities-0.175.31.dist-info}/entry_points.txt +1 -0
- utilities/__init__.py +1 -1
- utilities/altair.py +10 -7
- utilities/asyncio.py +113 -64
- utilities/atomicwrites.py +1 -1
- utilities/atools.py +64 -4
- utilities/cachetools.py +9 -6
- utilities/click.py +144 -49
- utilities/concurrent.py +1 -1
- utilities/contextlib.py +4 -2
- utilities/contextvars.py +20 -1
- utilities/cryptography.py +3 -3
- utilities/dataclasses.py +15 -28
- utilities/docker.py +381 -0
- utilities/enum.py +2 -2
- utilities/errors.py +1 -1
- utilities/fastapi.py +8 -3
- utilities/fpdf2.py +2 -2
- utilities/functions.py +20 -297
- utilities/git.py +19 -0
- utilities/grp.py +28 -0
- utilities/hypothesis.py +361 -79
- utilities/importlib.py +17 -1
- utilities/inflect.py +1 -1
- utilities/iterables.py +12 -58
- utilities/jinja2.py +148 -0
- utilities/json.py +1 -1
- utilities/libcst.py +7 -7
- utilities/logging.py +74 -85
- utilities/math.py +8 -4
- utilities/more_itertools.py +4 -6
- utilities/operator.py +1 -1
- utilities/orjson.py +86 -34
- utilities/os.py +49 -2
- utilities/parse.py +2 -2
- utilities/pathlib.py +66 -34
- utilities/permissions.py +298 -0
- utilities/platform.py +4 -4
- utilities/polars.py +934 -420
- utilities/polars_ols.py +1 -1
- utilities/postgres.py +296 -174
- utilities/pottery.py +8 -73
- utilities/pqdm.py +3 -3
- utilities/pwd.py +28 -0
- utilities/pydantic.py +11 -0
- utilities/pydantic_settings.py +240 -0
- utilities/pydantic_settings_sops.py +76 -0
- utilities/pyinstrument.py +5 -5
- utilities/pytest.py +155 -46
- utilities/pytest_plugins/pytest_randomly.py +1 -1
- utilities/pytest_plugins/pytest_regressions.py +7 -3
- utilities/pytest_regressions.py +27 -8
- utilities/random.py +11 -6
- utilities/re.py +1 -1
- utilities/redis.py +101 -64
- utilities/sentinel.py +10 -0
- utilities/shelve.py +4 -1
- utilities/shutil.py +25 -0
- utilities/slack_sdk.py +8 -3
- utilities/sqlalchemy.py +422 -352
- utilities/sqlalchemy_polars.py +28 -52
- utilities/string.py +1 -1
- utilities/subprocess.py +1947 -0
- utilities/tempfile.py +95 -4
- utilities/testbook.py +50 -0
- utilities/text.py +165 -42
- utilities/timer.py +2 -2
- utilities/traceback.py +46 -36
- utilities/types.py +62 -23
- utilities/typing.py +479 -19
- utilities/uuid.py +42 -5
- utilities/version.py +27 -26
- utilities/whenever.py +661 -151
- utilities/zoneinfo.py +80 -22
- dycw_utilities-0.148.5.dist-info/METADATA +0 -41
- dycw_utilities-0.148.5.dist-info/RECORD +0 -95
- dycw_utilities-0.148.5.dist-info/WHEEL +0 -4
- dycw_utilities-0.148.5.dist-info/licenses/LICENSE +0 -21
- utilities/eventkit.py +0 -388
- utilities/period.py +0 -237
- utilities/typed_settings.py +0 -144
utilities/click.py
CHANGED
|
@@ -3,21 +3,22 @@ from __future__ import annotations
|
|
|
3
3
|
import enum
|
|
4
4
|
import ipaddress
|
|
5
5
|
import pathlib
|
|
6
|
+
import uuid
|
|
7
|
+
from enum import StrEnum
|
|
6
8
|
from typing import TYPE_CHECKING, TypedDict, assert_never, override
|
|
7
9
|
|
|
8
|
-
import click
|
|
9
10
|
import whenever
|
|
10
11
|
from click import Choice, Context, Parameter, ParamType
|
|
11
12
|
from click.types import IntParamType, StringParamType
|
|
12
13
|
|
|
13
14
|
from utilities.enum import EnsureEnumError, ensure_enum
|
|
14
|
-
from utilities.functions import EnsureStrError, ensure_str, get_class_name
|
|
15
|
-
from utilities.iterables import is_iterable_not_str
|
|
15
|
+
from utilities.functions import EnsureStrError, ensure_str, get_class, get_class_name
|
|
16
|
+
from utilities.iterables import is_iterable_not_str, one_unique
|
|
16
17
|
from utilities.parse import ParseObjectError, parse_object
|
|
17
18
|
from utilities.text import split_str
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
20
|
-
from collections.abc import Iterable
|
|
21
|
+
from collections.abc import Iterable
|
|
21
22
|
|
|
22
23
|
from utilities.types import (
|
|
23
24
|
DateDeltaLike,
|
|
@@ -28,6 +29,7 @@ if TYPE_CHECKING:
|
|
|
28
29
|
IPv6AddressLike,
|
|
29
30
|
MaybeStr,
|
|
30
31
|
MonthDayLike,
|
|
32
|
+
PathLike,
|
|
31
33
|
PlainDateTimeLike,
|
|
32
34
|
TimeDeltaLike,
|
|
33
35
|
TimeLike,
|
|
@@ -36,27 +38,23 @@ if TYPE_CHECKING:
|
|
|
36
38
|
)
|
|
37
39
|
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
ExistingFilePath = click.Path(
|
|
42
|
-
exists=True, file_okay=True, dir_okay=False, path_type=pathlib.Path
|
|
43
|
-
)
|
|
44
|
-
ExistingDirPath = click.Path(
|
|
45
|
-
exists=True, file_okay=False, dir_okay=True, path_type=pathlib.Path
|
|
46
|
-
)
|
|
47
|
-
|
|
41
|
+
class _ContextSettings(TypedDict):
|
|
42
|
+
context_settings: _ContextSettingsInner
|
|
48
43
|
|
|
49
|
-
class _HelpOptionNames(TypedDict):
|
|
50
|
-
help_option_names: Sequence[str]
|
|
51
44
|
|
|
45
|
+
class _ContextSettingsInner(TypedDict):
|
|
46
|
+
max_content_width: int
|
|
47
|
+
help_option_names: list[str]
|
|
48
|
+
show_default: bool
|
|
52
49
|
|
|
53
|
-
class _ContextSettings(TypedDict):
|
|
54
|
-
context_settings: _HelpOptionNames
|
|
55
50
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
_MAX_CONTENT_WIDTH = 120
|
|
52
|
+
_CONTEXT_SETTINGS_INNER = _ContextSettingsInner(
|
|
53
|
+
max_content_width=_MAX_CONTENT_WIDTH,
|
|
54
|
+
help_option_names=["-h", "--help"],
|
|
55
|
+
show_default=True,
|
|
59
56
|
)
|
|
57
|
+
CONTEXT_SETTINGS = _ContextSettings(context_settings=_CONTEXT_SETTINGS_INNER)
|
|
60
58
|
|
|
61
59
|
|
|
62
60
|
# parameters
|
|
@@ -81,10 +79,10 @@ class Date(ParamType):
|
|
|
81
79
|
return value
|
|
82
80
|
case str():
|
|
83
81
|
try:
|
|
84
|
-
return whenever.Date.
|
|
82
|
+
return whenever.Date.parse_iso(value)
|
|
85
83
|
except ValueError as error:
|
|
86
84
|
self.fail(str(error), param, ctx)
|
|
87
|
-
case
|
|
85
|
+
case never:
|
|
88
86
|
assert_never(never)
|
|
89
87
|
|
|
90
88
|
|
|
@@ -107,10 +105,10 @@ class DateDelta(ParamType):
|
|
|
107
105
|
return value
|
|
108
106
|
case str():
|
|
109
107
|
try:
|
|
110
|
-
return whenever.DateDelta.
|
|
108
|
+
return whenever.DateDelta.parse_iso(value)
|
|
111
109
|
except ValueError as error:
|
|
112
110
|
self.fail(str(error), param, ctx)
|
|
113
|
-
case
|
|
111
|
+
case never:
|
|
114
112
|
assert_never(never)
|
|
115
113
|
|
|
116
114
|
|
|
@@ -133,10 +131,10 @@ class DateTimeDelta(ParamType):
|
|
|
133
131
|
return value
|
|
134
132
|
case str():
|
|
135
133
|
try:
|
|
136
|
-
return whenever.DateTimeDelta.
|
|
134
|
+
return whenever.DateTimeDelta.parse_iso(value)
|
|
137
135
|
except ValueError as error:
|
|
138
136
|
self.fail(str(error), param, ctx)
|
|
139
|
-
case
|
|
137
|
+
case never:
|
|
140
138
|
assert_never(never)
|
|
141
139
|
|
|
142
140
|
|
|
@@ -144,10 +142,13 @@ class Enum[E: enum.Enum](ParamType):
|
|
|
144
142
|
"""An enum-valued parameter."""
|
|
145
143
|
|
|
146
144
|
@override
|
|
147
|
-
def __init__(
|
|
145
|
+
def __init__(
|
|
146
|
+
self, enum: type[E], /, *, value: bool = False, case_sensitive: bool = False
|
|
147
|
+
) -> None:
|
|
148
148
|
cls = get_class_name(enum)
|
|
149
149
|
self.name = f"enum[{cls}]"
|
|
150
150
|
self._enum = enum
|
|
151
|
+
self._value = issubclass(self._enum, StrEnum) or value
|
|
151
152
|
self._case_sensitive = case_sensitive
|
|
152
153
|
super().__init__()
|
|
153
154
|
|
|
@@ -169,7 +170,52 @@ class Enum[E: enum.Enum](ParamType):
|
|
|
169
170
|
@override
|
|
170
171
|
def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
|
|
171
172
|
_ = ctx
|
|
172
|
-
desc = ",".join(e.name for e in self._enum)
|
|
173
|
+
desc = ",".join(str(e.value) if self._value else e.name for e in self._enum)
|
|
174
|
+
return _make_metavar(param, desc)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class EnumPartial[E: enum.Enum](ParamType):
|
|
178
|
+
"""An enum-valued parameter."""
|
|
179
|
+
|
|
180
|
+
@override
|
|
181
|
+
def __init__(
|
|
182
|
+
self,
|
|
183
|
+
members: Iterable[E],
|
|
184
|
+
/,
|
|
185
|
+
*,
|
|
186
|
+
value: bool = False,
|
|
187
|
+
case_sensitive: bool = False,
|
|
188
|
+
) -> None:
|
|
189
|
+
self._members = list(members)
|
|
190
|
+
self._enum = one_unique(get_class(e) for e in self._members)
|
|
191
|
+
cls = get_class_name(self._enum)
|
|
192
|
+
self.name = f"enum-partial[{cls}]"
|
|
193
|
+
self._value = issubclass(self._enum, StrEnum) or value
|
|
194
|
+
self._case_sensitive = case_sensitive
|
|
195
|
+
super().__init__()
|
|
196
|
+
|
|
197
|
+
@override
|
|
198
|
+
def __repr__(self) -> str:
|
|
199
|
+
cls = get_class_name(self._enum)
|
|
200
|
+
return f"ENUMPARTIAL[{cls}]"
|
|
201
|
+
|
|
202
|
+
@override
|
|
203
|
+
def convert(
|
|
204
|
+
self, value: EnumLike[E], param: Parameter | None, ctx: Context | None
|
|
205
|
+
) -> E:
|
|
206
|
+
"""Convert a value into the `Enum` type."""
|
|
207
|
+
try:
|
|
208
|
+
enum = ensure_enum(value, self._enum, case_sensitive=self._case_sensitive)
|
|
209
|
+
except EnsureEnumError as error:
|
|
210
|
+
self.fail(str(error), param, ctx)
|
|
211
|
+
if enum in self._members:
|
|
212
|
+
return enum
|
|
213
|
+
return self.fail(f"{enum.value!r} is not a selected member")
|
|
214
|
+
|
|
215
|
+
@override
|
|
216
|
+
def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
|
|
217
|
+
_ = ctx
|
|
218
|
+
desc = ",".join(str(e.value) if self._value else e.name for e in self._members)
|
|
173
219
|
return _make_metavar(param, desc)
|
|
174
220
|
|
|
175
221
|
|
|
@@ -195,7 +241,7 @@ class IPv4Address(ParamType):
|
|
|
195
241
|
return parse_object(ipaddress.IPv4Address, value)
|
|
196
242
|
except ParseObjectError as error:
|
|
197
243
|
self.fail(str(error), param, ctx)
|
|
198
|
-
case
|
|
244
|
+
case never:
|
|
199
245
|
assert_never(never)
|
|
200
246
|
|
|
201
247
|
|
|
@@ -221,7 +267,7 @@ class IPv6Address(ParamType):
|
|
|
221
267
|
return parse_object(ipaddress.IPv6Address, value)
|
|
222
268
|
except ParseObjectError as error:
|
|
223
269
|
self.fail(str(error), param, ctx)
|
|
224
|
-
case
|
|
270
|
+
case never:
|
|
225
271
|
assert_never(never)
|
|
226
272
|
|
|
227
273
|
|
|
@@ -244,10 +290,33 @@ class MonthDay(ParamType):
|
|
|
244
290
|
return value
|
|
245
291
|
case str():
|
|
246
292
|
try:
|
|
247
|
-
return whenever.MonthDay.
|
|
293
|
+
return whenever.MonthDay.parse_iso(value)
|
|
248
294
|
except ValueError as error:
|
|
249
295
|
self.fail(str(error), param, ctx)
|
|
250
|
-
case
|
|
296
|
+
case never:
|
|
297
|
+
assert_never(never)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class Path(ParamType):
|
|
301
|
+
"""A path-valued parameter."""
|
|
302
|
+
|
|
303
|
+
name = "path"
|
|
304
|
+
|
|
305
|
+
@override
|
|
306
|
+
def __repr__(self) -> str:
|
|
307
|
+
return self.name.upper()
|
|
308
|
+
|
|
309
|
+
@override
|
|
310
|
+
def convert(
|
|
311
|
+
self, value: PathLike, param: Parameter | None, ctx: Context | None
|
|
312
|
+
) -> pathlib.Path:
|
|
313
|
+
"""Convert a value into the `Path` type."""
|
|
314
|
+
match value:
|
|
315
|
+
case pathlib.Path():
|
|
316
|
+
return value.expanduser()
|
|
317
|
+
case str():
|
|
318
|
+
return pathlib.Path(value).expanduser()
|
|
319
|
+
case never:
|
|
251
320
|
assert_never(never)
|
|
252
321
|
|
|
253
322
|
|
|
@@ -270,10 +339,10 @@ class PlainDateTime(ParamType):
|
|
|
270
339
|
return value
|
|
271
340
|
case str():
|
|
272
341
|
try:
|
|
273
|
-
return whenever.PlainDateTime.
|
|
342
|
+
return whenever.PlainDateTime.parse_iso(value)
|
|
274
343
|
except ValueError as error:
|
|
275
344
|
self.fail(str(error), param, ctx)
|
|
276
|
-
case
|
|
345
|
+
case never:
|
|
277
346
|
assert_never(never)
|
|
278
347
|
|
|
279
348
|
|
|
@@ -296,10 +365,10 @@ class Time(ParamType):
|
|
|
296
365
|
return value
|
|
297
366
|
case str():
|
|
298
367
|
try:
|
|
299
|
-
return whenever.Time.
|
|
368
|
+
return whenever.Time.parse_iso(value)
|
|
300
369
|
except ValueError as error:
|
|
301
370
|
self.fail(str(error), param, ctx)
|
|
302
|
-
case
|
|
371
|
+
case never:
|
|
303
372
|
assert_never(never)
|
|
304
373
|
|
|
305
374
|
|
|
@@ -322,10 +391,36 @@ class TimeDelta(ParamType):
|
|
|
322
391
|
return value
|
|
323
392
|
case str():
|
|
324
393
|
try:
|
|
325
|
-
return whenever.TimeDelta.
|
|
394
|
+
return whenever.TimeDelta.parse_iso(value)
|
|
395
|
+
except ValueError as error:
|
|
396
|
+
self.fail(str(error), param, ctx)
|
|
397
|
+
case never:
|
|
398
|
+
assert_never(never)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class UUID(ParamType):
|
|
402
|
+
"""A UUID-valued parameter."""
|
|
403
|
+
|
|
404
|
+
name = "uuid"
|
|
405
|
+
|
|
406
|
+
@override
|
|
407
|
+
def __repr__(self) -> str:
|
|
408
|
+
return self.name.upper()
|
|
409
|
+
|
|
410
|
+
@override
|
|
411
|
+
def convert(
|
|
412
|
+
self, value: uuid.UUID | str, param: Parameter | None, ctx: Context | None
|
|
413
|
+
) -> uuid.UUID:
|
|
414
|
+
"""Convert a value into the `UUID` type."""
|
|
415
|
+
match value:
|
|
416
|
+
case uuid.UUID():
|
|
417
|
+
return value
|
|
418
|
+
case str():
|
|
419
|
+
try:
|
|
420
|
+
return uuid.UUID(value)
|
|
326
421
|
except ValueError as error:
|
|
327
422
|
self.fail(str(error), param, ctx)
|
|
328
|
-
case
|
|
423
|
+
case never:
|
|
329
424
|
assert_never(never)
|
|
330
425
|
|
|
331
426
|
|
|
@@ -348,10 +443,10 @@ class YearMonth(ParamType):
|
|
|
348
443
|
return value
|
|
349
444
|
case str():
|
|
350
445
|
try:
|
|
351
|
-
return whenever.YearMonth.
|
|
446
|
+
return whenever.YearMonth.parse_iso(value)
|
|
352
447
|
except ValueError as error:
|
|
353
448
|
self.fail(str(error), param, ctx)
|
|
354
|
-
case
|
|
449
|
+
case never:
|
|
355
450
|
assert_never(never)
|
|
356
451
|
|
|
357
452
|
|
|
@@ -374,10 +469,10 @@ class ZonedDateTime(ParamType):
|
|
|
374
469
|
return value
|
|
375
470
|
case str():
|
|
376
471
|
try:
|
|
377
|
-
return whenever.ZonedDateTime.
|
|
472
|
+
return whenever.ZonedDateTime.parse_iso(value)
|
|
378
473
|
except ValueError as error:
|
|
379
474
|
self.fail(str(error), param, ctx)
|
|
380
|
-
case
|
|
475
|
+
case never:
|
|
381
476
|
assert_never(never)
|
|
382
477
|
|
|
383
478
|
|
|
@@ -429,7 +524,7 @@ class FrozenSetChoices(FrozenSetParameter[Choice, str]):
|
|
|
429
524
|
@override
|
|
430
525
|
def __init__(
|
|
431
526
|
self,
|
|
432
|
-
choices:
|
|
527
|
+
choices: list[str],
|
|
433
528
|
/,
|
|
434
529
|
*,
|
|
435
530
|
case_sensitive: bool = False,
|
|
@@ -514,7 +609,7 @@ class ListChoices(ListParameter[Choice, str]):
|
|
|
514
609
|
@override
|
|
515
610
|
def __init__(
|
|
516
611
|
self,
|
|
517
|
-
choices:
|
|
612
|
+
choices: list[str],
|
|
518
613
|
/,
|
|
519
614
|
*,
|
|
520
615
|
case_sensitive: bool = False,
|
|
@@ -560,15 +655,13 @@ def _make_metavar(param: Parameter, desc: str, /) -> str:
|
|
|
560
655
|
|
|
561
656
|
|
|
562
657
|
__all__ = [
|
|
563
|
-
"
|
|
658
|
+
"CONTEXT_SETTINGS",
|
|
659
|
+
"UUID",
|
|
564
660
|
"Date",
|
|
565
661
|
"DateDelta",
|
|
566
662
|
"DateTimeDelta",
|
|
567
|
-
"DirPath",
|
|
568
663
|
"Enum",
|
|
569
|
-
"
|
|
570
|
-
"ExistingFilePath",
|
|
571
|
-
"FilePath",
|
|
664
|
+
"EnumPartial",
|
|
572
665
|
"FrozenSetChoices",
|
|
573
666
|
"FrozenSetEnums",
|
|
574
667
|
"FrozenSetParameter",
|
|
@@ -581,6 +674,8 @@ __all__ = [
|
|
|
581
674
|
"ListParameter",
|
|
582
675
|
"ListStrs",
|
|
583
676
|
"MonthDay",
|
|
677
|
+
"Path",
|
|
678
|
+
"Path",
|
|
584
679
|
"PlainDateTime",
|
|
585
680
|
"Time",
|
|
586
681
|
"TimeDelta",
|
utilities/concurrent.py
CHANGED
utilities/contextlib.py
CHANGED
|
@@ -8,7 +8,7 @@ from contextlib import (
|
|
|
8
8
|
asynccontextmanager,
|
|
9
9
|
contextmanager,
|
|
10
10
|
)
|
|
11
|
-
from functools import partial
|
|
11
|
+
from functools import partial, wraps
|
|
12
12
|
from signal import SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, getsignal, signal
|
|
13
13
|
from typing import TYPE_CHECKING, Any, assert_never, cast, overload
|
|
14
14
|
|
|
@@ -77,6 +77,7 @@ def enhanced_context_manager[**P, T_co](
|
|
|
77
77
|
make_gcm = contextmanager(func)
|
|
78
78
|
|
|
79
79
|
@contextmanager
|
|
80
|
+
@wraps(func)
|
|
80
81
|
def wrapped(*args: P.args, **kwargs: P.kwargs) -> Iterator[T_co]:
|
|
81
82
|
gcm = make_gcm(*args, **kwargs)
|
|
82
83
|
sigabrt0 = _swap_handler(SIGABRT, gcm) if sigabrt else None
|
|
@@ -159,6 +160,7 @@ def enhanced_async_context_manager[**P, T_co](
|
|
|
159
160
|
make_agcm = asynccontextmanager(func)
|
|
160
161
|
|
|
161
162
|
@asynccontextmanager
|
|
163
|
+
@wraps(func)
|
|
162
164
|
async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncIterator[T_co]:
|
|
163
165
|
agcm = make_agcm(*args, **kwargs)
|
|
164
166
|
sigabrt0 = _swap_handler(SIGABRT, agcm) if sigabrt else None
|
|
@@ -210,7 +212,7 @@ def _make_handler(
|
|
|
210
212
|
_ = loop.call_soon_threadsafe(
|
|
211
213
|
create_task, agcm.__aexit__(None, None, None)
|
|
212
214
|
)
|
|
213
|
-
case
|
|
215
|
+
case never:
|
|
214
216
|
assert_never(never)
|
|
215
217
|
if callable(orig_handler): # pragma: no cover
|
|
216
218
|
orig_handler(signum, frame)
|
utilities/contextvars.py
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from contextlib import contextmanager
|
|
3
4
|
from contextvars import ContextVar
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from collections.abc import Iterator
|
|
9
|
+
|
|
4
10
|
|
|
5
11
|
##
|
|
6
12
|
|
|
@@ -19,4 +25,17 @@ def set_global_breakpoint() -> None:
|
|
|
19
25
|
_ = _GLOBAL_BREAKPOINT.set(True)
|
|
20
26
|
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
##
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@contextmanager
|
|
32
|
+
def yield_set_context(var: ContextVar[bool], /) -> Iterator[None]:
|
|
33
|
+
"""Yield a context var as being set."""
|
|
34
|
+
token = var.set(True)
|
|
35
|
+
try:
|
|
36
|
+
yield
|
|
37
|
+
finally:
|
|
38
|
+
_ = var.reset(token)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
__all__ = ["global_breakpoint", "set_global_breakpoint", "yield_set_context"]
|
utilities/cryptography.py
CHANGED
|
@@ -11,18 +11,18 @@ _ENV_VAR = "FERNET_KEY"
|
|
|
11
11
|
|
|
12
12
|
def encrypt(text: str, /, *, env_var: str = _ENV_VAR) -> bytes:
|
|
13
13
|
"""Encrypt a string."""
|
|
14
|
-
return get_fernet(env_var
|
|
14
|
+
return get_fernet(env_var).encrypt(text.encode())
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def decrypt(text: bytes, /, *, env_var: str = _ENV_VAR) -> str:
|
|
18
18
|
"""Encrypt a string."""
|
|
19
|
-
return get_fernet(env_var
|
|
19
|
+
return get_fernet(env_var).decrypt(text).decode()
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
##
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def get_fernet(
|
|
25
|
+
def get_fernet(env_var: str = _ENV_VAR, /) -> Fernet:
|
|
26
26
|
"""Get the Fernet key."""
|
|
27
27
|
if (key := getenv(env_var)) is None:
|
|
28
28
|
raise GetFernetError(env_var=env_var)
|
utilities/dataclasses.py
CHANGED
|
@@ -6,11 +6,7 @@ from dataclasses import MISSING, dataclass, field, fields, replace
|
|
|
6
6
|
from typing import TYPE_CHECKING, Any, Literal, assert_never, overload, override
|
|
7
7
|
|
|
8
8
|
from utilities.errors import ImpossibleCaseError
|
|
9
|
-
from utilities.functions import
|
|
10
|
-
get_class_name,
|
|
11
|
-
is_dataclass_class,
|
|
12
|
-
is_dataclass_instance,
|
|
13
|
-
)
|
|
9
|
+
from utilities.functions import get_class_name
|
|
14
10
|
from utilities.iterables import (
|
|
15
11
|
OneStrEmptyError,
|
|
16
12
|
OneStrNonUniqueError,
|
|
@@ -25,7 +21,7 @@ from utilities.parse import (
|
|
|
25
21
|
serialize_object,
|
|
26
22
|
)
|
|
27
23
|
from utilities.re import ExtractGroupError, extract_group
|
|
28
|
-
from utilities.sentinel import Sentinel, sentinel
|
|
24
|
+
from utilities.sentinel import Sentinel, is_sentinel, sentinel
|
|
29
25
|
from utilities.text import (
|
|
30
26
|
BRACKETS,
|
|
31
27
|
LIST_SEPARATOR,
|
|
@@ -34,8 +30,8 @@ from utilities.text import (
|
|
|
34
30
|
_SplitKeyValuePairsSplitError,
|
|
35
31
|
split_key_value_pairs,
|
|
36
32
|
)
|
|
37
|
-
from utilities.types import SupportsLT
|
|
38
|
-
from utilities.typing import get_type_hints
|
|
33
|
+
from utilities.types import MaybeType, SupportsLT
|
|
34
|
+
from utilities.typing import get_type_hints, is_dataclass_class, is_dataclass_instance
|
|
39
35
|
|
|
40
36
|
if TYPE_CHECKING:
|
|
41
37
|
from collections.abc import Callable, Iterable, Iterator
|
|
@@ -214,7 +210,7 @@ def is_nullable_lt[T: SupportsLT](x: T | None, y: T | None, /) -> bool | None:
|
|
|
214
210
|
return True
|
|
215
211
|
case 0:
|
|
216
212
|
return None
|
|
217
|
-
case
|
|
213
|
+
case never:
|
|
218
214
|
assert_never(never)
|
|
219
215
|
|
|
220
216
|
|
|
@@ -275,8 +271,7 @@ def mapping_to_dataclass[T: Dataclass](
|
|
|
275
271
|
default = {
|
|
276
272
|
f.name
|
|
277
273
|
for f in fields_use
|
|
278
|
-
if (not
|
|
279
|
-
or (not isinstance(f.default_factory, Sentinel))
|
|
274
|
+
if (not is_sentinel(f.default)) or (not is_sentinel(f.default_factory))
|
|
280
275
|
}
|
|
281
276
|
have = set(field_names_to_values) | default
|
|
282
277
|
missing = {f.name for f in fields_use} - have
|
|
@@ -434,12 +429,10 @@ def replace_non_sentinel[T: Dataclass](
|
|
|
434
429
|
"""Replace attributes on a dataclass, filtering out sentinel values."""
|
|
435
430
|
if in_place:
|
|
436
431
|
for k, v in kwargs.items():
|
|
437
|
-
if not
|
|
432
|
+
if not is_sentinel(v):
|
|
438
433
|
setattr(obj, k, v)
|
|
439
434
|
return None
|
|
440
|
-
return replace(
|
|
441
|
-
obj, **{k: v for k, v in kwargs.items() if not isinstance(v, Sentinel)}
|
|
442
|
-
)
|
|
435
|
+
return replace(obj, **{k: v for k, v in kwargs.items() if not is_sentinel(v)})
|
|
443
436
|
|
|
444
437
|
|
|
445
438
|
##
|
|
@@ -520,7 +513,7 @@ def parse_dataclass[T: Dataclass](
|
|
|
520
513
|
)
|
|
521
514
|
case Mapping() as keys_to_serializes:
|
|
522
515
|
...
|
|
523
|
-
case
|
|
516
|
+
case never:
|
|
524
517
|
assert_never(never)
|
|
525
518
|
fields = list(
|
|
526
519
|
yield_fields(
|
|
@@ -833,7 +826,7 @@ def yield_fields(
|
|
|
833
826
|
warn_name_errors: bool = False,
|
|
834
827
|
) -> Iterator[_YieldFieldsClass[Any]]: ...
|
|
835
828
|
def yield_fields(
|
|
836
|
-
obj:
|
|
829
|
+
obj: MaybeType[Dataclass],
|
|
837
830
|
/,
|
|
838
831
|
*,
|
|
839
832
|
globalns: StrMapping | None = None,
|
|
@@ -912,17 +905,11 @@ class _YieldFieldsInstance[T]:
|
|
|
912
905
|
extra: Mapping[type[U], Callable[[U, U], bool]] | None = None,
|
|
913
906
|
) -> bool:
|
|
914
907
|
"""Check if the field value equals its default."""
|
|
915
|
-
if
|
|
916
|
-
self.default_factory, Sentinel
|
|
917
|
-
):
|
|
908
|
+
if is_sentinel(self.default) and is_sentinel(self.default_factory):
|
|
918
909
|
return False
|
|
919
|
-
if (not
|
|
920
|
-
self.default_factory, Sentinel
|
|
921
|
-
):
|
|
910
|
+
if (not is_sentinel(self.default)) and is_sentinel(self.default_factory):
|
|
922
911
|
expected = self.default
|
|
923
|
-
elif
|
|
924
|
-
not isinstance(self.default_factory, Sentinel)
|
|
925
|
-
):
|
|
912
|
+
elif is_sentinel(self.default) and (not is_sentinel(self.default_factory)):
|
|
926
913
|
expected = self.default_factory()
|
|
927
914
|
else: # pragma: no cover
|
|
928
915
|
raise ImpossibleCaseError(
|
|
@@ -1002,7 +989,7 @@ def _empty_error_str_core(
|
|
|
1002
989
|
return f"any field starting with {key!r}"
|
|
1003
990
|
case True, False:
|
|
1004
991
|
return f"any field starting with {key!r} (modulo case)"
|
|
1005
|
-
case
|
|
992
|
+
case never:
|
|
1006
993
|
assert_never(never)
|
|
1007
994
|
|
|
1008
995
|
|
|
@@ -1043,7 +1030,7 @@ def _non_unique_error_str_core(
|
|
|
1043
1030
|
head_msg = f"exactly one field starting with {key!r}"
|
|
1044
1031
|
case True, False:
|
|
1045
1032
|
head_msg = f"exactly one field starting with {key!r} (modulo case)"
|
|
1046
|
-
case
|
|
1033
|
+
case never:
|
|
1047
1034
|
assert_never(never)
|
|
1048
1035
|
return f"{head_msg}; got {first!r}, {second!r} and perhaps more"
|
|
1049
1036
|
|