dycw-utilities 0.153.14__py3-none-any.whl → 0.154.0__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.153.14.dist-info → dycw_utilities-0.154.0.dist-info}/METADATA +1 -1
- {dycw_utilities-0.153.14.dist-info → dycw_utilities-0.154.0.dist-info}/RECORD +13 -13
- utilities/__init__.py +1 -1
- utilities/fpdf2.py +2 -2
- utilities/iterables.py +1 -21
- utilities/logging.py +8 -49
- utilities/polars.py +2 -1
- utilities/pyinstrument.py +2 -4
- utilities/traceback.py +24 -16
- utilities/whenever.py +8 -4
- {dycw_utilities-0.153.14.dist-info → dycw_utilities-0.154.0.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.153.14.dist-info → dycw_utilities-0.154.0.dist-info}/entry_points.txt +0 -0
- {dycw_utilities-0.153.14.dist-info → dycw_utilities-0.154.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=AggdQNmHiytWszaBPt8g3NE_WaSx4IoGrhZ8f-NLdAI,60
|
2
2
|
utilities/altair.py,sha256=92E2lCdyHY4Zb-vCw6rEJIsWdKipuu-Tu2ab1ufUfAk,9079
|
3
3
|
utilities/asyncio.py,sha256=QXkTtugXkqtYt7Do23zgYErqzdp6jwzPpV_SP9fJ1gI,16780
|
4
4
|
utilities/atomicwrites.py,sha256=tPo6r-Rypd9u99u66B9z86YBPpnLrlHtwox_8Z7T34Y,5790
|
@@ -15,7 +15,7 @@ utilities/enum.py,sha256=5l6pwZD1cjSlVW4ss-zBPspWvrbrYrdtJWcg6f5_J5w,5781
|
|
15
15
|
utilities/errors.py,sha256=mFlDGSM0LI1jZ1pbqwLAH3ttLZ2JVIxyZLojw8tGVZU,1479
|
16
16
|
utilities/eventkit.py,sha256=ddoleSwW9zdc2tjX5Ge0pMKtYwV_JMxhHYOxnWX2AGM,12609
|
17
17
|
utilities/fastapi.py,sha256=3wpd63Tw9paSyy7STpAD7GGe8fLkLaRC6TPCwIGm1BU,1361
|
18
|
-
utilities/fpdf2.py,sha256=
|
18
|
+
utilities/fpdf2.py,sha256=HgM8JSvoioDXrjC0UR3HVLjnMnnb_mML7nL2EmkTwGI,1854
|
19
19
|
utilities/functions.py,sha256=0mmeZ8op3QkAooYRAyRZhpi3TgaJCiMnqbJtZl-myug,28266
|
20
20
|
utilities/functools.py,sha256=I00ru2gQPakZw2SHVeKIKXfTv741655s6HI0lUoE0D4,1552
|
21
21
|
utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
|
@@ -26,12 +26,12 @@ utilities/hypothesis.py,sha256=m44niSfuzuhgn7IQ1UOwUGgiu68xz4a6LHxB0IE6fNE,40341
|
|
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
|
29
|
-
utilities/iterables.py,sha256=
|
29
|
+
utilities/iterables.py,sha256=ZmXBSk_Rio-aqLwTaoX69HD81YVcndeLYQwjv0P64JM,43009
|
30
30
|
utilities/json.py,sha256=-WcGtSsCr9Y42wHZzAMnfvU6ihAfVftylFfRUORaDFo,2102
|
31
31
|
utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
|
32
32
|
utilities/libcst.py,sha256=TKgKN4bNmtBNEE-TUfhTyd1BrTncfsl_7tTuhpesGYY,5585
|
33
33
|
utilities/lightweight_charts.py,sha256=YM3ojBvJxuCSUBu_KrhFBmaMCvRPvupKC3qkm-UVZq4,2751
|
34
|
-
utilities/logging.py,sha256=
|
34
|
+
utilities/logging.py,sha256=ihbfQJgjc7t3Pds0oPvF_J1eigiqFKzxNOijzoee8U4,18064
|
35
35
|
utilities/math.py,sha256=7ve4RxX3g-FGGVnWV0K9bBeGnKUEjnTbH13VxdvFtGE,26847
|
36
36
|
utilities/memory_profiler.py,sha256=XzN56jDCa5aqXS_DxEjb_K4L6aIWh_5zyKi6OhcIxw0,853
|
37
37
|
utilities/modules.py,sha256=iuvLluJya-hvl1Q25-Jk3dLgx2Es3ck4SjJiEkAlVTs,3195
|
@@ -45,14 +45,14 @@ utilities/parse.py,sha256=JcJn5yXKhIWXBCwgBdPsyu7Hvcuw6kyEdqvaebCaI9k,17951
|
|
45
45
|
utilities/pathlib.py,sha256=qGuU8XPmdgGpy8tOMUgelfXx3kxI8h9IaV3TI_06QGE,8428
|
46
46
|
utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
|
47
47
|
utilities/platform.py,sha256=pTn7gw6N4T6LdKrf0virwarof_mze9WtoQlrGMzhGVI,2798
|
48
|
-
utilities/polars.py,sha256=
|
48
|
+
utilities/polars.py,sha256=I07Bk_Vp2T434qXkCKxSVQIkFJc1d8YkOH48fprypB0,78436
|
49
49
|
utilities/polars_ols.py,sha256=Uc9V5kvlWZ5cU93lKZ-cfAKdVFFw81tqwLW9PxtUvMs,5618
|
50
50
|
utilities/postgres.py,sha256=ynCTTaF-bVEOSW-KEAR-dlLh_hYjeVVjm__-4pEU8Zk,12269
|
51
51
|
utilities/pottery.py,sha256=HJ96oLRarTP37Vhg0WTyB3yAu2hETeg6HgRmpDIqyUs,6581
|
52
52
|
utilities/pqdm.py,sha256=z8bSMS7QJmWun65FQZruAqT-R3wqPAzNzhWcX9Nvr0A,3087
|
53
53
|
utilities/psutil.py,sha256=KUlu4lrUw9Zg1V7ZGetpWpGb9DB8l_SSDWGbANFNCPU,2104
|
54
54
|
utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
|
-
utilities/pyinstrument.py,sha256=
|
55
|
+
utilities/pyinstrument.py,sha256=NZCZz2nBo0BLJ9DTf7H_Q_KGxvsf2S2M3h0qYoYh2kw,804
|
56
56
|
utilities/pytest.py,sha256=2HHfAWkzZeK2OAzL2F49EDKooMkfDoGqg8Ev4cHC_N8,7869
|
57
57
|
utilities/pytest_regressions.py,sha256=ocjHTtfOeiGfQAKIei8pKNd61sxN9dawrJJ9gPt2wzA,4097
|
58
58
|
utilities/random.py,sha256=hZlH4gnAtoaofWswuJYjcygejrY8db4CzP-z_adO2Mo,4165
|
@@ -72,7 +72,7 @@ utilities/tempfile.py,sha256=HxB2BF28CcecDJLQ3Bx2Ej-Pb6RJc6W9ngSpB9CnP4k,2018
|
|
72
72
|
utilities/text.py,sha256=uwCDgpEunYruyh6sKMfNWK3Rp5H3ndpKRAkq86CBNys,13043
|
73
73
|
utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
|
74
74
|
utilities/timer.py,sha256=oXfTii6ymu57niP0BDGZjFD55LEHi2a19kqZKiTgaFQ,2588
|
75
|
-
utilities/traceback.py,sha256=
|
75
|
+
utilities/traceback.py,sha256=TjO7em98FDFLvROZ7gi2UJftFWNuSTkbCrf7mk-fg28,9416
|
76
76
|
utilities/typed_settings.py,sha256=SFWqS3lAzV7IfNRwqFcTk0YynTcQ7BmrcW2mr_KUnos,4466
|
77
77
|
utilities/types.py,sha256=L4cjFPyFZX58Urfw0S_i-XRywPIFyuSLOieewj0qqsM,18516
|
78
78
|
utilities/typing.py,sha256=Z-_XDaWyT_6wIo3qfNK-hvRlzxP2Jxa9PgXzm5rDYRA,13790
|
@@ -81,14 +81,14 @@ utilities/tzlocal.py,sha256=KyCXEgCTjqGFx-389JdTuhMRUaT06U1RCMdWoED-qro,728
|
|
81
81
|
utilities/uuid.py,sha256=nQZs6tFX4mqtc2Ku3KqjloYCqwpTKeTj8eKwQwh3FQI,1572
|
82
82
|
utilities/version.py,sha256=ipBj5-WYY_nelp2uwFlApfWWCzTLzPwpovUi9x_OBMs,5085
|
83
83
|
utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
84
|
-
utilities/whenever.py,sha256=
|
84
|
+
utilities/whenever.py,sha256=gPnFKWws4_tjiHPLzX1AukSwDjfMIO9Iim0DDNQyAqY,57532
|
85
85
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
86
86
|
utilities/zoneinfo.py,sha256=FBMcUQ4662Aq8SsuCL1OAhDQiyANmVjtb-C30DRrWoE,1966
|
87
87
|
utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
88
88
|
utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
|
89
89
|
utilities/pytest_plugins/pytest_regressions.py,sha256=9v8kAXDM2ycIXJBimoiF4EgrwbUvxTycFWJiGR_GHhM,1466
|
90
|
-
dycw_utilities-0.
|
91
|
-
dycw_utilities-0.
|
92
|
-
dycw_utilities-0.
|
93
|
-
dycw_utilities-0.
|
94
|
-
dycw_utilities-0.
|
90
|
+
dycw_utilities-0.154.0.dist-info/METADATA,sha256=34hYZf8Cdia0JBt2zwn4AaQY6m0S7qY5VfKXDDjw6OY,1696
|
91
|
+
dycw_utilities-0.154.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
92
|
+
dycw_utilities-0.154.0.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
|
93
|
+
dycw_utilities-0.154.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
94
|
+
dycw_utilities-0.154.0.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/fpdf2.py
CHANGED
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, override
|
|
6
6
|
from fpdf import FPDF
|
7
7
|
from fpdf.enums import XPos, YPos
|
8
8
|
|
9
|
-
from utilities.whenever import
|
9
|
+
from utilities.whenever import get_now, to_local_plain
|
10
10
|
|
11
11
|
if TYPE_CHECKING:
|
12
12
|
from collections.abc import Iterator
|
@@ -47,7 +47,7 @@ def yield_pdf(*, header: str | None = None) -> Iterator[_BasePDF]:
|
|
47
47
|
def footer(self) -> None:
|
48
48
|
self.set_y(-15)
|
49
49
|
self.set_font(family="Helvetica", style="I", size=8)
|
50
|
-
page_no, now = (self.page_no(),
|
50
|
+
page_no, now = (self.page_no(), to_local_plain(get_now()))
|
51
51
|
text = f"page {page_no}/{{}}; {now}"
|
52
52
|
_ = self.cell(
|
53
53
|
w=0,
|
utilities/iterables.py
CHANGED
@@ -18,7 +18,7 @@ from enum import Enum
|
|
18
18
|
from functools import cmp_to_key, partial, reduce
|
19
19
|
from itertools import accumulate, chain, groupby, islice, pairwise, product
|
20
20
|
from math import isnan
|
21
|
-
from operator import add,
|
21
|
+
from operator import add, or_
|
22
22
|
from typing import (
|
23
23
|
TYPE_CHECKING,
|
24
24
|
Any,
|
@@ -821,24 +821,6 @@ def filter_include_and_exclude[T, U](
|
|
821
821
|
##
|
822
822
|
|
823
823
|
|
824
|
-
def group_consecutive_integers(iterable: Iterable[int], /) -> Iterable[tuple[int, int]]:
|
825
|
-
"""Group consecutive integers."""
|
826
|
-
integers = sorted(iterable)
|
827
|
-
for _, group in groupby(enumerate(integers), key=lambda x: x[1] - x[0]):
|
828
|
-
as_list = list(map(itemgetter(1), group))
|
829
|
-
yield as_list[0], as_list[-1]
|
830
|
-
|
831
|
-
|
832
|
-
def ungroup_consecutive_integers(
|
833
|
-
iterable: Iterable[tuple[int, int]], /
|
834
|
-
) -> Iterable[int]:
|
835
|
-
"""Ungroup consecutive integers."""
|
836
|
-
return chain.from_iterable(range(start, end + 1) for start, end in iterable)
|
837
|
-
|
838
|
-
|
839
|
-
##
|
840
|
-
|
841
|
-
|
842
824
|
@overload
|
843
825
|
def groupby_lists[T](
|
844
826
|
iterable: Iterable[T], /, *, key: None = None
|
@@ -1504,7 +1486,6 @@ __all__ = [
|
|
1504
1486
|
"enumerate_with_edge",
|
1505
1487
|
"expanding_window",
|
1506
1488
|
"filter_include_and_exclude",
|
1507
|
-
"group_consecutive_integers",
|
1508
1489
|
"groupby_lists",
|
1509
1490
|
"hashable_to_iterable",
|
1510
1491
|
"is_iterable",
|
@@ -1527,6 +1508,5 @@ __all__ = [
|
|
1527
1508
|
"sum_mappings",
|
1528
1509
|
"take",
|
1529
1510
|
"transpose",
|
1530
|
-
"ungroup_consecutive_integers",
|
1531
1511
|
"unique_everseen",
|
1532
1512
|
]
|
utilities/logging.py
CHANGED
@@ -31,7 +31,7 @@ from typing import (
|
|
31
31
|
override,
|
32
32
|
)
|
33
33
|
|
34
|
-
from whenever import
|
34
|
+
from whenever import ZonedDateTime
|
35
35
|
|
36
36
|
from utilities.atomicwrites import move_many
|
37
37
|
from utilities.dataclasses import replace_non_sentinel
|
@@ -45,16 +45,15 @@ from utilities.re import (
|
|
45
45
|
extract_groups,
|
46
46
|
)
|
47
47
|
from utilities.sentinel import Sentinel, sentinel
|
48
|
-
from utilities.tzlocal import LOCAL_TIME_ZONE_NAME
|
49
48
|
from utilities.whenever import (
|
50
49
|
WheneverLogRecord,
|
51
|
-
format_compact,
|
52
50
|
get_now_local,
|
51
|
+
parse_plain_local,
|
53
52
|
to_local_plain,
|
54
53
|
)
|
55
54
|
|
56
55
|
if TYPE_CHECKING:
|
57
|
-
from collections.abc import
|
56
|
+
from collections.abc import Iterable, Mapping
|
58
57
|
from datetime import time
|
59
58
|
from logging import _FilterType
|
60
59
|
|
@@ -151,42 +150,6 @@ def basic_config(
|
|
151
150
|
##
|
152
151
|
|
153
152
|
|
154
|
-
def filter_for_key(
|
155
|
-
key: str, /, *, default: bool = False
|
156
|
-
) -> Callable[[LogRecord], bool]:
|
157
|
-
"""Make a filter for a given attribute."""
|
158
|
-
if (key in _FILTER_FOR_KEY_BLACKLIST) or key.startswith("__"):
|
159
|
-
raise FilterForKeyError(key=key)
|
160
|
-
|
161
|
-
def filter_(record: LogRecord, /) -> bool:
|
162
|
-
try:
|
163
|
-
value = getattr(record, key)
|
164
|
-
except AttributeError:
|
165
|
-
return default
|
166
|
-
return bool(value)
|
167
|
-
|
168
|
-
return filter_
|
169
|
-
|
170
|
-
|
171
|
-
# fmt: off
|
172
|
-
_FILTER_FOR_KEY_BLACKLIST = {
|
173
|
-
"args", "created", "exc_info", "exc_text", "filename", "funcName", "getMessage", "levelname", "levelno", "lineno", "module", "msecs", "msg", "name", "pathname", "process", "processName", "relativeCreated", "stack_info", "taskName", "thread", "threadName"
|
174
|
-
}
|
175
|
-
# fmt: on
|
176
|
-
|
177
|
-
|
178
|
-
@dataclass(kw_only=True, slots=True)
|
179
|
-
class FilterForKeyError(Exception):
|
180
|
-
key: str
|
181
|
-
|
182
|
-
@override
|
183
|
-
def __str__(self) -> str:
|
184
|
-
return f"Invalid key: {self.key!r}"
|
185
|
-
|
186
|
-
|
187
|
-
##
|
188
|
-
|
189
|
-
|
190
153
|
def get_format_str(*, prefix: str | None = None, hostname: bool = False) -> str:
|
191
154
|
"""Generate a format string."""
|
192
155
|
parts: list[str] = [
|
@@ -535,10 +498,8 @@ class _RotatingLogFile:
|
|
535
498
|
stem=stem,
|
536
499
|
suffix=suffix,
|
537
500
|
index=int(index),
|
538
|
-
start=
|
539
|
-
|
540
|
-
),
|
541
|
-
end=PlainDateTime.parse_common_iso(end).assume_tz(LOCAL_TIME_ZONE_NAME),
|
501
|
+
start=parse_plain_local(start),
|
502
|
+
end=parse_plain_local(end),
|
542
503
|
)
|
543
504
|
try:
|
544
505
|
index, end = extract_groups(patterns.pattern2, path.name)
|
@@ -550,7 +511,7 @@ class _RotatingLogFile:
|
|
550
511
|
stem=stem,
|
551
512
|
suffix=suffix,
|
552
513
|
index=int(index),
|
553
|
-
end=
|
514
|
+
end=parse_plain_local(end),
|
554
515
|
)
|
555
516
|
try:
|
556
517
|
index = extract_group(patterns.pattern1, path.name)
|
@@ -571,9 +532,9 @@ class _RotatingLogFile:
|
|
571
532
|
case int() as index, None, None:
|
572
533
|
tail = str(index)
|
573
534
|
case int() as index, None, ZonedDateTime() as end:
|
574
|
-
tail = f"{index}__{
|
535
|
+
tail = f"{index}__{to_local_plain(end)}"
|
575
536
|
case int() as index, ZonedDateTime() as start, ZonedDateTime() as end:
|
576
|
-
tail = f"{index}__{
|
537
|
+
tail = f"{index}__{to_local_plain(start)}__{to_local_plain(end)}"
|
577
538
|
case _: # pragma: no cover
|
578
539
|
raise ImpossibleCaseError(
|
579
540
|
case=[f"{self.index=}", f"{self.start=}", f"{self.end=}"]
|
@@ -626,12 +587,10 @@ def to_logger(logger: LoggerLike | None = None, /) -> Logger:
|
|
626
587
|
|
627
588
|
|
628
589
|
__all__ = [
|
629
|
-
"FilterForKeyError",
|
630
590
|
"GetLoggingLevelNumberError",
|
631
591
|
"SizeAndTimeRotatingFileHandler",
|
632
592
|
"add_filters",
|
633
593
|
"basic_config",
|
634
|
-
"filter_for_key",
|
635
594
|
"get_format_str",
|
636
595
|
"get_logging_level_number",
|
637
596
|
"setup_logging",
|
utilities/polars.py
CHANGED
@@ -2383,7 +2383,8 @@ def round_to_float(
|
|
2383
2383
|
) -> ExprOrSeries:
|
2384
2384
|
"""Round a column to the nearest multiple of another float."""
|
2385
2385
|
x = ensure_expr_or_series(x)
|
2386
|
-
|
2386
|
+
z = (x / y).round(mode=mode) * y
|
2387
|
+
return z.round(decimals=number_of_decimals(y) + 1)
|
2387
2388
|
|
2388
2389
|
|
2389
2390
|
##
|
utilities/pyinstrument.py
CHANGED
@@ -8,7 +8,7 @@ from pyinstrument.profiler import Profiler
|
|
8
8
|
|
9
9
|
from utilities.atomicwrites import writer
|
10
10
|
from utilities.pathlib import to_path
|
11
|
-
from utilities.whenever import
|
11
|
+
from utilities.whenever import get_now, to_local_plain
|
12
12
|
|
13
13
|
if TYPE_CHECKING:
|
14
14
|
from collections.abc import Iterator
|
@@ -21,9 +21,7 @@ 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 = to_path(path).joinpath(
|
25
|
-
f"profile__{format_compact(to_local_plain(get_now()))}.html"
|
26
|
-
)
|
24
|
+
filename = to_path(path).joinpath(f"profile__{to_local_plain(get_now())}.html")
|
27
25
|
with writer(filename) as temp:
|
28
26
|
_ = temp.write_text(profiler.output_html())
|
29
27
|
|
utilities/traceback.py
CHANGED
@@ -33,6 +33,7 @@ from utilities.whenever import (
|
|
33
33
|
format_compact,
|
34
34
|
get_now,
|
35
35
|
get_now_local,
|
36
|
+
parse_plain_local,
|
36
37
|
to_local_plain,
|
37
38
|
to_zoned_date_time,
|
38
39
|
)
|
@@ -43,6 +44,7 @@ if TYPE_CHECKING:
|
|
43
44
|
from types import TracebackType
|
44
45
|
|
45
46
|
from utilities.types import (
|
47
|
+
Delta,
|
46
48
|
MaybeCallableBoolLike,
|
47
49
|
MaybeCallablePathLike,
|
48
50
|
MaybeCallableZonedDateTimeLike,
|
@@ -95,16 +97,10 @@ def _yield_header_lines(
|
|
95
97
|
) -> Iterator[str]:
|
96
98
|
"""Yield the header lines."""
|
97
99
|
now = get_now_local()
|
98
|
-
start_use = to_zoned_date_time(start)
|
99
100
|
yield f"Date/time | {format_compact(now)}"
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
start_str = format_compact(start_use.to_tz(LOCAL_TIME_ZONE_NAME))
|
104
|
-
yield f"Started | {start_str}"
|
105
|
-
delta = None if start_use is None else (now - start_use)
|
106
|
-
delta_str = "" if delta is None else delta.format_common_iso()
|
107
|
-
yield f"Duration | {delta_str}"
|
101
|
+
start_use = to_zoned_date_time(start).to_tz(LOCAL_TIME_ZONE_NAME)
|
102
|
+
yield f"Started | {format_compact(start_use)}"
|
103
|
+
yield f"Duration | {(now - start_use).format_common_iso()}"
|
108
104
|
yield f"User | {getuser()}"
|
109
105
|
yield f"Host | {gethostname()}"
|
110
106
|
yield f"Process ID | {getpid()}"
|
@@ -205,6 +201,7 @@ def make_except_hook(
|
|
205
201
|
start: MaybeCallableZonedDateTimeLike = get_now,
|
206
202
|
version: MaybeCallableVersionLike | None = None,
|
207
203
|
path: MaybeCallablePathLike | None = None,
|
204
|
+
path_max_age: Delta | None = None,
|
208
205
|
max_width: int = RICH_MAX_WIDTH,
|
209
206
|
indent_size: int = RICH_INDENT_SIZE,
|
210
207
|
max_length: int | None = RICH_MAX_LENGTH,
|
@@ -222,6 +219,7 @@ def make_except_hook(
|
|
222
219
|
start=start,
|
223
220
|
version=version,
|
224
221
|
path=path,
|
222
|
+
path_max_age=path_max_age,
|
225
223
|
max_width=max_width,
|
226
224
|
indent_size=indent_size,
|
227
225
|
max_length=max_length,
|
@@ -242,6 +240,7 @@ def _make_except_hook_inner(
|
|
242
240
|
start: MaybeCallableZonedDateTimeLike = get_now,
|
243
241
|
version: MaybeCallableVersionLike | None = None,
|
244
242
|
path: MaybeCallablePathLike | None = None,
|
243
|
+
path_max_age: Delta | None = None,
|
245
244
|
max_width: int = RICH_MAX_WIDTH,
|
246
245
|
indent_size: int = RICH_INDENT_SIZE,
|
247
246
|
max_length: int | None = RICH_MAX_LENGTH,
|
@@ -258,11 +257,8 @@ def _make_except_hook_inner(
|
|
258
257
|
slim = format_exception_stack(exc_val, header=True, start=start, version=version)
|
259
258
|
_ = sys.stderr.write(f"{slim}\n") # don't 'from sys import stderr'
|
260
259
|
if path is not None:
|
261
|
-
path = (
|
262
|
-
|
263
|
-
.joinpath(format_compact(to_local_plain(get_now())))
|
264
|
-
.with_suffix(".txt")
|
265
|
-
)
|
260
|
+
path = to_path(path)
|
261
|
+
path_log = path.joinpath(to_local_plain(get_now())).with_suffix(".txt")
|
266
262
|
full = format_exception_stack(
|
267
263
|
exc_val,
|
268
264
|
header=True,
|
@@ -276,8 +272,10 @@ def _make_except_hook_inner(
|
|
276
272
|
max_depth=max_depth,
|
277
273
|
expand_all=expand_all,
|
278
274
|
)
|
279
|
-
with writer(
|
275
|
+
with writer(path_log, overwrite=True) as temp:
|
280
276
|
_ = temp.write_text(full)
|
277
|
+
if path_max_age is not None:
|
278
|
+
_make_except_hook_purge(path, path_max_age)
|
281
279
|
if slack_url is not None: # pragma: no cover
|
282
280
|
from utilities.slack_sdk import SendToSlackError, send_to_slack
|
283
281
|
|
@@ -285,13 +283,23 @@ def _make_except_hook_inner(
|
|
285
283
|
send_to_slack(slack_url, f"```{slim}```")
|
286
284
|
except SendToSlackError as error:
|
287
285
|
_ = stderr.write(f"{error}\n")
|
288
|
-
|
289
286
|
if to_bool(pudb): # pragma: no cover
|
290
287
|
from pudb import post_mortem
|
291
288
|
|
292
289
|
post_mortem(tb=traceback, e_type=exc_type, e_value=exc_val)
|
293
290
|
|
294
291
|
|
292
|
+
def _make_except_hook_purge(path: PathLike, max_age: Delta, /) -> None:
|
293
|
+
threshold = get_now() - max_age
|
294
|
+
paths = {
|
295
|
+
p
|
296
|
+
for p in Path(path).iterdir()
|
297
|
+
if p.is_file() and (parse_plain_local(p.stem) <= threshold)
|
298
|
+
}
|
299
|
+
for p in paths:
|
300
|
+
p.unlink(missing_ok=True)
|
301
|
+
|
302
|
+
|
295
303
|
@dataclass(kw_only=True, slots=True)
|
296
304
|
class MakeExceptHookError(Exception):
|
297
305
|
@override
|
utilities/whenever.py
CHANGED
@@ -52,8 +52,6 @@ if TYPE_CHECKING:
|
|
52
52
|
TimeZoneLike,
|
53
53
|
)
|
54
54
|
|
55
|
-
# type vars
|
56
|
-
|
57
55
|
|
58
56
|
# bounds
|
59
57
|
|
@@ -1008,9 +1006,14 @@ class _ToHoursNanosecondsError(ToHoursError):
|
|
1008
1006
|
##
|
1009
1007
|
|
1010
1008
|
|
1011
|
-
def to_local_plain(date_time: ZonedDateTime, /) ->
|
1009
|
+
def to_local_plain(date_time: ZonedDateTime, /) -> str:
|
1012
1010
|
"""Convert a datetime to its local/plain variant."""
|
1013
|
-
return date_time.to_tz(LOCAL_TIME_ZONE_NAME).to_plain()
|
1011
|
+
return format_compact(date_time.to_tz(LOCAL_TIME_ZONE_NAME).to_plain())
|
1012
|
+
|
1013
|
+
|
1014
|
+
def parse_plain_local(text: str, /) -> ZonedDateTime:
|
1015
|
+
"""Parse a plain, local datetime."""
|
1016
|
+
return PlainDateTime.parse_common_iso(text).assume_tz(LOCAL_TIME_ZONE_NAME)
|
1014
1017
|
|
1015
1018
|
|
1016
1019
|
##
|
@@ -1967,6 +1970,7 @@ __all__ = [
|
|
1967
1970
|
"get_today_local",
|
1968
1971
|
"mean_datetime",
|
1969
1972
|
"min_max_date",
|
1973
|
+
"parse_plain_local",
|
1970
1974
|
"round_date_or_date_time",
|
1971
1975
|
"sub_year_month",
|
1972
1976
|
"to_date",
|
File without changes
|
File without changes
|
File without changes
|