dycw-utilities 0.126.2__py3-none-any.whl → 0.126.4__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.126.2.dist-info → dycw_utilities-0.126.4.dist-info}/METADATA +3 -3
- {dycw_utilities-0.126.2.dist-info → dycw_utilities-0.126.4.dist-info}/RECORD +10 -9
- utilities/__init__.py +1 -1
- utilities/hypothesis.py +19 -46
- utilities/libcst.py +1 -1
- utilities/pottery.py +50 -0
- utilities/psutil.py +1 -1
- utilities/redis.py +0 -19
- {dycw_utilities-0.126.2.dist-info → dycw_utilities-0.126.4.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.126.2.dist-info → dycw_utilities-0.126.4.dist-info}/licenses/LICENSE +0 -0
@@ -1,12 +1,12 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: dycw-utilities
|
3
|
-
Version: 0.126.
|
3
|
+
Version: 0.126.4
|
4
4
|
Author-email: Derek Wan <d.wan@icloud.com>
|
5
5
|
License-File: LICENSE
|
6
6
|
Requires-Python: >=3.12
|
7
7
|
Requires-Dist: typing-extensions<4.14,>=4.13.1
|
8
8
|
Provides-Extra: test
|
9
|
-
Requires-Dist: hypothesis<6.
|
9
|
+
Requires-Dist: hypothesis<6.133,>=6.132.0; extra == 'test'
|
10
10
|
Requires-Dist: pytest-asyncio<1.1,>=1.0.0; extra == 'test'
|
11
11
|
Requires-Dist: pytest-cov<6.2,>=6.1.1; extra == 'test'
|
12
12
|
Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
|
@@ -78,7 +78,7 @@ Provides-Extra: zzz-test-hypothesis
|
|
78
78
|
Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-hypothesis'
|
79
79
|
Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-hypothesis'
|
80
80
|
Requires-Dist: greenlet<3.3,>=3.2.0; extra == 'zzz-test-hypothesis'
|
81
|
-
Requires-Dist: hypothesis<6.
|
81
|
+
Requires-Dist: hypothesis<6.133,>=6.132.0; extra == 'zzz-test-hypothesis'
|
82
82
|
Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-hypothesis'
|
83
83
|
Requires-Dist: numpy<2.3,>=2.2.6; extra == 'zzz-test-hypothesis'
|
84
84
|
Requires-Dist: pathvalidate<3.3,>=3.2.3; extra == 'zzz-test-hypothesis'
|
@@ -1,4 +1,4 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=q-AzOWuCGiffIcmyXrrhqNK_TnB_0a0oNDlPGkcdXQE,60
|
2
2
|
utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
|
3
3
|
utilities/asyncio.py,sha256=K5Kj7rsM0nA17-b7d7mrNgPR1U_NbkfQmTruq5LBLRA,51778
|
4
4
|
utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
|
@@ -23,12 +23,12 @@ utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
|
|
23
23
|
utilities/git.py,sha256=wpt5dZ5Oi5931pN24_VLZYaQOvmR0OcQuVtgHzFUN1k,2359
|
24
24
|
utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
|
25
25
|
utilities/http.py,sha256=WcahTcKYRtZ04WXQoWt5EGCgFPcyHD3EJdlMfxvDt-0,946
|
26
|
-
utilities/hypothesis.py,sha256=
|
26
|
+
utilities/hypothesis.py,sha256=DwhPpnz9w5H9YxLpoXTRjhTpadDjrX5-yUVT-Kq6Tgc,45216
|
27
27
|
utilities/importlib.py,sha256=mV1xT_O_zt_GnZZ36tl3xOmMaN_3jErDWY54fX39F6Y,429
|
28
28
|
utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
|
29
29
|
utilities/iterables.py,sha256=mDqw2_0MUVp-P8FklgcaVTi2TXduH0MxbhTDzzhSBho,44915
|
30
30
|
utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
|
31
|
-
utilities/libcst.py,sha256=
|
31
|
+
utilities/libcst.py,sha256=Jto5ppzRzsxn4AD32IS8n0lbgLYXwsVJB6EY8giNZyY,4974
|
32
32
|
utilities/lightweight_charts.py,sha256=0xNfcsrgFI0R9xL25LtSm-W5yhfBI93qQNT6HyaXAhg,2769
|
33
33
|
utilities/logging.py,sha256=gwo3pusPjnWO1ollrtn1VKYyRAQJTue4SkCbMeNvec4,25715
|
34
34
|
utilities/loguru.py,sha256=MEMQVWrdECxk1e3FxGzmOf21vWT9j8CAir98SEXFKPA,3809
|
@@ -49,8 +49,9 @@ utilities/pickle.py,sha256=Bhvd7cZl-zQKQDFjUerqGuSKlHvnW1K2QXeU5UZibtg,657
|
|
49
49
|
utilities/platform.py,sha256=48IOKx1IC6ZJXWG-b56ZQptITcNFhWRjELW72o2dGTA,2398
|
50
50
|
utilities/polars.py,sha256=QlmUpYTqHNkcLnWOQh1TW22W2QyLzvifCvBcbsqhpdE,63272
|
51
51
|
utilities/polars_ols.py,sha256=Uc9V5kvlWZ5cU93lKZ-cfAKdVFFw81tqwLW9PxtUvMs,5618
|
52
|
+
utilities/pottery.py,sha256=_PA24g2B23etpiF2awZ4oR7RciUgEHu_j7Ls3QKMkjY,1527
|
52
53
|
utilities/pqdm.py,sha256=foRytQybmOQ05pjt5LF7ANyzrIa--4ScDE3T2wd31a4,3118
|
53
|
-
utilities/psutil.py,sha256=
|
54
|
+
utilities/psutil.py,sha256=RtbLKOoIJhqrJmEoHDBVeSD-KPzshtS0FtRXBP9_w2s,3751
|
54
55
|
utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
56
|
utilities/pydantic.py,sha256=f6qtR5mO2YMuyvNmbaEj5YeD9eGA4YYfb7Bjzh9jUs0,1845
|
56
57
|
utilities/pyinstrument.py,sha256=OJFDh4o1CWIa4aYPYURdQjgap_nvP45KUsCEe94rQHY,829
|
@@ -60,7 +61,7 @@ utilities/pytest_regressions.py,sha256=-SVT9647Dg6-JcdsiaDKXe3NdOmmrvGevLKWwGjxq
|
|
60
61
|
utilities/python_dotenv.py,sha256=iWcnpXbH7S6RoXHiLlGgyuH6udCupAcPd_gQ0eAenQ0,3190
|
61
62
|
utilities/random.py,sha256=lYdjgxB7GCfU_fwFVl5U-BIM_HV3q6_urL9byjrwDM8,4157
|
62
63
|
utilities/re.py,sha256=5J4d8VwIPFVrX2Eb8zfoxImDv7IwiN_U7mJ07wR2Wvs,3958
|
63
|
-
utilities/redis.py,sha256=
|
64
|
+
utilities/redis.py,sha256=U-LN6Li3vPTQ2sGGh85_1eD53payujg4qYX0TfmbLSs,32205
|
64
65
|
utilities/reprlib.py,sha256=Re9bk3n-kC__9DxQmRlevqFA86pE6TtVfWjUgpbVOv0,1849
|
65
66
|
utilities/rich.py,sha256=t50MwwVBsoOLxzmeVFSVpjno4OW6Ufum32skXbV8-Bs,1911
|
66
67
|
utilities/scipy.py,sha256=X6ROnHwiUhAmPhM0jkfEh0-Fd9iRvwiqtCQMOLmOQF8,945
|
@@ -90,7 +91,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
|
90
91
|
utilities/whenever.py,sha256=jS31ZAY5OMxFxLja_Yo5Fidi87Pd-GoVZ7Vi_teqVDA,16743
|
91
92
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
92
93
|
utilities/zoneinfo.py,sha256=-5j7IQ9nb7gR43rdgA7ms05im-XuqhAk9EJnQBXxCoQ,1874
|
93
|
-
dycw_utilities-0.126.
|
94
|
-
dycw_utilities-0.126.
|
95
|
-
dycw_utilities-0.126.
|
96
|
-
dycw_utilities-0.126.
|
94
|
+
dycw_utilities-0.126.4.dist-info/METADATA,sha256=GiVs9mIgDnNbbpw9H2PkBdxD3LIObcakDua9XR5Xh6g,12849
|
95
|
+
dycw_utilities-0.126.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
96
|
+
dycw_utilities-0.126.4.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
97
|
+
dycw_utilities-0.126.4.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/hypothesis.py
CHANGED
@@ -2,22 +2,18 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import builtins
|
4
4
|
import datetime as dt
|
5
|
-
from contextlib import
|
6
|
-
AbstractAsyncContextManager,
|
7
|
-
asynccontextmanager,
|
8
|
-
contextmanager,
|
9
|
-
suppress,
|
10
|
-
)
|
5
|
+
from contextlib import contextmanager
|
11
6
|
from dataclasses import dataclass
|
12
7
|
from datetime import timezone
|
13
8
|
from enum import Enum, auto
|
14
9
|
from functools import partial
|
15
10
|
from math import ceil, floor, inf, isclose, isfinite, nan
|
16
|
-
from os import environ
|
11
|
+
from os import environ, getpid
|
17
12
|
from pathlib import Path
|
18
13
|
from re import search
|
19
14
|
from string import ascii_letters, ascii_lowercase, ascii_uppercase, digits, printable
|
20
15
|
from subprocess import check_call
|
16
|
+
from threading import get_ident
|
21
17
|
from typing import (
|
22
18
|
TYPE_CHECKING,
|
23
19
|
Any,
|
@@ -92,27 +88,19 @@ from utilities.pathlib import temp_cwd
|
|
92
88
|
from utilities.platform import IS_WINDOWS
|
93
89
|
from utilities.sentinel import Sentinel, sentinel
|
94
90
|
from utilities.tempfile import TEMP_DIR, TemporaryDirectory
|
91
|
+
from utilities.tzlocal import get_now_local
|
95
92
|
from utilities.version import Version
|
96
93
|
from utilities.zoneinfo import UTC
|
97
94
|
|
98
95
|
if TYPE_CHECKING:
|
99
|
-
from collections.abc import
|
100
|
-
AsyncIterator,
|
101
|
-
Collection,
|
102
|
-
Hashable,
|
103
|
-
Iterable,
|
104
|
-
Iterator,
|
105
|
-
Sequence,
|
106
|
-
)
|
96
|
+
from collections.abc import Collection, Hashable, Iterable, Iterator, Sequence
|
107
97
|
from zoneinfo import ZoneInfo
|
108
98
|
|
109
99
|
from hypothesis.database import ExampleDatabase
|
110
100
|
from numpy.random import RandomState
|
111
|
-
from redis.typing import KeyT
|
112
101
|
from sqlalchemy.ext.asyncio import AsyncEngine
|
113
102
|
|
114
103
|
from utilities.numpy import NDArrayB, NDArrayF, NDArrayI, NDArrayO
|
115
|
-
from utilities.redis import _TestRedis
|
116
104
|
from utilities.sqlalchemy import Dialect, TableOrORMInstOrClass
|
117
105
|
from utilities.types import Duration, Number, RoundMode
|
118
106
|
|
@@ -1422,6 +1410,19 @@ def uint64s(
|
|
1422
1410
|
##
|
1423
1411
|
|
1424
1412
|
|
1413
|
+
@composite
|
1414
|
+
def unique_strs(draw: DrawFn, /) -> str:
|
1415
|
+
"""Strategy for generating unique strings."""
|
1416
|
+
now = get_now_local()
|
1417
|
+
pid = getpid()
|
1418
|
+
ident = get_ident()
|
1419
|
+
key = str(draw(uuids())).replace("-", "")
|
1420
|
+
return f"{now:%Y%m%d%H%M%S%f}_{pid}_{ident}_{key}"
|
1421
|
+
|
1422
|
+
|
1423
|
+
##
|
1424
|
+
|
1425
|
+
|
1425
1426
|
@composite
|
1426
1427
|
def versions(draw: DrawFn, /, *, suffix: MaybeSearchStrategy[bool] = False) -> Version:
|
1427
1428
|
"""Strategy for generating versions."""
|
@@ -1434,34 +1435,6 @@ def versions(draw: DrawFn, /, *, suffix: MaybeSearchStrategy[bool] = False) -> V
|
|
1434
1435
|
##
|
1435
1436
|
|
1436
1437
|
|
1437
|
-
def yield_test_redis(data: DataObject, /) -> AbstractAsyncContextManager[_TestRedis]:
|
1438
|
-
"""Strategy for generating test redis clients."""
|
1439
|
-
from redis.exceptions import ResponseError # skipif-ci-and-not-linux
|
1440
|
-
|
1441
|
-
from utilities.redis import _TestRedis, yield_redis # skipif-ci-and-not-linux
|
1442
|
-
from utilities.tzlocal import get_now_local # skipif-ci-and-not-linux
|
1443
|
-
|
1444
|
-
now = get_now_local() # skipif-ci-and-not-linux
|
1445
|
-
uuid = data.draw(uuids()) # skipif-ci-and-not-linux
|
1446
|
-
key = f"{now}_{uuid}" # skipif-ci-and-not-linux
|
1447
|
-
|
1448
|
-
@asynccontextmanager
|
1449
|
-
async def func() -> AsyncIterator[_TestRedis]: # skipif-ci-and-not-linux
|
1450
|
-
async with yield_redis(db=15) as redis: # skipif-ci-and-not-linux
|
1451
|
-
keys = cast("list[KeyT]", await redis.keys(pattern=f"{key}_*"))
|
1452
|
-
with suppress(ResponseError):
|
1453
|
-
_ = await redis.delete(*keys)
|
1454
|
-
yield _TestRedis(redis=redis, timestamp=now, uuid=uuid, key=key)
|
1455
|
-
keys = cast("list[KeyT]", await redis.keys(pattern=f"{key}_*"))
|
1456
|
-
with suppress(ResponseError):
|
1457
|
-
_ = await redis.delete(*keys)
|
1458
|
-
|
1459
|
-
return func() # skipif-ci-and-not-linux
|
1460
|
-
|
1461
|
-
|
1462
|
-
##
|
1463
|
-
|
1464
|
-
|
1465
1438
|
@composite
|
1466
1439
|
def zoned_datetimes(
|
1467
1440
|
draw: DrawFn,
|
@@ -1578,7 +1551,7 @@ __all__ = [
|
|
1578
1551
|
"triples",
|
1579
1552
|
"uint32s",
|
1580
1553
|
"uint64s",
|
1554
|
+
"unique_strs",
|
1581
1555
|
"versions",
|
1582
|
-
"yield_test_redis",
|
1583
1556
|
"zoned_datetimes",
|
1584
1557
|
]
|
utilities/libcst.py
CHANGED
@@ -156,7 +156,7 @@ def render_module(source: str | Module, /) -> str:
|
|
156
156
|
case str() as text:
|
157
157
|
try:
|
158
158
|
return check_output(["ruff", "format", "-"], input=text, text=True)
|
159
|
-
except CalledProcessError:
|
159
|
+
except CalledProcessError: # pragma: no cover
|
160
160
|
return text
|
161
161
|
case Module() as module:
|
162
162
|
return render_module(module.code)
|
utilities/pottery.py
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
from contextlib import asynccontextmanager, suppress
|
5
|
+
from typing import TYPE_CHECKING
|
6
|
+
|
7
|
+
from pottery import AIORedlock
|
8
|
+
from pottery.exceptions import ReleaseUnlockedLock
|
9
|
+
from redis.asyncio import Redis
|
10
|
+
|
11
|
+
from utilities.datetime import MILLISECOND, SECOND, datetime_duration_to_float
|
12
|
+
from utilities.iterables import always_iterable
|
13
|
+
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from collections.abc import AsyncIterator
|
16
|
+
|
17
|
+
from utilities.types import Duration, MaybeIterable
|
18
|
+
|
19
|
+
|
20
|
+
@asynccontextmanager
|
21
|
+
async def yield_locked_resource(
|
22
|
+
redis: MaybeIterable[Redis],
|
23
|
+
key: str,
|
24
|
+
/,
|
25
|
+
*,
|
26
|
+
duration: Duration = 10 * SECOND,
|
27
|
+
sleep: Duration = MILLISECOND,
|
28
|
+
) -> AsyncIterator[None]:
|
29
|
+
"""Yield a locked resource."""
|
30
|
+
masters = ( # skipif-ci-and-not-linux
|
31
|
+
{redis} if isinstance(redis, Redis) else set(always_iterable(redis))
|
32
|
+
)
|
33
|
+
duration_use = datetime_duration_to_float(duration) # skipif-ci-and-not-linux
|
34
|
+
lock = AIORedlock( # skipif-ci-and-not-linux
|
35
|
+
key=key,
|
36
|
+
masters=masters,
|
37
|
+
auto_release_time=duration_use,
|
38
|
+
context_manager_timeout=duration_use,
|
39
|
+
)
|
40
|
+
sleep_use = datetime_duration_to_float(sleep) # skipif-ci-and-not-linux
|
41
|
+
while not await lock.acquire(): # pragma: no cover
|
42
|
+
_ = await asyncio.sleep(sleep_use)
|
43
|
+
try: # skipif-ci-and-not-linux
|
44
|
+
yield
|
45
|
+
finally: # skipif-ci-and-not-linux
|
46
|
+
with suppress(ReleaseUnlockedLock):
|
47
|
+
await lock.release()
|
48
|
+
|
49
|
+
|
50
|
+
__all__ = ["yield_locked_resource"]
|
utilities/psutil.py
CHANGED
@@ -31,7 +31,6 @@ class MemoryMonitorService(Looper[None]):
|
|
31
31
|
console: str | None = field(default=None, repr=False)
|
32
32
|
path: PathLike = "memory.txt"
|
33
33
|
_console: Logger | None = field(init=False, repr=False)
|
34
|
-
_max_age: int | None = field(default=None, init=False, repr=False)
|
35
34
|
_path: Path = field(init=False, repr=False)
|
36
35
|
|
37
36
|
@override
|
@@ -40,6 +39,7 @@ class MemoryMonitorService(Looper[None]):
|
|
40
39
|
if self.console is not None:
|
41
40
|
self._console = getLogger(self.console)
|
42
41
|
self._path = Path(self.path)
|
42
|
+
self._path.parent.mkdir(parents=True, exist_ok=True)
|
43
43
|
|
44
44
|
@override
|
45
45
|
async def core(self) -> None:
|
utilities/redis.py
CHANGED
@@ -21,7 +21,6 @@ from typing import (
|
|
21
21
|
overload,
|
22
22
|
override,
|
23
23
|
)
|
24
|
-
from uuid import UUID, uuid4
|
25
24
|
|
26
25
|
from redis.asyncio import Redis
|
27
26
|
from redis.typing import EncodableT
|
@@ -32,14 +31,12 @@ from utilities.datetime import (
|
|
32
31
|
SECOND,
|
33
32
|
datetime_duration_to_float,
|
34
33
|
datetime_duration_to_timedelta,
|
35
|
-
get_now,
|
36
34
|
)
|
37
35
|
from utilities.errors import ImpossibleCaseError
|
38
36
|
from utilities.functions import ensure_int, get_class_name, identity
|
39
37
|
from utilities.iterables import always_iterable, one
|
40
38
|
|
41
39
|
if TYPE_CHECKING:
|
42
|
-
import datetime as dt
|
43
40
|
from collections.abc import (
|
44
41
|
AsyncIterator,
|
45
42
|
Awaitable,
|
@@ -984,22 +981,6 @@ def _deserialize(
|
|
984
981
|
return deserializer_use(data) # skipif-ci-and-not-linux
|
985
982
|
|
986
983
|
|
987
|
-
##
|
988
|
-
|
989
|
-
|
990
|
-
@dataclass(repr=False, kw_only=True, slots=True)
|
991
|
-
class _TestRedis:
|
992
|
-
"""A container for a redis client; for testing purposes only."""
|
993
|
-
|
994
|
-
redis: Redis
|
995
|
-
timestamp: dt.datetime = field(default_factory=get_now)
|
996
|
-
uuid: UUID = field(default_factory=uuid4)
|
997
|
-
key: str
|
998
|
-
|
999
|
-
|
1000
|
-
_ = _TestRedis
|
1001
|
-
|
1002
|
-
|
1003
984
|
__all__ = [
|
1004
985
|
"PublishService",
|
1005
986
|
"Publisher",
|
File without changes
|
File without changes
|