dycw-utilities 0.175.17__py3-none-any.whl → 0.185.8__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.185.8.dist-info/METADATA +33 -0
- dycw_utilities-0.185.8.dist-info/RECORD +90 -0
- {dycw_utilities-0.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +2 -2
- utilities/__init__.py +1 -1
- utilities/altair.py +8 -6
- utilities/asyncio.py +40 -56
- utilities/atools.py +9 -11
- utilities/cachetools.py +8 -6
- utilities/click.py +4 -3
- utilities/concurrent.py +1 -1
- utilities/constants.py +492 -0
- utilities/contextlib.py +23 -30
- utilities/contextvars.py +1 -23
- utilities/core.py +2581 -0
- utilities/dataclasses.py +16 -119
- utilities/docker.py +139 -45
- utilities/enum.py +1 -1
- utilities/errors.py +2 -16
- utilities/fastapi.py +5 -5
- utilities/fpdf2.py +2 -1
- utilities/functions.py +33 -264
- utilities/http.py +2 -3
- utilities/hypothesis.py +48 -25
- utilities/iterables.py +39 -575
- utilities/jinja2.py +3 -6
- utilities/jupyter.py +5 -3
- utilities/libcst.py +1 -1
- utilities/lightweight_charts.py +4 -6
- utilities/logging.py +17 -15
- utilities/math.py +1 -36
- utilities/more_itertools.py +4 -6
- utilities/numpy.py +2 -1
- utilities/operator.py +2 -2
- utilities/orjson.py +24 -25
- utilities/os.py +4 -185
- utilities/packaging.py +129 -0
- utilities/parse.py +33 -13
- utilities/pathlib.py +2 -136
- utilities/platform.py +8 -90
- utilities/polars.py +34 -31
- utilities/postgres.py +9 -4
- utilities/pottery.py +20 -18
- utilities/pqdm.py +3 -4
- utilities/psutil.py +2 -3
- utilities/pydantic.py +18 -4
- utilities/pydantic_settings.py +7 -9
- utilities/pydantic_settings_sops.py +3 -3
- utilities/pyinstrument.py +4 -4
- utilities/pytest.py +49 -108
- utilities/pytest_plugins/pytest_regressions.py +2 -2
- utilities/pytest_regressions.py +8 -6
- utilities/random.py +2 -8
- utilities/redis.py +98 -94
- utilities/reprlib.py +11 -118
- utilities/shellingham.py +66 -0
- utilities/slack_sdk.py +13 -12
- utilities/sqlalchemy.py +42 -30
- utilities/sqlalchemy_polars.py +16 -25
- utilities/subprocess.py +1166 -148
- utilities/tabulate.py +32 -0
- utilities/testbook.py +8 -8
- utilities/text.py +24 -115
- utilities/throttle.py +159 -0
- utilities/time.py +18 -0
- utilities/timer.py +29 -12
- utilities/traceback.py +15 -22
- utilities/types.py +38 -3
- utilities/typing.py +18 -12
- utilities/uuid.py +1 -1
- utilities/version.py +202 -45
- utilities/whenever.py +22 -150
- dycw_utilities-0.175.17.dist-info/METADATA +0 -34
- dycw_utilities-0.175.17.dist-info/RECORD +0 -103
- utilities/atomicwrites.py +0 -182
- utilities/cryptography.py +0 -41
- utilities/getpass.py +0 -8
- utilities/git.py +0 -19
- utilities/grp.py +0 -28
- utilities/gzip.py +0 -31
- utilities/json.py +0 -70
- utilities/permissions.py +0 -298
- utilities/pickle.py +0 -25
- utilities/pwd.py +0 -28
- utilities/re.py +0 -156
- utilities/sentinel.py +0 -73
- utilities/socket.py +0 -8
- utilities/string.py +0 -20
- utilities/tempfile.py +0 -136
- utilities/tzdata.py +0 -11
- utilities/tzlocal.py +0 -28
- utilities/warnings.py +0 -65
- utilities/zipfile.py +0 -25
- utilities/zoneinfo.py +0 -133
- {dycw_utilities-0.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/entry_points.txt +0 -0
utilities/polars.py
CHANGED
|
@@ -54,25 +54,31 @@ from polars.testing import assert_frame_equal, assert_series_equal
|
|
|
54
54
|
from whenever import DateDelta, DateTimeDelta, PlainDateTime, TimeDelta, ZonedDateTime
|
|
55
55
|
|
|
56
56
|
import utilities.math
|
|
57
|
+
from utilities.constants import UTC
|
|
58
|
+
from utilities.core import (
|
|
59
|
+
OneEmptyError,
|
|
60
|
+
OneNonUniqueError,
|
|
61
|
+
always_iterable,
|
|
62
|
+
one,
|
|
63
|
+
read_bytes,
|
|
64
|
+
repr_,
|
|
65
|
+
suppress_warnings,
|
|
66
|
+
to_time_zone_name,
|
|
67
|
+
write_bytes,
|
|
68
|
+
)
|
|
57
69
|
from utilities.dataclasses import yield_fields
|
|
58
70
|
from utilities.errors import ImpossibleCaseError
|
|
59
71
|
from utilities.functions import get_class_name
|
|
60
|
-
from utilities.gzip import read_binary
|
|
61
72
|
from utilities.iterables import (
|
|
62
73
|
CheckIterablesEqualError,
|
|
63
74
|
CheckMappingsEqualError,
|
|
64
75
|
CheckSuperMappingError,
|
|
65
|
-
OneEmptyError,
|
|
66
|
-
OneNonUniqueError,
|
|
67
|
-
always_iterable,
|
|
68
76
|
check_iterables_equal,
|
|
69
77
|
check_mappings_equal,
|
|
70
78
|
check_supermapping,
|
|
71
79
|
is_iterable_not_str,
|
|
72
|
-
one,
|
|
73
80
|
resolve_include_and_exclude,
|
|
74
81
|
)
|
|
75
|
-
from utilities.json import write_formatted_json
|
|
76
82
|
from utilities.math import (
|
|
77
83
|
MAX_DECIMALS,
|
|
78
84
|
CheckIntegerError,
|
|
@@ -81,8 +87,7 @@ from utilities.math import (
|
|
|
81
87
|
is_less_than,
|
|
82
88
|
is_non_negative,
|
|
83
89
|
)
|
|
84
|
-
from utilities.
|
|
85
|
-
from utilities.types import MaybeStr, Number, PathLike, WeekDay
|
|
90
|
+
from utilities.types import MaybeStr, Number, PathLike, StrDict, WeekDay
|
|
86
91
|
from utilities.typing import (
|
|
87
92
|
get_args,
|
|
88
93
|
is_dataclass_class,
|
|
@@ -94,14 +99,12 @@ from utilities.typing import (
|
|
|
94
99
|
is_set_type,
|
|
95
100
|
make_isinstance,
|
|
96
101
|
)
|
|
97
|
-
from utilities.warnings import suppress_warnings
|
|
98
102
|
from utilities.whenever import (
|
|
99
103
|
DatePeriod,
|
|
100
104
|
TimePeriod,
|
|
101
105
|
ZonedDateTimePeriod,
|
|
102
106
|
to_py_time_delta,
|
|
103
107
|
)
|
|
104
|
-
from utilities.zoneinfo import UTC, to_time_zone_name
|
|
105
108
|
|
|
106
109
|
if TYPE_CHECKING:
|
|
107
110
|
import datetime as dt
|
|
@@ -343,7 +346,7 @@ class AppendRowError(Exception):
|
|
|
343
346
|
class _AppendRowPredicateError(AppendRowError):
|
|
344
347
|
@override
|
|
345
348
|
def __str__(self) -> str:
|
|
346
|
-
return f"Predicate failed; got {
|
|
349
|
+
return f"Predicate failed; got {repr_(self.row)}"
|
|
347
350
|
|
|
348
351
|
|
|
349
352
|
@dataclass(kw_only=True, slots=True)
|
|
@@ -352,7 +355,7 @@ class _AppendRowExtraKeysError(AppendRowError):
|
|
|
352
355
|
|
|
353
356
|
@override
|
|
354
357
|
def __str__(self) -> str:
|
|
355
|
-
return f"Extra key(s) found; got {
|
|
358
|
+
return f"Extra key(s) found; got {repr_(self.extra)}"
|
|
356
359
|
|
|
357
360
|
|
|
358
361
|
@dataclass(kw_only=True, slots=True)
|
|
@@ -361,7 +364,7 @@ class _AppendRowMissingKeysError(AppendRowError):
|
|
|
361
364
|
|
|
362
365
|
@override
|
|
363
366
|
def __str__(self) -> str:
|
|
364
|
-
return f"Missing key(s) found; got {
|
|
367
|
+
return f"Missing key(s) found; got {repr_(self.missing)}"
|
|
365
368
|
|
|
366
369
|
|
|
367
370
|
@dataclass(kw_only=True, slots=True)
|
|
@@ -370,7 +373,7 @@ class _AppendRowNullColumnsError(AppendRowError):
|
|
|
370
373
|
|
|
371
374
|
@override
|
|
372
375
|
def __str__(self) -> str:
|
|
373
|
-
return f"Null column(s) found; got {
|
|
376
|
+
return f"Null column(s) found; got {repr_(self.columns)}"
|
|
374
377
|
|
|
375
378
|
|
|
376
379
|
##
|
|
@@ -569,7 +572,7 @@ class _CheckPolarsDataFrameColumnsError(CheckPolarsDataFrameError):
|
|
|
569
572
|
|
|
570
573
|
@override
|
|
571
574
|
def __str__(self) -> str:
|
|
572
|
-
return f"DataFrame must have columns {
|
|
575
|
+
return f"DataFrame must have columns {repr_(self.columns)}; got {repr_(self.df.columns)}:\n\n{self.df}"
|
|
573
576
|
|
|
574
577
|
|
|
575
578
|
def _check_polars_dataframe_dtypes(
|
|
@@ -587,7 +590,7 @@ class _CheckPolarsDataFrameDTypesError(CheckPolarsDataFrameError):
|
|
|
587
590
|
|
|
588
591
|
@override
|
|
589
592
|
def __str__(self) -> str:
|
|
590
|
-
return f"DataFrame must have dtypes {
|
|
593
|
+
return f"DataFrame must have dtypes {repr_(self.dtypes)}; got {repr_(self.df.dtypes)}:\n\n{self.df}"
|
|
591
594
|
|
|
592
595
|
|
|
593
596
|
def _check_polars_dataframe_height(
|
|
@@ -650,9 +653,9 @@ class _CheckPolarsDataFramePredicatesError(CheckPolarsDataFrameError):
|
|
|
650
653
|
|
|
651
654
|
def _yield_parts(self) -> Iterator[str]:
|
|
652
655
|
if len(self.missing) >= 1:
|
|
653
|
-
yield f"missing columns were {
|
|
656
|
+
yield f"missing columns were {repr_(self.missing)}"
|
|
654
657
|
if len(self.failed) >= 1:
|
|
655
|
-
yield f"failed predicates were {
|
|
658
|
+
yield f"failed predicates were {repr_(self.failed)}"
|
|
656
659
|
|
|
657
660
|
|
|
658
661
|
def _check_polars_dataframe_schema_list(df: DataFrame, schema: SchemaDict, /) -> None:
|
|
@@ -672,7 +675,7 @@ class _CheckPolarsDataFrameSchemaListError(CheckPolarsDataFrameError):
|
|
|
672
675
|
|
|
673
676
|
@override
|
|
674
677
|
def __str__(self) -> str:
|
|
675
|
-
return f"DataFrame must have schema {
|
|
678
|
+
return f"DataFrame must have schema {repr_(self.schema)} (ordered); got {repr_(self.df.schema)}:\n\n{self.df}"
|
|
676
679
|
|
|
677
680
|
|
|
678
681
|
def _check_polars_dataframe_schema_set(df: DataFrame, schema: SchemaDict, /) -> None:
|
|
@@ -688,7 +691,7 @@ class _CheckPolarsDataFrameSchemaSetError(CheckPolarsDataFrameError):
|
|
|
688
691
|
|
|
689
692
|
@override
|
|
690
693
|
def __str__(self) -> str:
|
|
691
|
-
return f"DataFrame must have schema {
|
|
694
|
+
return f"DataFrame must have schema {repr_(self.schema)} (unordered); got {repr_(self.df.schema)}:\n\n{self.df}"
|
|
692
695
|
|
|
693
696
|
|
|
694
697
|
def _check_polars_dataframe_schema_subset(df: DataFrame, schema: SchemaDict, /) -> None:
|
|
@@ -704,7 +707,7 @@ class _CheckPolarsDataFrameSchemaSubsetError(CheckPolarsDataFrameError):
|
|
|
704
707
|
|
|
705
708
|
@override
|
|
706
709
|
def __str__(self) -> str:
|
|
707
|
-
return f"DataFrame schema must include {
|
|
710
|
+
return f"DataFrame schema must include {repr_(self.schema)} (unordered); got {repr_(self.df.schema)}:\n\n{self.df}"
|
|
708
711
|
|
|
709
712
|
|
|
710
713
|
def _check_polars_dataframe_shape(df: DataFrame, shape: tuple[int, int], /) -> None:
|
|
@@ -742,7 +745,7 @@ class _CheckPolarsDataFrameSortedError(CheckPolarsDataFrameError):
|
|
|
742
745
|
|
|
743
746
|
@override
|
|
744
747
|
def __str__(self) -> str:
|
|
745
|
-
return f"DataFrame must be sorted on {
|
|
748
|
+
return f"DataFrame must be sorted on {repr_(self.by)}:\n\n{self.df}"
|
|
746
749
|
|
|
747
750
|
|
|
748
751
|
def _check_polars_dataframe_unique(
|
|
@@ -761,7 +764,7 @@ class _CheckPolarsDataFrameUniqueError(CheckPolarsDataFrameError):
|
|
|
761
764
|
|
|
762
765
|
@override
|
|
763
766
|
def __str__(self) -> str:
|
|
764
|
-
return f"DataFrame must be unique on {
|
|
767
|
+
return f"DataFrame must be unique on {repr_(self.by)}:\n\n{self.df}"
|
|
765
768
|
|
|
766
769
|
|
|
767
770
|
def _check_polars_dataframe_width(df: DataFrame, width: int, /) -> None:
|
|
@@ -1132,7 +1135,7 @@ class _DataClassToDataFrameNonUniqueError(DataClassToDataFrameError):
|
|
|
1132
1135
|
|
|
1133
1136
|
@override
|
|
1134
1137
|
def __str__(self) -> str:
|
|
1135
|
-
return f"Iterable {
|
|
1138
|
+
return f"Iterable {repr_(self.objs)} must contain exactly 1 class; got {self.first}, {self.second} and perhaps more"
|
|
1136
1139
|
|
|
1137
1140
|
|
|
1138
1141
|
##
|
|
@@ -1147,7 +1150,7 @@ def dataclass_to_schema(
|
|
|
1147
1150
|
warn_name_errors: bool = False,
|
|
1148
1151
|
) -> SchemaDict:
|
|
1149
1152
|
"""Cast a dataclass as a schema dict."""
|
|
1150
|
-
out:
|
|
1153
|
+
out: StrDict = {}
|
|
1151
1154
|
for field in yield_fields(
|
|
1152
1155
|
obj, globalns=globalns, localns=localns, warn_name_errors=warn_name_errors
|
|
1153
1156
|
):
|
|
@@ -1204,7 +1207,7 @@ def _dataclass_to_schema_one(
|
|
|
1204
1207
|
if issubclass(obj, enum.Enum):
|
|
1205
1208
|
return pl.Enum([e.name for e in obj])
|
|
1206
1209
|
if is_dataclass_class(obj):
|
|
1207
|
-
out:
|
|
1210
|
+
out: StrDict = {}
|
|
1208
1211
|
for field in yield_fields(obj, globalns=globalns, localns=localns):
|
|
1209
1212
|
out[field.name] = _dataclass_to_schema_one(
|
|
1210
1213
|
field.type_, globalns=globalns, localns=localns
|
|
@@ -2425,7 +2428,7 @@ def _replace_time_zone_one(
|
|
|
2425
2428
|
|
|
2426
2429
|
def read_series(path: PathLike, /, *, decompress: bool = False) -> Series:
|
|
2427
2430
|
"""Read a Series from disk."""
|
|
2428
|
-
data =
|
|
2431
|
+
data = read_bytes(path, decompress=decompress)
|
|
2429
2432
|
return deserialize_series(data)
|
|
2430
2433
|
|
|
2431
2434
|
|
|
@@ -2439,12 +2442,12 @@ def write_series(
|
|
|
2439
2442
|
) -> None:
|
|
2440
2443
|
"""Write a Series to disk."""
|
|
2441
2444
|
data = serialize_series(series)
|
|
2442
|
-
|
|
2445
|
+
write_bytes(path, data, compress=compress, overwrite=overwrite, json=True)
|
|
2443
2446
|
|
|
2444
2447
|
|
|
2445
2448
|
def read_dataframe(path: PathLike, /, *, decompress: bool = False) -> DataFrame:
|
|
2446
2449
|
"""Read a DataFrame from disk."""
|
|
2447
|
-
data =
|
|
2450
|
+
data = read_bytes(path, decompress=decompress)
|
|
2448
2451
|
return deserialize_dataframe(data)
|
|
2449
2452
|
|
|
2450
2453
|
|
|
@@ -2453,7 +2456,7 @@ def write_dataframe(
|
|
|
2453
2456
|
) -> None:
|
|
2454
2457
|
"""Write a DataFrame to disk."""
|
|
2455
2458
|
data = serialize_dataframe(df)
|
|
2456
|
-
|
|
2459
|
+
write_bytes(path, data, compress=compress, overwrite=overwrite, json=True)
|
|
2457
2460
|
|
|
2458
2461
|
|
|
2459
2462
|
def serialize_series(series: Series, /) -> bytes:
|
|
@@ -2665,7 +2668,7 @@ class SelectExactError(Exception):
|
|
|
2665
2668
|
|
|
2666
2669
|
@override
|
|
2667
2670
|
def __str__(self) -> str:
|
|
2668
|
-
return f"All columns must be selected; got {
|
|
2671
|
+
return f"All columns must be selected; got {repr_(self.columns)} remaining"
|
|
2669
2672
|
|
|
2670
2673
|
|
|
2671
2674
|
##
|
utilities/postgres.py
CHANGED
|
@@ -9,10 +9,9 @@ from sqlalchemy import Table
|
|
|
9
9
|
from sqlalchemy.orm import DeclarativeBase
|
|
10
10
|
|
|
11
11
|
from utilities.asyncio import stream_command
|
|
12
|
+
from utilities.core import always_iterable, yield_temp_environ
|
|
12
13
|
from utilities.docker import docker_exec_cmd
|
|
13
|
-
from utilities.iterables import always_iterable
|
|
14
14
|
from utilities.logging import to_logger
|
|
15
|
-
from utilities.os import temp_environ
|
|
16
15
|
from utilities.pathlib import ensure_suffix
|
|
17
16
|
from utilities.sqlalchemy import extract_url, get_table_name
|
|
18
17
|
from utilities.timer import Timer
|
|
@@ -82,7 +81,10 @@ async def pg_dump(
|
|
|
82
81
|
if logger is not None:
|
|
83
82
|
to_logger(logger).info("Would run:\n\t%r", str(cmd))
|
|
84
83
|
return True
|
|
85
|
-
with
|
|
84
|
+
with (
|
|
85
|
+
yield_temp_environ(PGPASSWORD=url.password),
|
|
86
|
+
Timer() as timer,
|
|
87
|
+
): # pragma: no cover
|
|
86
88
|
try:
|
|
87
89
|
output = await stream_command(cmd)
|
|
88
90
|
except KeyboardInterrupt:
|
|
@@ -237,7 +239,10 @@ async def restore(
|
|
|
237
239
|
if logger is not None:
|
|
238
240
|
to_logger(logger).info("Would run:\n\t%r", str(cmd))
|
|
239
241
|
return True
|
|
240
|
-
with
|
|
242
|
+
with (
|
|
243
|
+
yield_temp_environ(PGPASSWORD=url.password),
|
|
244
|
+
Timer() as timer,
|
|
245
|
+
): # pragma: no cover
|
|
241
246
|
try:
|
|
242
247
|
output = await stream_command(cmd)
|
|
243
248
|
except KeyboardInterrupt:
|
utilities/pottery.py
CHANGED
|
@@ -9,21 +9,21 @@ from pottery import AIORedlock
|
|
|
9
9
|
from pottery.exceptions import ReleaseUnlockedLock
|
|
10
10
|
from redis.asyncio import Redis
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
import utilities.asyncio
|
|
13
|
+
from utilities.constants import MILLISECOND, SECOND
|
|
13
14
|
from utilities.contextlib import enhanced_async_context_manager
|
|
14
|
-
from utilities.
|
|
15
|
-
from utilities.
|
|
15
|
+
from utilities.core import always_iterable
|
|
16
|
+
from utilities.functions import in_seconds
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
18
19
|
from collections.abc import AsyncIterator, Iterable
|
|
19
20
|
|
|
20
|
-
from
|
|
21
|
+
from utilities.types import Duration, MaybeIterable
|
|
21
22
|
|
|
22
|
-
from utilities.types import MaybeIterable
|
|
23
23
|
|
|
24
24
|
_NUM: int = 1
|
|
25
|
-
_TIMEOUT_RELEASE:
|
|
26
|
-
_SLEEP:
|
|
25
|
+
_TIMEOUT_RELEASE: Duration = 10 * SECOND
|
|
26
|
+
_SLEEP: Duration = MILLISECOND
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
##
|
|
@@ -47,11 +47,11 @@ async def yield_access(
|
|
|
47
47
|
/,
|
|
48
48
|
*,
|
|
49
49
|
num: int = _NUM,
|
|
50
|
-
timeout_release:
|
|
50
|
+
timeout_release: Duration = _TIMEOUT_RELEASE,
|
|
51
51
|
num_extensions: int | None = None,
|
|
52
|
-
timeout_acquire:
|
|
53
|
-
sleep:
|
|
54
|
-
throttle:
|
|
52
|
+
timeout_acquire: Duration | None = None,
|
|
53
|
+
sleep: Duration = _SLEEP,
|
|
54
|
+
throttle: Duration | None = None,
|
|
55
55
|
) -> AsyncIterator[AIORedlock]:
|
|
56
56
|
"""Acquire access to a locked resource."""
|
|
57
57
|
if num <= 0:
|
|
@@ -63,7 +63,7 @@ async def yield_access(
|
|
|
63
63
|
AIORedlock(
|
|
64
64
|
key=f"{key}_{i}_of_{num}",
|
|
65
65
|
masters=masters,
|
|
66
|
-
auto_release_time=
|
|
66
|
+
auto_release_time=in_seconds(timeout_release),
|
|
67
67
|
num_extensions=maxsize if num_extensions is None else num_extensions,
|
|
68
68
|
)
|
|
69
69
|
for i in range(1, num + 1)
|
|
@@ -75,7 +75,7 @@ async def yield_access(
|
|
|
75
75
|
)
|
|
76
76
|
yield lock
|
|
77
77
|
finally: # skipif-ci-and-not-linux
|
|
78
|
-
await
|
|
78
|
+
await utilities.asyncio.sleep(throttle)
|
|
79
79
|
if lock is not None:
|
|
80
80
|
with suppress(ReleaseUnlockedLock):
|
|
81
81
|
await lock.release()
|
|
@@ -87,18 +87,20 @@ async def _get_first_available_lock(
|
|
|
87
87
|
/,
|
|
88
88
|
*,
|
|
89
89
|
num: int = _NUM,
|
|
90
|
-
timeout:
|
|
91
|
-
sleep:
|
|
90
|
+
timeout: Duration | None = None,
|
|
91
|
+
sleep: Duration | None = _SLEEP,
|
|
92
92
|
) -> AIORedlock:
|
|
93
93
|
locks = list(locks) # skipif-ci-and-not-linux
|
|
94
94
|
error = _YieldAccessUnableToAcquireLockError( # skipif-ci-and-not-linux
|
|
95
95
|
key=key, num=num, timeout=timeout
|
|
96
96
|
)
|
|
97
|
-
async with
|
|
97
|
+
async with utilities.asyncio.timeout( # skipif-ci-and-not-linux
|
|
98
|
+
timeout, error=error
|
|
99
|
+
):
|
|
98
100
|
while True:
|
|
99
101
|
if (result := await _get_first_available_lock_if_any(locks)) is not None:
|
|
100
102
|
return result
|
|
101
|
-
await
|
|
103
|
+
await utilities.asyncio.sleep(sleep)
|
|
102
104
|
|
|
103
105
|
|
|
104
106
|
async def _get_first_available_lock_if_any(
|
|
@@ -127,7 +129,7 @@ class _YieldAccessNumLocksError(YieldAccessError):
|
|
|
127
129
|
@dataclass(kw_only=True, slots=True)
|
|
128
130
|
class _YieldAccessUnableToAcquireLockError(YieldAccessError):
|
|
129
131
|
num: int
|
|
130
|
-
timeout:
|
|
132
|
+
timeout: Duration | None
|
|
131
133
|
|
|
132
134
|
@override
|
|
133
135
|
def __str__(self) -> str:
|
utilities/pqdm.py
CHANGED
|
@@ -6,18 +6,17 @@ from typing import TYPE_CHECKING, Any, Literal, assert_never
|
|
|
6
6
|
from pqdm import processes, threads
|
|
7
7
|
from tqdm.auto import tqdm as tqdm_auto
|
|
8
8
|
|
|
9
|
-
from utilities.
|
|
9
|
+
from utilities.constants import Sentinel, sentinel
|
|
10
|
+
from utilities.core import get_func_name, is_sentinel
|
|
10
11
|
from utilities.iterables import apply_to_varargs
|
|
11
12
|
from utilities.os import get_cpu_use
|
|
12
|
-
from utilities.sentinel import Sentinel, is_sentinel, sentinel
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
15
|
from collections.abc import Callable, Iterable
|
|
16
16
|
|
|
17
17
|
from tqdm import tqdm as tqdm_type
|
|
18
18
|
|
|
19
|
-
from utilities.
|
|
20
|
-
from utilities.types import Parallelism
|
|
19
|
+
from utilities.types import IntOrAll, Parallelism
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
type _ExceptionBehaviour = Literal["ignore", "immediate", "deferred"]
|
utilities/psutil.py
CHANGED
|
@@ -6,8 +6,7 @@ from typing import TYPE_CHECKING, Self
|
|
|
6
6
|
|
|
7
7
|
from psutil import swap_memory, virtual_memory
|
|
8
8
|
|
|
9
|
-
from utilities.
|
|
10
|
-
from utilities.whenever import get_now
|
|
9
|
+
from utilities.core import get_now, suppress_super_attribute_error
|
|
11
10
|
|
|
12
11
|
if TYPE_CHECKING:
|
|
13
12
|
from whenever import ZonedDateTime
|
|
@@ -30,7 +29,7 @@ class MemoryUsage:
|
|
|
30
29
|
swap_pct: float = field(init=False)
|
|
31
30
|
|
|
32
31
|
def __post_init__(self) -> None:
|
|
33
|
-
with
|
|
32
|
+
with suppress_super_attribute_error():
|
|
34
33
|
super().__post_init__() # pyright: ignore[reportAttributeAccessIssue]
|
|
35
34
|
self.virtual_used_mb = self._to_mb(self.virtual_used)
|
|
36
35
|
self.virtual_total_mb = self._to_mb(self.virtual_total)
|
utilities/pydantic.py
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Annotated
|
|
4
|
+
from typing import Annotated, assert_never
|
|
5
5
|
|
|
6
|
-
from pydantic import BeforeValidator
|
|
6
|
+
from pydantic import BeforeValidator, SecretStr
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
from utilities.types import PathLike
|
|
9
9
|
|
|
10
|
+
type ExpandedPath = Annotated[PathLike, BeforeValidator(lambda p: Path(p).expanduser())]
|
|
11
|
+
type SecretLike = SecretStr | str
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
|
|
14
|
+
def extract_secret(value: SecretLike, /) -> str:
|
|
15
|
+
"""Given a secret, extract its value."""
|
|
16
|
+
match value:
|
|
17
|
+
case SecretStr():
|
|
18
|
+
return value.get_secret_value()
|
|
19
|
+
case str():
|
|
20
|
+
return value
|
|
21
|
+
case never:
|
|
22
|
+
assert_never(never)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = ["ExpandedPath", "SecretLike", "extract_secret"]
|
utilities/pydantic_settings.py
CHANGED
|
@@ -16,15 +16,15 @@ from pydantic_settings import (
|
|
|
16
16
|
)
|
|
17
17
|
from pydantic_settings.sources import DEFAULT_PATH
|
|
18
18
|
|
|
19
|
+
from utilities.core import always_iterable
|
|
19
20
|
from utilities.errors import ImpossibleCaseError
|
|
20
|
-
from utilities.iterables import always_iterable
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
23
|
from collections.abc import Iterator, Sequence
|
|
24
24
|
|
|
25
25
|
from pydantic_settings.sources import PathType
|
|
26
26
|
|
|
27
|
-
from utilities.types import MaybeSequenceStr, PathLike
|
|
27
|
+
from utilities.types import MaybeSequenceStr, PathLike, StrDict
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
type PathLikeWithSection = tuple[PathLike, MaybeSequenceStr]
|
|
@@ -95,7 +95,7 @@ class JsonConfigSectionSettingsSource(JsonConfigSettingsSource):
|
|
|
95
95
|
self.section = section
|
|
96
96
|
|
|
97
97
|
@override
|
|
98
|
-
def __call__(self) ->
|
|
98
|
+
def __call__(self) -> StrDict:
|
|
99
99
|
return _get_section(super().__call__(), self.section)
|
|
100
100
|
|
|
101
101
|
|
|
@@ -112,7 +112,7 @@ class TomlConfigSectionSettingsSource(TomlConfigSettingsSource):
|
|
|
112
112
|
self.section = section
|
|
113
113
|
|
|
114
114
|
@override
|
|
115
|
-
def __call__(self) ->
|
|
115
|
+
def __call__(self) -> StrDict:
|
|
116
116
|
return _get_section(super().__call__(), self.section)
|
|
117
117
|
|
|
118
118
|
|
|
@@ -136,7 +136,7 @@ class YamlConfigSectionSettingsSource(YamlConfigSettingsSource):
|
|
|
136
136
|
self.section = section
|
|
137
137
|
|
|
138
138
|
@override
|
|
139
|
-
def __call__(self) ->
|
|
139
|
+
def __call__(self) -> StrDict:
|
|
140
140
|
return _get_section(super().__call__(), self.section)
|
|
141
141
|
|
|
142
142
|
|
|
@@ -150,9 +150,7 @@ def _ensure_section(file: PathLikeOrWithSection, /) -> PathLikeWithSection:
|
|
|
150
150
|
assert_never(never)
|
|
151
151
|
|
|
152
152
|
|
|
153
|
-
def _get_section(
|
|
154
|
-
mapping: dict[str, Any], section: MaybeSequenceStr, /
|
|
155
|
-
) -> dict[str, Any]:
|
|
153
|
+
def _get_section(mapping: StrDict, section: MaybeSequenceStr, /) -> StrDict:
|
|
156
154
|
return reduce(lambda acc, el: acc.get(el, {}), always_iterable(section), mapping)
|
|
157
155
|
|
|
158
156
|
|
|
@@ -215,7 +213,7 @@ def _load_settings_create_model[T: BaseSettings](
|
|
|
215
213
|
cls: type[T], /, *, values: T | None = None
|
|
216
214
|
) -> type[T]:
|
|
217
215
|
values_use = cls() if values is None else values
|
|
218
|
-
kwargs:
|
|
216
|
+
kwargs: StrDict = {}
|
|
219
217
|
for name, field in cls.model_fields.items():
|
|
220
218
|
if (ann := field.annotation) is None:
|
|
221
219
|
raise ImpossibleCaseError(case=[f"{ann=}"]) # pragma: no cover
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from logging import Filter, LogRecord, getLogger
|
|
4
4
|
from re import search
|
|
5
|
-
from typing import TYPE_CHECKING,
|
|
5
|
+
from typing import TYPE_CHECKING, ClassVar, override
|
|
6
6
|
|
|
7
7
|
from pydantic_settings.sources import DEFAULT_PATH
|
|
8
8
|
from pydantic_settings_sops import SOPSConfigSettingsSource
|
|
@@ -20,7 +20,7 @@ if TYPE_CHECKING:
|
|
|
20
20
|
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource
|
|
21
21
|
from pydantic_settings.sources import PathType
|
|
22
22
|
|
|
23
|
-
from utilities.types import MaybeSequenceStr
|
|
23
|
+
from utilities.types import MaybeSequenceStr, StrDict
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class _SuppressDefaultConfigMessage(Filter):
|
|
@@ -69,7 +69,7 @@ class SOPSConfigSectionSettingsSource(SOPSConfigSettingsSource):
|
|
|
69
69
|
self.section = section
|
|
70
70
|
|
|
71
71
|
@override
|
|
72
|
-
def __call__(self) ->
|
|
72
|
+
def __call__(self) -> StrDict:
|
|
73
73
|
return _get_section(super().__call__(), self.section)
|
|
74
74
|
|
|
75
75
|
|
utilities/pyinstrument.py
CHANGED
|
@@ -6,9 +6,9 @@ from typing import TYPE_CHECKING
|
|
|
6
6
|
|
|
7
7
|
from pyinstrument.profiler import Profiler
|
|
8
8
|
|
|
9
|
-
from utilities.
|
|
9
|
+
from utilities.core import get_now_local, write_text
|
|
10
10
|
from utilities.pathlib import to_path
|
|
11
|
-
from utilities.whenever import format_compact
|
|
11
|
+
from utilities.whenever import format_compact
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from collections.abc import Iterator
|
|
@@ -24,8 +24,8 @@ def profile(path: MaybeCallablePathLike = Path.cwd, /) -> Iterator[None]:
|
|
|
24
24
|
filename = to_path(path).joinpath(
|
|
25
25
|
f"profile__{format_compact(get_now_local(), path=True)}.html"
|
|
26
26
|
)
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
text = profiler.output_html()
|
|
28
|
+
write_text(filename, text, overwrite=True)
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
__all__ = ["profile"]
|