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

Files changed (83) hide show
  1. dycw_utilities-0.174.12.dist-info/METADATA +41 -0
  2. dycw_utilities-0.174.12.dist-info/RECORD +104 -0
  3. dycw_utilities-0.174.12.dist-info/WHEEL +4 -0
  4. {dycw_utilities-0.148.5.dist-info → dycw_utilities-0.174.12.dist-info}/entry_points.txt +3 -0
  5. utilities/__init__.py +1 -1
  6. utilities/{eventkit.py → aeventkit.py} +12 -11
  7. utilities/altair.py +7 -6
  8. utilities/asyncio.py +113 -64
  9. utilities/atomicwrites.py +1 -1
  10. utilities/atools.py +64 -4
  11. utilities/cachetools.py +9 -6
  12. utilities/click.py +145 -49
  13. utilities/concurrent.py +1 -1
  14. utilities/contextlib.py +4 -2
  15. utilities/contextvars.py +20 -1
  16. utilities/cryptography.py +3 -3
  17. utilities/dataclasses.py +15 -28
  18. utilities/docker.py +292 -0
  19. utilities/enum.py +2 -2
  20. utilities/errors.py +1 -1
  21. utilities/fastapi.py +8 -3
  22. utilities/fpdf2.py +2 -2
  23. utilities/functions.py +20 -297
  24. utilities/git.py +19 -0
  25. utilities/grp.py +28 -0
  26. utilities/hypothesis.py +360 -78
  27. utilities/inflect.py +1 -1
  28. utilities/iterables.py +12 -58
  29. utilities/jinja2.py +148 -0
  30. utilities/json.py +1 -1
  31. utilities/libcst.py +7 -7
  32. utilities/logging.py +74 -85
  33. utilities/math.py +8 -4
  34. utilities/more_itertools.py +4 -6
  35. utilities/operator.py +1 -1
  36. utilities/orjson.py +86 -34
  37. utilities/os.py +49 -2
  38. utilities/parse.py +2 -2
  39. utilities/pathlib.py +66 -34
  40. utilities/permissions.py +297 -0
  41. utilities/platform.py +5 -5
  42. utilities/polars.py +932 -420
  43. utilities/polars_ols.py +1 -1
  44. utilities/postgres.py +296 -174
  45. utilities/pottery.py +8 -73
  46. utilities/pqdm.py +3 -3
  47. utilities/pwd.py +28 -0
  48. utilities/pydantic.py +11 -0
  49. utilities/pydantic_settings.py +240 -0
  50. utilities/pydantic_settings_sops.py +76 -0
  51. utilities/pyinstrument.py +5 -5
  52. utilities/pytest.py +155 -46
  53. utilities/pytest_plugins/pytest_randomly.py +1 -1
  54. utilities/pytest_plugins/pytest_regressions.py +7 -3
  55. utilities/pytest_regressions.py +2 -3
  56. utilities/random.py +11 -6
  57. utilities/re.py +1 -1
  58. utilities/redis.py +101 -64
  59. utilities/sentinel.py +10 -0
  60. utilities/shelve.py +4 -1
  61. utilities/shutil.py +25 -0
  62. utilities/slack_sdk.py +8 -3
  63. utilities/sqlalchemy.py +422 -352
  64. utilities/sqlalchemy_polars.py +28 -52
  65. utilities/string.py +1 -1
  66. utilities/subprocess.py +864 -0
  67. utilities/tempfile.py +62 -4
  68. utilities/testbook.py +50 -0
  69. utilities/text.py +165 -42
  70. utilities/timer.py +2 -2
  71. utilities/traceback.py +46 -36
  72. utilities/types.py +62 -23
  73. utilities/typing.py +479 -19
  74. utilities/uuid.py +42 -5
  75. utilities/version.py +27 -26
  76. utilities/whenever.py +661 -151
  77. utilities/zoneinfo.py +80 -22
  78. dycw_utilities-0.148.5.dist-info/METADATA +0 -41
  79. dycw_utilities-0.148.5.dist-info/RECORD +0 -95
  80. dycw_utilities-0.148.5.dist-info/WHEEL +0 -4
  81. dycw_utilities-0.148.5.dist-info/licenses/LICENSE +0 -21
  82. utilities/period.py +0 -237
  83. utilities/typed_settings.py +0 -144
utilities/atools.py CHANGED
@@ -1,14 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Callable
4
- from typing import TYPE_CHECKING, Any
4
+ from functools import partial
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING, Any, cast, overload
5
7
 
6
- from atools import memoize
8
+ import atools
9
+ from whenever import TimeDelta
7
10
 
8
- from utilities.types import Coro
11
+ from utilities.types import Coro, PathLike
9
12
 
10
13
  if TYPE_CHECKING:
11
- from whenever import TimeDelta
14
+ from atools._memoize_decorator import Keygen, Pickler
12
15
 
13
16
 
14
17
  type _Key[**P, T] = tuple[Callable[P, Coro[T]], TimeDelta]
@@ -36,4 +39,61 @@ async def call_memoized[**P, T](
36
39
  return await memoized_func(*args, **kwargs)
37
40
 
38
41
 
42
+ ##
43
+
44
+
45
+ @overload
46
+ def memoize[F: Callable[..., Coro[Any]]](
47
+ func: F,
48
+ /,
49
+ *,
50
+ db_path: PathLike | None = None,
51
+ duration: float | TimeDelta | None = None,
52
+ keygen: Keygen | None = None,
53
+ pickler: Pickler | None = None,
54
+ size: int | None = None,
55
+ ) -> F: ...
56
+ @overload
57
+ def memoize[F: Callable[..., Coro[Any]]](
58
+ func: None = None,
59
+ /,
60
+ *,
61
+ db_path: PathLike | None = None,
62
+ duration: float | TimeDelta | None = None,
63
+ keygen: Keygen | None = None,
64
+ pickler: Pickler | None = None,
65
+ size: int | None = None,
66
+ ) -> Callable[[F], F]: ...
67
+ def memoize[F: Callable[..., Coro[Any]]](
68
+ func: F | None = None,
69
+ /,
70
+ *,
71
+ db_path: PathLike | None = None,
72
+ duration: float | TimeDelta | None = None,
73
+ keygen: Keygen | None = None,
74
+ pickler: Pickler | None = None,
75
+ size: int | None = None,
76
+ ) -> F | Callable[[F], F]:
77
+ """Memoize a function."""
78
+ if func is None:
79
+ result = partial(
80
+ memoize,
81
+ db_path=db_path,
82
+ duration=duration,
83
+ keygen=keygen,
84
+ pickler=pickler,
85
+ size=size,
86
+ )
87
+ return cast("Callable[[F], F]", result)
88
+ return atools.memoize(
89
+ db_path=None if db_path is None else Path(db_path),
90
+ duration=duration.py_timedelta()
91
+ if isinstance(duration, TimeDelta)
92
+ else duration,
93
+ keygen=keygen,
94
+ pickler=pickler,
95
+ size=size,
96
+ )(func)
97
+
98
+
39
99
  __all__ = ["call_memoized"]
utilities/cachetools.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from collections.abc import Callable, Hashable, Iterable, Iterator, MutableSet
4
4
  from math import inf
5
5
  from time import monotonic
6
- from typing import TYPE_CHECKING, Any, override
6
+ from typing import TYPE_CHECKING, Any, cast, override
7
7
 
8
8
  import cachetools
9
9
  from cachetools.func import ttl_cache
@@ -100,11 +100,14 @@ def cache[F: Callable](
100
100
  typed_: bool = False,
101
101
  ) -> Callable[[F], F]:
102
102
  """Decorate a function with `max_size` and/or `ttl` settings."""
103
- return ttl_cache(
104
- maxsize=inf if max_size is None else max_size,
105
- ttl=inf if max_duration is None else max_duration.in_seconds(),
106
- timer=timer,
107
- typed=typed_,
103
+ return cast(
104
+ "F",
105
+ ttl_cache(
106
+ maxsize=max_size,
107
+ ttl=inf if max_duration is None else max_duration.in_seconds(),
108
+ timer=timer,
109
+ typed=typed_,
110
+ ),
108
111
  )
109
112
 
110
113
 
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, Sequence
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
- FilePath = click.Path(file_okay=True, dir_okay=False, path_type=pathlib.Path)
40
- DirPath = click.Path(file_okay=False, dir_okay=True, path_type=pathlib.Path)
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
- CONTEXT_SETTINGS_HELP_OPTION_NAMES = _ContextSettings(
58
- context_settings=_HelpOptionNames(help_option_names=["-h", "--help"])
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.parse_common_iso(value)
82
+ return whenever.Date.parse_iso(value)
85
83
  except ValueError as error:
86
84
  self.fail(str(error), param, ctx)
87
- case _ as never:
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.parse_common_iso(value)
108
+ return whenever.DateDelta.parse_iso(value)
111
109
  except ValueError as error:
112
110
  self.fail(str(error), param, ctx)
113
- case _ as never:
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.parse_common_iso(value)
134
+ return whenever.DateTimeDelta.parse_iso(value)
137
135
  except ValueError as error:
138
136
  self.fail(str(error), param, ctx)
139
- case _ as never:
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__(self, enum: type[E], /, *, case_sensitive: bool = False) -> None:
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,53 @@ 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
+ self.fail(f"{enum.value!r} is not a selected member")
214
+ return None
215
+
216
+ @override
217
+ def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
218
+ _ = ctx
219
+ desc = ",".join(str(e.value) if self._value else e.name for e in self._members)
173
220
  return _make_metavar(param, desc)
174
221
 
175
222
 
@@ -195,7 +242,7 @@ class IPv4Address(ParamType):
195
242
  return parse_object(ipaddress.IPv4Address, value)
196
243
  except ParseObjectError as error:
197
244
  self.fail(str(error), param, ctx)
198
- case _ as never:
245
+ case never:
199
246
  assert_never(never)
200
247
 
201
248
 
@@ -221,7 +268,7 @@ class IPv6Address(ParamType):
221
268
  return parse_object(ipaddress.IPv6Address, value)
222
269
  except ParseObjectError as error:
223
270
  self.fail(str(error), param, ctx)
224
- case _ as never:
271
+ case never:
225
272
  assert_never(never)
226
273
 
227
274
 
@@ -244,10 +291,33 @@ class MonthDay(ParamType):
244
291
  return value
245
292
  case str():
246
293
  try:
247
- return whenever.MonthDay.parse_common_iso(value)
294
+ return whenever.MonthDay.parse_iso(value)
248
295
  except ValueError as error:
249
296
  self.fail(str(error), param, ctx)
250
- case _ as never:
297
+ case never:
298
+ assert_never(never)
299
+
300
+
301
+ class Path(ParamType):
302
+ """A path-valued parameter."""
303
+
304
+ name = "path"
305
+
306
+ @override
307
+ def __repr__(self) -> str:
308
+ return self.name.upper()
309
+
310
+ @override
311
+ def convert(
312
+ self, value: PathLike, param: Parameter | None, ctx: Context | None
313
+ ) -> pathlib.Path:
314
+ """Convert a value into the `Path` type."""
315
+ match value:
316
+ case pathlib.Path():
317
+ return value.expanduser()
318
+ case str():
319
+ return pathlib.Path(value).expanduser()
320
+ case never:
251
321
  assert_never(never)
252
322
 
253
323
 
@@ -270,10 +340,10 @@ class PlainDateTime(ParamType):
270
340
  return value
271
341
  case str():
272
342
  try:
273
- return whenever.PlainDateTime.parse_common_iso(value)
343
+ return whenever.PlainDateTime.parse_iso(value)
274
344
  except ValueError as error:
275
345
  self.fail(str(error), param, ctx)
276
- case _ as never:
346
+ case never:
277
347
  assert_never(never)
278
348
 
279
349
 
@@ -296,10 +366,10 @@ class Time(ParamType):
296
366
  return value
297
367
  case str():
298
368
  try:
299
- return whenever.Time.parse_common_iso(value)
369
+ return whenever.Time.parse_iso(value)
300
370
  except ValueError as error:
301
371
  self.fail(str(error), param, ctx)
302
- case _ as never:
372
+ case never:
303
373
  assert_never(never)
304
374
 
305
375
 
@@ -322,10 +392,36 @@ class TimeDelta(ParamType):
322
392
  return value
323
393
  case str():
324
394
  try:
325
- return whenever.TimeDelta.parse_common_iso(value)
395
+ return whenever.TimeDelta.parse_iso(value)
396
+ except ValueError as error:
397
+ self.fail(str(error), param, ctx)
398
+ case never:
399
+ assert_never(never)
400
+
401
+
402
+ class UUID(ParamType):
403
+ """A UUID-valued parameter."""
404
+
405
+ name = "uuid"
406
+
407
+ @override
408
+ def __repr__(self) -> str:
409
+ return self.name.upper()
410
+
411
+ @override
412
+ def convert(
413
+ self, value: uuid.UUID | str, param: Parameter | None, ctx: Context | None
414
+ ) -> uuid.UUID:
415
+ """Convert a value into the `UUID` type."""
416
+ match value:
417
+ case uuid.UUID():
418
+ return value
419
+ case str():
420
+ try:
421
+ return uuid.UUID(value)
326
422
  except ValueError as error:
327
423
  self.fail(str(error), param, ctx)
328
- case _ as never:
424
+ case never:
329
425
  assert_never(never)
330
426
 
331
427
 
@@ -348,10 +444,10 @@ class YearMonth(ParamType):
348
444
  return value
349
445
  case str():
350
446
  try:
351
- return whenever.YearMonth.parse_common_iso(value)
447
+ return whenever.YearMonth.parse_iso(value)
352
448
  except ValueError as error:
353
449
  self.fail(str(error), param, ctx)
354
- case _ as never:
450
+ case never:
355
451
  assert_never(never)
356
452
 
357
453
 
@@ -374,10 +470,10 @@ class ZonedDateTime(ParamType):
374
470
  return value
375
471
  case str():
376
472
  try:
377
- return whenever.ZonedDateTime.parse_common_iso(value)
473
+ return whenever.ZonedDateTime.parse_iso(value)
378
474
  except ValueError as error:
379
475
  self.fail(str(error), param, ctx)
380
- case _ as never:
476
+ case never:
381
477
  assert_never(never)
382
478
 
383
479
 
@@ -429,7 +525,7 @@ class FrozenSetChoices(FrozenSetParameter[Choice, str]):
429
525
  @override
430
526
  def __init__(
431
527
  self,
432
- choices: Sequence[str],
528
+ choices: list[str],
433
529
  /,
434
530
  *,
435
531
  case_sensitive: bool = False,
@@ -514,7 +610,7 @@ class ListChoices(ListParameter[Choice, str]):
514
610
  @override
515
611
  def __init__(
516
612
  self,
517
- choices: Sequence[str],
613
+ choices: list[str],
518
614
  /,
519
615
  *,
520
616
  case_sensitive: bool = False,
@@ -560,15 +656,13 @@ def _make_metavar(param: Parameter, desc: str, /) -> str:
560
656
 
561
657
 
562
658
  __all__ = [
563
- "CONTEXT_SETTINGS_HELP_OPTION_NAMES",
659
+ "CONTEXT_SETTINGS",
660
+ "UUID",
564
661
  "Date",
565
662
  "DateDelta",
566
663
  "DateTimeDelta",
567
- "DirPath",
568
664
  "Enum",
569
- "ExistingDirPath",
570
- "ExistingFilePath",
571
- "FilePath",
665
+ "EnumPartial",
572
666
  "FrozenSetChoices",
573
667
  "FrozenSetEnums",
574
668
  "FrozenSetParameter",
@@ -581,6 +675,8 @@ __all__ = [
581
675
  "ListParameter",
582
676
  "ListStrs",
583
677
  "MonthDay",
678
+ "Path",
679
+ "Path",
584
680
  "PlainDateTime",
585
681
  "Time",
586
682
  "TimeDelta",
utilities/concurrent.py CHANGED
@@ -84,7 +84,7 @@ def concurrent_starmap[T](
84
84
  initargs=initargs,
85
85
  ) as pool:
86
86
  result = pool.map(apply, iterable, timeout=timeout, chunksize=chunksize)
87
- case _ as never:
87
+ case never:
88
88
  assert_never(never)
89
89
  return list(result)
90
90
 
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 _ as never:
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
- __all__ = ["global_breakpoint", "set_global_breakpoint"]
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=env_var).encrypt(text.encode())
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=env_var).decrypt(text).decode()
19
+ return get_fernet(env_var).decrypt(text).decode()
20
20
 
21
21
 
22
22
  ##
23
23
 
24
24
 
25
- def get_fernet(*, env_var: str = _ENV_VAR) -> 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)