dycw-utilities 0.152.0__py3-none-any.whl → 0.153.1__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.
- {dycw_utilities-0.152.0.dist-info → dycw_utilities-0.153.1.dist-info}/METADATA +1 -1
- {dycw_utilities-0.152.0.dist-info → dycw_utilities-0.153.1.dist-info}/RECORD +26 -27
- utilities/__init__.py +1 -1
- utilities/asyncio.py +12 -11
- utilities/cryptography.py +3 -3
- utilities/eventkit.py +8 -8
- utilities/functions.py +1 -33
- utilities/logging.py +23 -24
- utilities/pathlib.py +34 -34
- utilities/postgres.py +12 -12
- utilities/pottery.py +5 -5
- utilities/pyinstrument.py +3 -3
- utilities/pytest.py +8 -8
- utilities/pytest_plugins/pytest_randomly.py +1 -1
- utilities/pytest_plugins/pytest_regressions.py +1 -1
- utilities/random.py +8 -6
- utilities/text.py +58 -2
- utilities/traceback.py +15 -18
- utilities/typed_settings.py +3 -3
- utilities/types.py +24 -14
- utilities/uuid.py +42 -5
- utilities/version.py +27 -26
- utilities/whenever.py +431 -37
- utilities/period.py +0 -370
- {dycw_utilities-0.152.0.dist-info → dycw_utilities-0.153.1.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.152.0.dist-info → dycw_utilities-0.153.1.dist-info}/entry_points.txt +0 -0
- {dycw_utilities-0.152.0.dist-info → dycw_utilities-0.153.1.dist-info}/licenses/LICENSE +0 -0
utilities/postgres.py
CHANGED
@@ -10,7 +10,7 @@ from sqlalchemy.orm import DeclarativeBase
|
|
10
10
|
|
11
11
|
from utilities.asyncio import stream_command
|
12
12
|
from utilities.iterables import always_iterable
|
13
|
-
from utilities.logging import
|
13
|
+
from utilities.logging import to_logger
|
14
14
|
from utilities.os import temp_environ
|
15
15
|
from utilities.pathlib import ensure_suffix
|
16
16
|
from utilities.sqlalchemy import extract_url, get_table_name
|
@@ -22,7 +22,7 @@ if TYPE_CHECKING:
|
|
22
22
|
|
23
23
|
from utilities.sqlalchemy import TableOrORMInstOrClass
|
24
24
|
from utilities.types import (
|
25
|
-
|
25
|
+
LoggerLike,
|
26
26
|
MaybeCollection,
|
27
27
|
MaybeCollectionStr,
|
28
28
|
PathLike,
|
@@ -53,7 +53,7 @@ async def pg_dump(
|
|
53
53
|
role: str | None = None,
|
54
54
|
docker: str | None = None,
|
55
55
|
dry_run: bool = False,
|
56
|
-
logger:
|
56
|
+
logger: LoggerLike | None = None,
|
57
57
|
) -> None:
|
58
58
|
"""Run `pg_dump`."""
|
59
59
|
path = _path_pg_dump(path, format_=format_)
|
@@ -79,14 +79,14 @@ async def pg_dump(
|
|
79
79
|
)
|
80
80
|
if dry_run:
|
81
81
|
if logger is not None:
|
82
|
-
|
82
|
+
to_logger(logger).info("Would run:\n\t%r", str(cmd))
|
83
83
|
return
|
84
84
|
with temp_environ(PGPASSWORD=url.password), Timer() as timer: # pragma: no cover
|
85
85
|
try:
|
86
86
|
output = await stream_command(cmd)
|
87
87
|
except KeyboardInterrupt:
|
88
88
|
if logger is not None:
|
89
|
-
|
89
|
+
to_logger(logger).info(
|
90
90
|
"Cancelled backup to %r after %s", str(path), timer
|
91
91
|
)
|
92
92
|
rmtree(path, ignore_errors=True)
|
@@ -94,12 +94,12 @@ async def pg_dump(
|
|
94
94
|
match output.return_code:
|
95
95
|
case 0:
|
96
96
|
if logger is not None:
|
97
|
-
|
97
|
+
to_logger(logger).info(
|
98
98
|
"Backup to %r finished after %s", str(path), timer
|
99
99
|
)
|
100
100
|
case _:
|
101
101
|
if logger is not None:
|
102
|
-
|
102
|
+
to_logger(logger).exception(
|
103
103
|
"Backup to %r failed after %s\nstderr:\n%s",
|
104
104
|
str(path),
|
105
105
|
timer,
|
@@ -217,7 +217,7 @@ async def restore(
|
|
217
217
|
role: str | None = None,
|
218
218
|
docker: str | None = None,
|
219
219
|
dry_run: bool = False,
|
220
|
-
logger:
|
220
|
+
logger: LoggerLike | None = None,
|
221
221
|
) -> None:
|
222
222
|
"""Run `pg_restore`/`psql`."""
|
223
223
|
cmd = _build_pg_restore_or_psql(
|
@@ -236,26 +236,26 @@ async def restore(
|
|
236
236
|
)
|
237
237
|
if dry_run:
|
238
238
|
if logger is not None:
|
239
|
-
|
239
|
+
to_logger(logger).info("Would run:\n\t%r", str(cmd))
|
240
240
|
return
|
241
241
|
with temp_environ(PGPASSWORD=url.password), Timer() as timer: # pragma: no cover
|
242
242
|
try:
|
243
243
|
output = await stream_command(cmd)
|
244
244
|
except KeyboardInterrupt:
|
245
245
|
if logger is not None:
|
246
|
-
|
246
|
+
to_logger(logger).info(
|
247
247
|
"Cancelled restore from %r after %s", str(path), timer
|
248
248
|
)
|
249
249
|
else:
|
250
250
|
match output.return_code:
|
251
251
|
case 0:
|
252
252
|
if logger is not None:
|
253
|
-
|
253
|
+
to_logger(logger).info(
|
254
254
|
"Restore from %r finished after %s", str(path), timer
|
255
255
|
)
|
256
256
|
case _:
|
257
257
|
if logger is not None:
|
258
|
-
|
258
|
+
to_logger(logger).exception(
|
259
259
|
"Restore from %r failed after %s\nstderr:\n%s",
|
260
260
|
str(path),
|
261
261
|
timer,
|
utilities/pottery.py
CHANGED
@@ -14,7 +14,7 @@ from utilities.asyncio import loop_until_succeed, sleep_td, timeout_td
|
|
14
14
|
from utilities.contextlib import enhanced_async_context_manager
|
15
15
|
from utilities.contextvars import yield_set_context
|
16
16
|
from utilities.iterables import always_iterable
|
17
|
-
from utilities.logging import
|
17
|
+
from utilities.logging import to_logger
|
18
18
|
from utilities.whenever import MILLISECOND, SECOND, to_seconds
|
19
19
|
|
20
20
|
if TYPE_CHECKING:
|
@@ -23,7 +23,7 @@ if TYPE_CHECKING:
|
|
23
23
|
|
24
24
|
from whenever import Delta
|
25
25
|
|
26
|
-
from utilities.types import Coro,
|
26
|
+
from utilities.types import Coro, LoggerLike, MaybeIterable
|
27
27
|
|
28
28
|
_NUM: int = 1
|
29
29
|
_TIMEOUT_TRY_ACQUIRE: Delta = SECOND
|
@@ -57,7 +57,7 @@ async def try_yield_coroutine_looper(
|
|
57
57
|
timeout_acquire: Delta = _TIMEOUT_TRY_ACQUIRE,
|
58
58
|
sleep_acquire: Delta = _SLEEP,
|
59
59
|
throttle: Delta | None = None,
|
60
|
-
logger:
|
60
|
+
logger: LoggerLike | None = None,
|
61
61
|
sleep_error: Delta | None = None,
|
62
62
|
context: ContextVar[bool] | None = None,
|
63
63
|
) -> AsyncIterator[CoroutineLooper | None]:
|
@@ -81,7 +81,7 @@ async def try_yield_coroutine_looper(
|
|
81
81
|
yield looper
|
82
82
|
except _YieldAccessUnableToAcquireLockError as error: # skipif-ci-and-not-linux
|
83
83
|
if logger is not None:
|
84
|
-
|
84
|
+
to_logger(logger).info("%s", error)
|
85
85
|
async with nullcontext():
|
86
86
|
yield
|
87
87
|
|
@@ -91,7 +91,7 @@ class CoroutineLooper:
|
|
91
91
|
"""Looper, guarded by a lock, to repeatedly call a coroutine until it succeeds."""
|
92
92
|
|
93
93
|
lock: AIORedlock
|
94
|
-
logger:
|
94
|
+
logger: LoggerLike | None = None
|
95
95
|
sleep: Delta | None = None
|
96
96
|
|
97
97
|
async def __call__[**P](
|
utilities/pyinstrument.py
CHANGED
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
|
|
7
7
|
from pyinstrument.profiler import Profiler
|
8
8
|
|
9
9
|
from utilities.atomicwrites import writer
|
10
|
-
from utilities.pathlib import
|
10
|
+
from utilities.pathlib import to_path
|
11
11
|
from utilities.whenever import format_compact, get_now, to_local_plain
|
12
12
|
|
13
13
|
if TYPE_CHECKING:
|
@@ -17,11 +17,11 @@ if TYPE_CHECKING:
|
|
17
17
|
|
18
18
|
|
19
19
|
@contextmanager
|
20
|
-
def profile(
|
20
|
+
def profile(path: MaybeCallablePathLike = Path.cwd, /) -> Iterator[None]:
|
21
21
|
"""Profile the contents of a block."""
|
22
22
|
with Profiler() as profiler:
|
23
23
|
yield
|
24
|
-
filename =
|
24
|
+
filename = to_path(path).joinpath(
|
25
25
|
f"profile__{format_compact(to_local_plain(get_now()))}.html"
|
26
26
|
)
|
27
27
|
with writer(filename) as temp:
|
utilities/pytest.py
CHANGED
@@ -173,7 +173,7 @@ def _throttle_inner[F: Callable[..., MaybeCoro[None]]](
|
|
173
173
|
def throttle_sync_on_pass(*args: Any, **kwargs: Any) -> None:
|
174
174
|
_skipif_recent(root=root, delta=delta)
|
175
175
|
cast("Callable[..., None]", func)(*args, **kwargs)
|
176
|
-
_write(root
|
176
|
+
_write(root)
|
177
177
|
|
178
178
|
return cast("Any", throttle_sync_on_pass)
|
179
179
|
|
@@ -182,7 +182,7 @@ def _throttle_inner[F: Callable[..., MaybeCoro[None]]](
|
|
182
182
|
@wraps(func)
|
183
183
|
def throttle_sync_on_try(*args: Any, **kwargs: Any) -> None:
|
184
184
|
_skipif_recent(root=root, delta=delta)
|
185
|
-
_write(root
|
185
|
+
_write(root)
|
186
186
|
cast("Callable[..., None]", func)(*args, **kwargs)
|
187
187
|
|
188
188
|
return cast("Any", throttle_sync_on_try)
|
@@ -193,7 +193,7 @@ def _throttle_inner[F: Callable[..., MaybeCoro[None]]](
|
|
193
193
|
async def throttle_async_on_pass(*args: Any, **kwargs: Any) -> None:
|
194
194
|
_skipif_recent(root=root, delta=delta)
|
195
195
|
await cast("Callable[..., Coro[None]]", func)(*args, **kwargs)
|
196
|
-
_write(root
|
196
|
+
_write(root)
|
197
197
|
|
198
198
|
return cast("Any", throttle_async_on_pass)
|
199
199
|
|
@@ -202,7 +202,7 @@ def _throttle_inner[F: Callable[..., MaybeCoro[None]]](
|
|
202
202
|
@wraps(func)
|
203
203
|
async def throttle_async_on_try(*args: Any, **kwargs: Any) -> None:
|
204
204
|
_skipif_recent(root=root, delta=delta)
|
205
|
-
_write(root
|
205
|
+
_write(root)
|
206
206
|
await cast("Callable[..., Coro[None]]", func)(*args, **kwargs)
|
207
207
|
|
208
208
|
return cast("Any", throttle_async_on_try)
|
@@ -214,7 +214,7 @@ def _throttle_inner[F: Callable[..., MaybeCoro[None]]](
|
|
214
214
|
def _skipif_recent(*, root: PathLike | None = None, delta: Delta = SECOND) -> None:
|
215
215
|
if skip is None:
|
216
216
|
return # pragma: no cover
|
217
|
-
path = _get_path(root
|
217
|
+
path = _get_path(root)
|
218
218
|
try:
|
219
219
|
contents = path.read_text()
|
220
220
|
except FileNotFoundError:
|
@@ -229,7 +229,7 @@ def _skipif_recent(*, root: PathLike | None = None, delta: Delta = SECOND) -> No
|
|
229
229
|
_ = skip(reason=f"{_get_name()} throttled (age {age})")
|
230
230
|
|
231
231
|
|
232
|
-
def _get_path(
|
232
|
+
def _get_path(root: PathLike | None = None, /) -> Path:
|
233
233
|
if root is None:
|
234
234
|
root_use = get_root().joinpath(".pytest_cache", "throttle") # pragma: no cover
|
235
235
|
else:
|
@@ -246,8 +246,8 @@ def _get_name() -> str:
|
|
246
246
|
return environ["PYTEST_CURRENT_TEST"]
|
247
247
|
|
248
248
|
|
249
|
-
def _write(
|
250
|
-
path = _get_path(root
|
249
|
+
def _write(root: PathLike | None = None, /) -> None:
|
250
|
+
path = _get_path(root)
|
251
251
|
with writer(path, overwrite=True) as temp:
|
252
252
|
_ = temp.write_text(get_now_local().format_common_iso())
|
253
253
|
|
@@ -46,7 +46,7 @@ def _get_path(request: FixtureRequest, /) -> Path:
|
|
46
46
|
path = Path(cast("Any", request).fspath)
|
47
47
|
root = Path("src", "tests")
|
48
48
|
tail = node_id_path(request.node.nodeid, root=root)
|
49
|
-
return get_root(path
|
49
|
+
return get_root(path).joinpath(root, "regressions", tail)
|
50
50
|
|
51
51
|
|
52
52
|
__all__ = ["orjson_regression", "polars_regression"]
|
utilities/random.py
CHANGED
@@ -17,16 +17,16 @@ SYSTEM_RANDOM = SystemRandom()
|
|
17
17
|
|
18
18
|
def bernoulli(*, true: float = 0.5, seed: Seed | None = None) -> bool:
|
19
19
|
"""Return a Bernoulli random variate."""
|
20
|
-
|
21
|
-
return bool(
|
20
|
+
state = get_state(seed)
|
21
|
+
return bool(state.binomialvariate(p=true))
|
22
22
|
|
23
23
|
|
24
24
|
##
|
25
25
|
|
26
26
|
|
27
|
-
def get_docker_name(
|
27
|
+
def get_docker_name(seed: Seed | None = None, /) -> str:
|
28
28
|
"""Get a docker name."""
|
29
|
-
state = get_state(seed
|
29
|
+
state = get_state(seed)
|
30
30
|
prefix = state.choice(_DOCKER_PREFIXES)
|
31
31
|
suffix = state.choice(_DOCKER_SUFFIXES)
|
32
32
|
digit = state.randint(0, 9)
|
@@ -47,16 +47,18 @@ _DOCKER_SUFFIXES = [
|
|
47
47
|
##
|
48
48
|
|
49
49
|
|
50
|
-
def get_state(
|
50
|
+
def get_state(seed: Seed | None = None, /) -> Random:
|
51
51
|
"""Get a random state."""
|
52
52
|
return seed if isinstance(seed, Random) else Random(x=seed)
|
53
53
|
|
54
54
|
|
55
55
|
##
|
56
|
+
|
57
|
+
|
56
58
|
def shuffle[T](iterable: Iterable[T], /, *, seed: Seed | None = None) -> list[T]:
|
57
59
|
"""Shuffle an iterable."""
|
58
60
|
copy = list(iterable).copy()
|
59
|
-
state = get_state(seed
|
61
|
+
state = get_state(seed)
|
60
62
|
state.shuffle(copy)
|
61
63
|
return copy
|
62
64
|
|
utilities/text.py
CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import re
|
4
4
|
from collections import deque
|
5
|
+
from collections.abc import Callable
|
5
6
|
from dataclasses import dataclass
|
6
7
|
from itertools import chain
|
7
8
|
from os import getpid
|
@@ -9,16 +10,25 @@ from re import IGNORECASE, Match, escape, search
|
|
9
10
|
from textwrap import dedent
|
10
11
|
from threading import get_ident
|
11
12
|
from time import time_ns
|
12
|
-
from typing import
|
13
|
+
from typing import (
|
14
|
+
TYPE_CHECKING,
|
15
|
+
Any,
|
16
|
+
ClassVar,
|
17
|
+
Literal,
|
18
|
+
assert_never,
|
19
|
+
overload,
|
20
|
+
override,
|
21
|
+
)
|
13
22
|
from uuid import uuid4
|
14
23
|
|
15
24
|
from utilities.iterables import CheckDuplicatesError, check_duplicates, transpose
|
16
25
|
from utilities.reprlib import get_repr
|
26
|
+
from utilities.sentinel import Sentinel
|
17
27
|
|
18
28
|
if TYPE_CHECKING:
|
19
29
|
from collections.abc import Iterable, Mapping, Sequence
|
20
30
|
|
21
|
-
from utilities.types import StrStrMapping
|
31
|
+
from utilities.types import MaybeCallableBoolLike, MaybeCallableStr, StrStrMapping
|
22
32
|
|
23
33
|
|
24
34
|
DEFAULT_SEPARATOR = ","
|
@@ -428,6 +438,50 @@ def strip_and_dedent(text: str, /, *, trailing: bool = False) -> str:
|
|
428
438
|
##
|
429
439
|
|
430
440
|
|
441
|
+
@overload
|
442
|
+
def to_bool(bool_: MaybeCallableBoolLike, /) -> bool: ...
|
443
|
+
@overload
|
444
|
+
def to_bool(bool_: None, /) -> None: ...
|
445
|
+
@overload
|
446
|
+
def to_bool(bool_: Sentinel, /) -> Sentinel: ...
|
447
|
+
def to_bool(
|
448
|
+
bool_: MaybeCallableBoolLike | None | Sentinel, /
|
449
|
+
) -> bool | None | Sentinel:
|
450
|
+
"""Convert to a bool."""
|
451
|
+
match bool_:
|
452
|
+
case bool() | None | Sentinel():
|
453
|
+
return bool_
|
454
|
+
case str():
|
455
|
+
return parse_bool(bool_)
|
456
|
+
case Callable() as func:
|
457
|
+
return to_bool(func())
|
458
|
+
case never:
|
459
|
+
assert_never(never)
|
460
|
+
|
461
|
+
|
462
|
+
##
|
463
|
+
|
464
|
+
|
465
|
+
@overload
|
466
|
+
def to_str(text: MaybeCallableStr, /) -> str: ...
|
467
|
+
@overload
|
468
|
+
def to_str(text: None, /) -> None: ...
|
469
|
+
@overload
|
470
|
+
def to_str(text: Sentinel, /) -> Sentinel: ...
|
471
|
+
def to_str(text: MaybeCallableStr | None | Sentinel, /) -> str | None | Sentinel:
|
472
|
+
"""Convert to a string."""
|
473
|
+
match text:
|
474
|
+
case str() | None | Sentinel():
|
475
|
+
return text
|
476
|
+
case Callable() as func:
|
477
|
+
return to_str(func())
|
478
|
+
case never:
|
479
|
+
assert_never(never)
|
480
|
+
|
481
|
+
|
482
|
+
##
|
483
|
+
|
484
|
+
|
431
485
|
def unique_str() -> str:
|
432
486
|
"""Generate at unique string."""
|
433
487
|
now = time_ns()
|
@@ -456,5 +510,7 @@ __all__ = [
|
|
456
510
|
"split_str",
|
457
511
|
"str_encode",
|
458
512
|
"strip_and_dedent",
|
513
|
+
"to_bool",
|
514
|
+
"to_str",
|
459
515
|
"unique_str",
|
460
516
|
]
|
utilities/traceback.py
CHANGED
@@ -15,9 +15,8 @@ from typing import TYPE_CHECKING, override
|
|
15
15
|
|
16
16
|
from utilities.atomicwrites import writer
|
17
17
|
from utilities.errors import repr_error
|
18
|
-
from utilities.functions import to_bool
|
19
18
|
from utilities.iterables import OneEmptyError, one
|
20
|
-
from utilities.pathlib import
|
19
|
+
from utilities.pathlib import module_path, to_path
|
21
20
|
from utilities.reprlib import (
|
22
21
|
RICH_EXPAND_ALL,
|
23
22
|
RICH_INDENT_SIZE,
|
@@ -27,8 +26,9 @@ from utilities.reprlib import (
|
|
27
26
|
RICH_MAX_WIDTH,
|
28
27
|
yield_mapping_repr,
|
29
28
|
)
|
29
|
+
from utilities.text import to_bool
|
30
30
|
from utilities.tzlocal import LOCAL_TIME_ZONE_NAME
|
31
|
-
from utilities.version import
|
31
|
+
from utilities.version import to_version
|
32
32
|
from utilities.whenever import (
|
33
33
|
format_compact,
|
34
34
|
get_now,
|
@@ -43,9 +43,9 @@ if TYPE_CHECKING:
|
|
43
43
|
from types import TracebackType
|
44
44
|
|
45
45
|
from utilities.types import (
|
46
|
-
|
46
|
+
MaybeCallableBoolLike,
|
47
47
|
MaybeCallablePathLike,
|
48
|
-
|
48
|
+
MaybeCallableZonedDateTimeLike,
|
49
49
|
PathLike,
|
50
50
|
)
|
51
51
|
from utilities.version import MaybeCallableVersionLike
|
@@ -54,15 +54,12 @@ if TYPE_CHECKING:
|
|
54
54
|
##
|
55
55
|
|
56
56
|
|
57
|
-
_START = get_now()
|
58
|
-
|
59
|
-
|
60
57
|
def format_exception_stack(
|
61
58
|
error: BaseException,
|
62
59
|
/,
|
63
60
|
*,
|
64
61
|
header: bool = False,
|
65
|
-
start:
|
62
|
+
start: MaybeCallableZonedDateTimeLike = get_now,
|
66
63
|
version: MaybeCallableVersionLike | None = None,
|
67
64
|
capture_locals: bool = False,
|
68
65
|
max_width: int = RICH_MAX_WIDTH,
|
@@ -93,12 +90,12 @@ def format_exception_stack(
|
|
93
90
|
|
94
91
|
def _yield_header_lines(
|
95
92
|
*,
|
96
|
-
start:
|
93
|
+
start: MaybeCallableZonedDateTimeLike = get_now,
|
97
94
|
version: MaybeCallableVersionLike | None = None,
|
98
95
|
) -> Iterator[str]:
|
99
96
|
"""Yield the header lines."""
|
100
97
|
now = get_now_local()
|
101
|
-
start_use = to_zoned_date_time(
|
98
|
+
start_use = to_zoned_date_time(start)
|
102
99
|
yield f"Date/time | {format_compact(now)}"
|
103
100
|
if start_use is None:
|
104
101
|
start_str = ""
|
@@ -111,7 +108,7 @@ def _yield_header_lines(
|
|
111
108
|
yield f"User | {getuser()}"
|
112
109
|
yield f"Host | {gethostname()}"
|
113
110
|
yield f"Process ID | {getpid()}"
|
114
|
-
version_use = "" if version is None else
|
111
|
+
version_use = "" if version is None else to_version(version)
|
115
112
|
yield f"Version | {version_use}"
|
116
113
|
yield ""
|
117
114
|
|
@@ -205,7 +202,7 @@ def _trim_path(path: PathLike, pattern: str, /) -> Path | None:
|
|
205
202
|
|
206
203
|
def make_except_hook(
|
207
204
|
*,
|
208
|
-
start:
|
205
|
+
start: MaybeCallableZonedDateTimeLike = get_now,
|
209
206
|
version: MaybeCallableVersionLike | None = None,
|
210
207
|
path: MaybeCallablePathLike | None = None,
|
211
208
|
max_width: int = RICH_MAX_WIDTH,
|
@@ -215,7 +212,7 @@ def make_except_hook(
|
|
215
212
|
max_depth: int | None = RICH_MAX_DEPTH,
|
216
213
|
expand_all: bool = RICH_EXPAND_ALL,
|
217
214
|
slack_url: str | None = None,
|
218
|
-
pudb:
|
215
|
+
pudb: MaybeCallableBoolLike = False,
|
219
216
|
) -> Callable[
|
220
217
|
[type[BaseException] | None, BaseException | None, TracebackType | None], None
|
221
218
|
]:
|
@@ -242,7 +239,7 @@ def _make_except_hook_inner(
|
|
242
239
|
traceback: TracebackType | None,
|
243
240
|
/,
|
244
241
|
*,
|
245
|
-
start:
|
242
|
+
start: MaybeCallableZonedDateTimeLike = get_now,
|
246
243
|
version: MaybeCallableVersionLike | None = None,
|
247
244
|
path: MaybeCallablePathLike | None = None,
|
248
245
|
max_width: int = RICH_MAX_WIDTH,
|
@@ -252,7 +249,7 @@ def _make_except_hook_inner(
|
|
252
249
|
max_depth: int | None = RICH_MAX_DEPTH,
|
253
250
|
expand_all: bool = RICH_EXPAND_ALL,
|
254
251
|
slack_url: str | None = None,
|
255
|
-
pudb:
|
252
|
+
pudb: MaybeCallableBoolLike = False,
|
256
253
|
) -> None:
|
257
254
|
"""Exception hook to log the traceback."""
|
258
255
|
_ = (exc_type, traceback)
|
@@ -262,7 +259,7 @@ def _make_except_hook_inner(
|
|
262
259
|
_ = sys.stderr.write(f"{slim}\n") # don't 'from sys import stderr'
|
263
260
|
if path is not None:
|
264
261
|
path = (
|
265
|
-
|
262
|
+
to_path(path)
|
266
263
|
.joinpath(format_compact(to_local_plain(get_now())))
|
267
264
|
.with_suffix(".txt")
|
268
265
|
)
|
@@ -289,7 +286,7 @@ def _make_except_hook_inner(
|
|
289
286
|
except SendToSlackError as error:
|
290
287
|
_ = stderr.write(f"{error}\n")
|
291
288
|
|
292
|
-
if to_bool(
|
289
|
+
if to_bool(pudb): # pragma: no cover
|
293
290
|
from pudb import post_mortem
|
294
291
|
|
295
292
|
post_mortem(tb=traceback, e_type=exc_type, e_value=exc_val)
|
utilities/typed_settings.py
CHANGED
@@ -26,7 +26,7 @@ from whenever import (
|
|
26
26
|
)
|
27
27
|
|
28
28
|
from utilities.iterables import always_iterable
|
29
|
-
from utilities.pathlib import
|
29
|
+
from utilities.pathlib import to_path
|
30
30
|
from utilities.string import substitute_environ
|
31
31
|
|
32
32
|
if TYPE_CHECKING:
|
@@ -86,12 +86,12 @@ def _make_converter[T](
|
|
86
86
|
|
87
87
|
|
88
88
|
def _parse_path(
|
89
|
-
path: str, /, *, resolve: bool = False, pwd: MaybeCallablePathLike
|
89
|
+
path: str, /, *, resolve: bool = False, pwd: MaybeCallablePathLike = Path.cwd
|
90
90
|
) -> Path:
|
91
91
|
path = substitute_environ(path, **environ)
|
92
92
|
match resolve:
|
93
93
|
case True:
|
94
|
-
return
|
94
|
+
return to_path(pwd).joinpath(path).resolve()
|
95
95
|
case False:
|
96
96
|
return Path(path)
|
97
97
|
case never:
|
utilities/types.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import datetime as dt
|
4
|
-
from asyncio import Event
|
5
4
|
from collections.abc import Callable, Collection, Coroutine, Iterable, Mapping
|
6
5
|
from enum import Enum
|
7
6
|
from ipaddress import IPv4Address, IPv6Address
|
@@ -19,6 +18,7 @@ from typing import (
|
|
19
18
|
overload,
|
20
19
|
runtime_checkable,
|
21
20
|
)
|
21
|
+
from uuid import UUID
|
22
22
|
from zoneinfo import ZoneInfo
|
23
23
|
|
24
24
|
from whenever import (
|
@@ -39,6 +39,7 @@ _T_contra = TypeVar("_T_contra", contravariant=True)
|
|
39
39
|
|
40
40
|
|
41
41
|
# basic
|
42
|
+
type BoolLike = MaybeStr[bool]
|
42
43
|
type OpenMode = Literal[
|
43
44
|
"r",
|
44
45
|
"w",
|
@@ -62,8 +63,7 @@ type OpenMode = Literal[
|
|
62
63
|
"a+b",
|
63
64
|
]
|
64
65
|
type MaybeCallable[T] = T | Callable[[], T]
|
65
|
-
type
|
66
|
-
type MaybeCallableStr = MaybeCallable[str]
|
66
|
+
type MaybeCallableBoolLike = MaybeCallable[BoolLike]
|
67
67
|
type MaybeStr[T] = T | str
|
68
68
|
type MaybeType[T] = T | type[T]
|
69
69
|
type StrMapping = Mapping[str, Any]
|
@@ -74,7 +74,6 @@ type TupleOrStrMapping = tuple[Any, ...] | StrMapping
|
|
74
74
|
|
75
75
|
# asyncio
|
76
76
|
type Coro[T] = Coroutine[Any, Any, T]
|
77
|
-
type MaybeCallableEvent = MaybeCallable[Event]
|
78
77
|
type MaybeCoro[T] = T | Coro[T]
|
79
78
|
|
80
79
|
|
@@ -115,10 +114,11 @@ IPv6AddressLike = MaybeStr[IPv6Address]
|
|
115
114
|
|
116
115
|
|
117
116
|
# iterables
|
117
|
+
type SequenceLT[T] = list[T] | tuple[T, ...]
|
118
|
+
# iterables - maybe
|
118
119
|
type MaybeCollection[T] = T | Collection[T]
|
119
120
|
type MaybeIterable[T] = T | Iterable[T]
|
120
121
|
type MaybeSequence[T] = T | SequenceLT[T]
|
121
|
-
type SequenceLT[T] = list[T] | tuple[T, ...]
|
122
122
|
# iterables - str
|
123
123
|
type SequenceStr = SequenceLT[str]
|
124
124
|
type CollectionStr = dict[str, Any] | frozenset[str] | set[str] | SequenceStr
|
@@ -129,7 +129,7 @@ type MaybeSequenceStr = str | SequenceStr
|
|
129
129
|
|
130
130
|
# logging
|
131
131
|
type LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
132
|
-
type
|
132
|
+
type LoggerLike = MaybeStr[Logger]
|
133
133
|
|
134
134
|
|
135
135
|
# math
|
@@ -213,8 +213,8 @@ type SerializeObjectExtra = Mapping[Any, Callable[[Any], str]]
|
|
213
213
|
|
214
214
|
|
215
215
|
# pathlib
|
216
|
-
type MaybeCallablePathLike = MaybeCallable[PathLike]
|
217
216
|
type PathLike = MaybeStr[Path]
|
217
|
+
type MaybeCallablePathLike = MaybeCallable[PathLike]
|
218
218
|
|
219
219
|
|
220
220
|
# random
|
@@ -225,11 +225,20 @@ type Seed = int | float | str | bytes | bytearray | Random
|
|
225
225
|
type PatternLike = MaybeStr[Pattern[str]]
|
226
226
|
|
227
227
|
|
228
|
+
# text
|
229
|
+
type MaybeCallableStr = MaybeCallable[str]
|
230
|
+
|
231
|
+
|
228
232
|
# traceback
|
229
233
|
type ExcInfo = tuple[type[BaseException], BaseException, TracebackType]
|
230
234
|
type OptExcInfo = ExcInfo | tuple[None, None, None]
|
231
235
|
|
232
236
|
|
237
|
+
# uuid
|
238
|
+
type UUIDLike = MaybeStr[UUID]
|
239
|
+
type MaybeCallableUUIDLike = MaybeCallable[UUIDLike | Seed]
|
240
|
+
|
241
|
+
|
233
242
|
# whenever
|
234
243
|
type DateDeltaLike = MaybeStr[DateDelta]
|
235
244
|
type DateLike = MaybeStr[Date]
|
@@ -239,8 +248,8 @@ type DateTimeRoundMode = Literal[
|
|
239
248
|
"ceil", "floor", "half_ceil", "half_floor", "half_even"
|
240
249
|
]
|
241
250
|
type Delta = DateDelta | TimeDelta | DateTimeDelta
|
242
|
-
type
|
243
|
-
type
|
251
|
+
type MaybeCallableDateLike = MaybeCallable[DateLike]
|
252
|
+
type MaybeCallableZonedDateTimeLike = MaybeCallable[ZonedDateTimeLike]
|
244
253
|
type MonthDayLike = MaybeStr[MonthDay]
|
245
254
|
type PlainDateTimeLike = MaybeStr[PlainDateTime]
|
246
255
|
type TimeDeltaLike = MaybeStr[TimeDelta]
|
@@ -277,15 +286,15 @@ __all__ = [
|
|
277
286
|
"IPv4AddressLike",
|
278
287
|
"IPv6AddressLike",
|
279
288
|
"LogLevel",
|
280
|
-
"
|
289
|
+
"LoggerLike",
|
281
290
|
"MathRoundMode",
|
282
291
|
"MaybeCallable",
|
283
|
-
"
|
284
|
-
"
|
285
|
-
"MaybeCallableEvent",
|
292
|
+
"MaybeCallableBoolLike",
|
293
|
+
"MaybeCallableDateLike",
|
286
294
|
"MaybeCallablePathLike",
|
287
295
|
"MaybeCallableStr",
|
288
|
-
"
|
296
|
+
"MaybeCallableUUIDLike",
|
297
|
+
"MaybeCallableZonedDateTimeLike",
|
289
298
|
"MaybeCollection",
|
290
299
|
"MaybeCollectionStr",
|
291
300
|
"MaybeCoro",
|
@@ -329,6 +338,7 @@ __all__ = [
|
|
329
338
|
"TimeZoneLike",
|
330
339
|
"TupleOrStrMapping",
|
331
340
|
"TypeLike",
|
341
|
+
"UUIDLike",
|
332
342
|
"WeekDay",
|
333
343
|
"YearMonthLike",
|
334
344
|
"ZonedDateTimeLike",
|