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/pytest.py
CHANGED
|
@@ -8,26 +8,37 @@ from os import environ
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from re import sub
|
|
10
10
|
from types import FunctionType
|
|
11
|
-
from typing import TYPE_CHECKING, Any, assert_never, cast, override
|
|
12
|
-
|
|
13
|
-
from
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
from typing import TYPE_CHECKING, Any, NoReturn, assert_never, cast, override
|
|
12
|
+
|
|
13
|
+
from utilities.constants import (
|
|
14
|
+
IS_CI,
|
|
15
|
+
IS_CI_AND_NOT_LINUX,
|
|
16
|
+
IS_LINUX,
|
|
17
|
+
IS_MAC,
|
|
18
|
+
IS_NOT_LINUX,
|
|
19
|
+
IS_NOT_MAC,
|
|
20
|
+
SECOND,
|
|
21
|
+
)
|
|
16
22
|
from utilities.functools import cache
|
|
17
23
|
from utilities.hashlib import md5_hash
|
|
18
|
-
from utilities.os import get_env_var
|
|
19
24
|
from utilities.pathlib import (
|
|
20
25
|
_GetTailEmptyError,
|
|
21
26
|
ensure_suffix,
|
|
22
|
-
|
|
27
|
+
get_repo_root,
|
|
23
28
|
get_tail,
|
|
24
29
|
module_path,
|
|
25
30
|
)
|
|
26
|
-
from utilities.platform import IS_LINUX, IS_MAC, IS_NOT_LINUX, IS_NOT_MAC
|
|
27
31
|
from utilities.random import bernoulli
|
|
28
32
|
from utilities.text import to_bool
|
|
29
|
-
from utilities.
|
|
30
|
-
from utilities.
|
|
33
|
+
from utilities.throttle import throttle
|
|
34
|
+
from utilities.types import (
|
|
35
|
+
Coro,
|
|
36
|
+
Duration,
|
|
37
|
+
MaybeCallableBoolLike,
|
|
38
|
+
MaybeCoro,
|
|
39
|
+
PathLike,
|
|
40
|
+
Seed,
|
|
41
|
+
)
|
|
31
42
|
|
|
32
43
|
if TYPE_CHECKING:
|
|
33
44
|
from collections.abc import Iterable
|
|
@@ -36,11 +47,7 @@ if TYPE_CHECKING:
|
|
|
36
47
|
from _pytest.config.argparsing import Parser
|
|
37
48
|
from _pytest.python import Function
|
|
38
49
|
|
|
39
|
-
from utilities.types import
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
IS_CI = "CI" in environ
|
|
43
|
-
IS_CI_AND_NOT_LINUX = IS_CI and IS_NOT_LINUX
|
|
50
|
+
from utilities.types import PathLike
|
|
44
51
|
|
|
45
52
|
|
|
46
53
|
try: # WARNING: this package cannot use unguarded `pytest` imports
|
|
@@ -138,9 +145,9 @@ def add_pytest_configure(config: Config, options: Iterable[tuple[str, str]], /)
|
|
|
138
145
|
|
|
139
146
|
|
|
140
147
|
def make_ids(obj: Any, /) -> str:
|
|
141
|
-
if isinstance(obj, FunctionType):
|
|
148
|
+
if isinstance(obj, FunctionType): # pragma: no cover
|
|
142
149
|
return sub(r"\s+at +0x[0-9a-fA-F]+", "", repr(obj))
|
|
143
|
-
return repr(obj)
|
|
150
|
+
return repr(obj) # pragma: no cover
|
|
144
151
|
|
|
145
152
|
|
|
146
153
|
##
|
|
@@ -195,7 +202,7 @@ class _NodeIdToPathNotGetTailError(NodeIdToPathError):
|
|
|
195
202
|
##
|
|
196
203
|
|
|
197
204
|
|
|
198
|
-
def
|
|
205
|
+
def run_test_frac[F: Callable[..., MaybeCoro[None]]](
|
|
199
206
|
*,
|
|
200
207
|
predicate: MaybeCallableBoolLike | None = None,
|
|
201
208
|
frac: float = 0.5,
|
|
@@ -203,11 +210,11 @@ def run_frac[F: Callable[..., MaybeCoro[None]]](
|
|
|
203
210
|
) -> Callable[[F], F]:
|
|
204
211
|
"""Run a test only a fraction of the time.."""
|
|
205
212
|
return cast(
|
|
206
|
-
"Any", partial(
|
|
213
|
+
"Any", partial(_run_test_frac_inner, predicate=predicate, frac=frac, seed=seed)
|
|
207
214
|
)
|
|
208
215
|
|
|
209
216
|
|
|
210
|
-
def
|
|
217
|
+
def _run_test_frac_inner[F: Callable[..., MaybeCoro[None]]](
|
|
211
218
|
func: F,
|
|
212
219
|
/,
|
|
213
220
|
*,
|
|
@@ -255,109 +262,43 @@ def _skipif_frac(
|
|
|
255
262
|
##
|
|
256
263
|
|
|
257
264
|
|
|
258
|
-
def
|
|
259
|
-
*, root: PathLike | None = None,
|
|
265
|
+
def throttle_test[F: Callable[..., MaybeCoro[None]]](
|
|
266
|
+
*, on_try: bool = False, root: PathLike | None = None, duration: Duration = SECOND
|
|
260
267
|
) -> Callable[[F], F]:
|
|
261
268
|
"""Throttle a test. On success by default, on try otherwise."""
|
|
262
|
-
return
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
*,
|
|
269
|
-
root: PathLike | None = None,
|
|
270
|
-
delta: Delta = SECOND,
|
|
271
|
-
on_try: bool = False,
|
|
272
|
-
) -> F:
|
|
273
|
-
if get_env_var("THROTTLE", nullable=True) is not None:
|
|
274
|
-
return func
|
|
275
|
-
match bool(iscoroutinefunction(func)), on_try:
|
|
276
|
-
case False, False:
|
|
277
|
-
|
|
278
|
-
@wraps(func)
|
|
279
|
-
def throttle_sync_on_pass(*args: Any, **kwargs: Any) -> None:
|
|
280
|
-
_skipif_recent(root=root, delta=delta)
|
|
281
|
-
cast("Callable[..., None]", func)(*args, **kwargs)
|
|
282
|
-
_write(root)
|
|
283
|
-
|
|
284
|
-
return cast("Any", throttle_sync_on_pass)
|
|
285
|
-
|
|
286
|
-
case False, True:
|
|
287
|
-
|
|
288
|
-
@wraps(func)
|
|
289
|
-
def throttle_sync_on_try(*args: Any, **kwargs: Any) -> None:
|
|
290
|
-
_skipif_recent(root=root, delta=delta)
|
|
291
|
-
_write(root)
|
|
292
|
-
cast("Callable[..., None]", func)(*args, **kwargs)
|
|
293
|
-
|
|
294
|
-
return cast("Any", throttle_sync_on_try)
|
|
295
|
-
|
|
296
|
-
case True, False:
|
|
269
|
+
return throttle(
|
|
270
|
+
on_try=on_try,
|
|
271
|
+
duration=duration,
|
|
272
|
+
path=partial(_get_test_path, root=root),
|
|
273
|
+
raiser=_run_skip,
|
|
274
|
+
)
|
|
297
275
|
|
|
298
|
-
@wraps(func)
|
|
299
|
-
async def throttle_async_on_pass(*args: Any, **kwargs: Any) -> None:
|
|
300
|
-
_skipif_recent(root=root, delta=delta)
|
|
301
|
-
await cast("Callable[..., Coro[None]]", func)(*args, **kwargs)
|
|
302
|
-
_write(root)
|
|
303
276
|
|
|
304
|
-
|
|
277
|
+
def _run_skip() -> NoReturn:
|
|
278
|
+
from pytest import skip
|
|
305
279
|
|
|
306
|
-
|
|
280
|
+
skip(reason=f"{_get_name()} throttled")
|
|
307
281
|
|
|
308
|
-
@wraps(func)
|
|
309
|
-
async def throttle_async_on_try(*args: Any, **kwargs: Any) -> None:
|
|
310
|
-
_skipif_recent(root=root, delta=delta)
|
|
311
|
-
_write(root)
|
|
312
|
-
await cast("Callable[..., Coro[None]]", func)(*args, **kwargs)
|
|
313
282
|
|
|
314
|
-
|
|
283
|
+
def _get_name() -> str:
|
|
284
|
+
return environ["PYTEST_CURRENT_TEST"]
|
|
315
285
|
|
|
316
|
-
case never:
|
|
317
|
-
assert_never(never)
|
|
318
286
|
|
|
287
|
+
@cache
|
|
288
|
+
def _md5_hash_cached(text: str, /) -> str:
|
|
289
|
+
return md5_hash(text)
|
|
319
290
|
|
|
320
|
-
def _skipif_recent(*, root: PathLike | None = None, delta: Delta = SECOND) -> None:
|
|
321
|
-
from pytest import skip
|
|
322
291
|
|
|
323
|
-
|
|
324
|
-
try:
|
|
325
|
-
contents = path.read_text()
|
|
326
|
-
except FileNotFoundError:
|
|
327
|
-
return
|
|
328
|
-
try:
|
|
329
|
-
last = ZonedDateTime.parse_iso(contents)
|
|
330
|
-
except ValueError:
|
|
331
|
-
return
|
|
332
|
-
now = get_now_local()
|
|
333
|
-
if (now - delta) < last:
|
|
334
|
-
age = now - last
|
|
335
|
-
_ = skip(reason=f"{_get_name()} throttled (age {age})")
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
def _get_path(root: PathLike | None = None, /) -> Path:
|
|
292
|
+
def _get_test_path(*, root: PathLike | None = None) -> Path:
|
|
339
293
|
if root is None:
|
|
340
|
-
root_use =
|
|
294
|
+
root_use = get_repo_root().joinpath(
|
|
295
|
+
".pytest_cache", "throttle"
|
|
296
|
+
) # pragma: no cover
|
|
341
297
|
else:
|
|
342
298
|
root_use = root
|
|
343
299
|
return Path(root_use, _md5_hash_cached(_get_name()))
|
|
344
300
|
|
|
345
301
|
|
|
346
|
-
@cache
|
|
347
|
-
def _md5_hash_cached(text: str, /) -> str:
|
|
348
|
-
return md5_hash(text)
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
def _get_name() -> str:
|
|
352
|
-
return environ["PYTEST_CURRENT_TEST"]
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
def _write(root: PathLike | None = None, /) -> None:
|
|
356
|
-
path = _get_path(root)
|
|
357
|
-
with writer(path, overwrite=True) as temp:
|
|
358
|
-
_ = temp.write_text(get_now_local().format_iso())
|
|
359
|
-
|
|
360
|
-
|
|
361
302
|
__all__ = [
|
|
362
303
|
"IS_CI",
|
|
363
304
|
"IS_CI_AND_NOT_LINUX",
|
|
@@ -367,12 +308,12 @@ __all__ = [
|
|
|
367
308
|
"add_pytest_configure",
|
|
368
309
|
"make_ids",
|
|
369
310
|
"node_id_path",
|
|
370
|
-
"
|
|
311
|
+
"run_test_frac",
|
|
371
312
|
"skipif_ci",
|
|
372
313
|
"skipif_ci_and_not_linux",
|
|
373
314
|
"skipif_linux",
|
|
374
315
|
"skipif_mac",
|
|
375
316
|
"skipif_not_linux",
|
|
376
317
|
"skipif_not_mac",
|
|
377
|
-
"
|
|
318
|
+
"throttle_test",
|
|
378
319
|
]
|
|
@@ -40,7 +40,7 @@ else:
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def _get_path(request: FixtureRequest, /) -> Path:
|
|
43
|
-
from utilities.pathlib import
|
|
43
|
+
from utilities.pathlib import get_repo_root
|
|
44
44
|
from utilities.pytest import _NodeIdToPathNotGetTailError, node_id_path
|
|
45
45
|
|
|
46
46
|
path = Path(cast("Any", request).fspath)
|
|
@@ -50,7 +50,7 @@ def _get_path(request: FixtureRequest, /) -> Path:
|
|
|
50
50
|
except _NodeIdToPathNotGetTailError:
|
|
51
51
|
root = Path("tests")
|
|
52
52
|
tail = node_id_path(request.node.nodeid, root=root)
|
|
53
|
-
return
|
|
53
|
+
return get_repo_root(path).joinpath(root, "regressions", tail)
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
__all__ = ["orjson_regression", "polars_regression"]
|
utilities/pytest_regressions.py
CHANGED
|
@@ -4,14 +4,14 @@ from contextlib import suppress
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from json import loads
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from shutil import copytree
|
|
8
7
|
from typing import TYPE_CHECKING, Any, assert_never, override
|
|
9
8
|
|
|
9
|
+
from pytest_datadir.plugin import LazyDataDir
|
|
10
10
|
from pytest_regressions.file_regression import FileRegressionFixture
|
|
11
11
|
|
|
12
|
+
from utilities.core import _CopyOrMoveSourceNotFoundError, copy, repr_
|
|
12
13
|
from utilities.functions import ensure_str
|
|
13
14
|
from utilities.operator import is_equal
|
|
14
|
-
from utilities.reprlib import get_repr
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
17
|
from polars import DataFrame, Series
|
|
@@ -33,10 +33,12 @@ class OrjsonRegressionFixture:
|
|
|
33
33
|
path = Path(path)
|
|
34
34
|
original_datadir = path.parent
|
|
35
35
|
data_dir = tmp_path.joinpath(ensure_str(request.fixturename))
|
|
36
|
-
with suppress(
|
|
37
|
-
|
|
36
|
+
with suppress(_CopyOrMoveSourceNotFoundError):
|
|
37
|
+
copy(original_datadir, data_dir, overwrite=True)
|
|
38
38
|
self._fixture = FileRegressionFixture(
|
|
39
|
-
datadir=
|
|
39
|
+
datadir=LazyDataDir(original_datadir=original_datadir, tmp_path=data_dir),
|
|
40
|
+
original_datadir=original_datadir,
|
|
41
|
+
request=request,
|
|
40
42
|
)
|
|
41
43
|
self._basename = path.name
|
|
42
44
|
|
|
@@ -93,7 +95,7 @@ class OrjsonRegressionError(Exception):
|
|
|
93
95
|
|
|
94
96
|
@override
|
|
95
97
|
def __str__(self) -> str:
|
|
96
|
-
return f"Obtained object (at {str(self.path_obtained)!r}) and existing object (at {str(self.path_existing)!r}) differ; got {
|
|
98
|
+
return f"Obtained object (at {str(self.path_obtained)!r}) and existing object (at {str(self.path_existing)!r}) differ; got {repr_(self.obtained)} and {repr_(self.existing)}"
|
|
97
99
|
|
|
98
100
|
|
|
99
101
|
##
|
utilities/random.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from random import Random
|
|
3
|
+
from random import Random
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
from utilities.functools import cache
|
|
@@ -11,12 +11,6 @@ if TYPE_CHECKING:
|
|
|
11
11
|
from utilities.types import Seed
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
SYSTEM_RANDOM = SystemRandom()
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
##
|
|
18
|
-
|
|
19
|
-
|
|
20
14
|
def bernoulli(*, true: float = 0.5, seed: Seed | None = None) -> bool:
|
|
21
15
|
"""Return a Bernoulli random variate."""
|
|
22
16
|
state = get_state(seed)
|
|
@@ -66,4 +60,4 @@ def shuffle[T](iterable: Iterable[T], /, *, seed: Seed | None = None) -> list[T]
|
|
|
66
60
|
return copy
|
|
67
61
|
|
|
68
62
|
|
|
69
|
-
__all__ = ["
|
|
63
|
+
__all__ = ["bernoulli", "get_docker_name", "get_state", "shuffle"]
|