dycw-utilities 0.148.5__py3-none-any.whl → 0.174.12__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.
Potentially problematic release.
This version of dycw-utilities might be problematic. Click here for more details.
- dycw_utilities-0.174.12.dist-info/METADATA +41 -0
- dycw_utilities-0.174.12.dist-info/RECORD +104 -0
- dycw_utilities-0.174.12.dist-info/WHEEL +4 -0
- {dycw_utilities-0.148.5.dist-info → dycw_utilities-0.174.12.dist-info}/entry_points.txt +3 -0
- utilities/__init__.py +1 -1
- utilities/{eventkit.py → aeventkit.py} +12 -11
- utilities/altair.py +7 -6
- utilities/asyncio.py +113 -64
- utilities/atomicwrites.py +1 -1
- utilities/atools.py +64 -4
- utilities/cachetools.py +9 -6
- utilities/click.py +145 -49
- utilities/concurrent.py +1 -1
- utilities/contextlib.py +4 -2
- utilities/contextvars.py +20 -1
- utilities/cryptography.py +3 -3
- utilities/dataclasses.py +15 -28
- utilities/docker.py +292 -0
- utilities/enum.py +2 -2
- utilities/errors.py +1 -1
- utilities/fastapi.py +8 -3
- utilities/fpdf2.py +2 -2
- utilities/functions.py +20 -297
- utilities/git.py +19 -0
- utilities/grp.py +28 -0
- utilities/hypothesis.py +360 -78
- utilities/inflect.py +1 -1
- utilities/iterables.py +12 -58
- utilities/jinja2.py +148 -0
- utilities/json.py +1 -1
- utilities/libcst.py +7 -7
- utilities/logging.py +74 -85
- utilities/math.py +8 -4
- utilities/more_itertools.py +4 -6
- utilities/operator.py +1 -1
- utilities/orjson.py +86 -34
- utilities/os.py +49 -2
- utilities/parse.py +2 -2
- utilities/pathlib.py +66 -34
- utilities/permissions.py +297 -0
- utilities/platform.py +5 -5
- utilities/polars.py +932 -420
- utilities/polars_ols.py +1 -1
- utilities/postgres.py +296 -174
- utilities/pottery.py +8 -73
- utilities/pqdm.py +3 -3
- utilities/pwd.py +28 -0
- utilities/pydantic.py +11 -0
- utilities/pydantic_settings.py +240 -0
- utilities/pydantic_settings_sops.py +76 -0
- utilities/pyinstrument.py +5 -5
- utilities/pytest.py +155 -46
- utilities/pytest_plugins/pytest_randomly.py +1 -1
- utilities/pytest_plugins/pytest_regressions.py +7 -3
- utilities/pytest_regressions.py +2 -3
- utilities/random.py +11 -6
- utilities/re.py +1 -1
- utilities/redis.py +101 -64
- utilities/sentinel.py +10 -0
- utilities/shelve.py +4 -1
- utilities/shutil.py +25 -0
- utilities/slack_sdk.py +8 -3
- utilities/sqlalchemy.py +422 -352
- utilities/sqlalchemy_polars.py +28 -52
- utilities/string.py +1 -1
- utilities/subprocess.py +864 -0
- utilities/tempfile.py +62 -4
- utilities/testbook.py +50 -0
- utilities/text.py +165 -42
- utilities/timer.py +2 -2
- utilities/traceback.py +46 -36
- utilities/types.py +62 -23
- utilities/typing.py +479 -19
- utilities/uuid.py +42 -5
- utilities/version.py +27 -26
- utilities/whenever.py +661 -151
- utilities/zoneinfo.py +80 -22
- dycw_utilities-0.148.5.dist-info/METADATA +0 -41
- dycw_utilities-0.148.5.dist-info/RECORD +0 -95
- dycw_utilities-0.148.5.dist-info/WHEEL +0 -4
- dycw_utilities-0.148.5.dist-info/licenses/LICENSE +0 -21
- utilities/period.py +0 -237
- utilities/typed_settings.py +0 -144
utilities/pytest.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Callable
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from functools import partial, wraps
|
|
5
6
|
from inspect import iscoroutinefunction
|
|
6
7
|
from os import environ
|
|
7
8
|
from pathlib import Path
|
|
9
|
+
from re import sub
|
|
10
|
+
from types import FunctionType
|
|
8
11
|
from typing import TYPE_CHECKING, Any, assert_never, cast, override
|
|
9
12
|
|
|
10
13
|
from whenever import ZonedDateTime
|
|
@@ -13,46 +16,67 @@ from utilities.atomicwrites import writer
|
|
|
13
16
|
from utilities.functools import cache
|
|
14
17
|
from utilities.hashlib import md5_hash
|
|
15
18
|
from utilities.os import get_env_var
|
|
16
|
-
from utilities.pathlib import
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
IS_NOT_WINDOWS,
|
|
23
|
-
IS_WINDOWS,
|
|
19
|
+
from utilities.pathlib import (
|
|
20
|
+
_GetTailEmptyError,
|
|
21
|
+
ensure_suffix,
|
|
22
|
+
get_root,
|
|
23
|
+
get_tail,
|
|
24
|
+
module_path,
|
|
24
25
|
)
|
|
25
|
-
from utilities.
|
|
26
|
+
from utilities.platform import IS_LINUX, IS_MAC, IS_NOT_LINUX, IS_NOT_MAC
|
|
27
|
+
from utilities.random import bernoulli
|
|
28
|
+
from utilities.text import to_bool
|
|
29
|
+
from utilities.types import MaybeCallableBoolLike, MaybeCoro, Seed
|
|
26
30
|
from utilities.whenever import SECOND, get_now_local
|
|
27
31
|
|
|
28
32
|
if TYPE_CHECKING:
|
|
29
|
-
from collections.abc import
|
|
33
|
+
from collections.abc import Iterable
|
|
30
34
|
|
|
31
|
-
from utilities.types import Coro, Delta, PathLike
|
|
32
|
-
|
|
33
|
-
try: # WARNING: this package cannot use unguarded `pytest` imports
|
|
34
35
|
from _pytest.config import Config
|
|
35
36
|
from _pytest.config.argparsing import Parser
|
|
36
37
|
from _pytest.python import Function
|
|
37
|
-
|
|
38
|
+
|
|
39
|
+
from utilities.types import Coro, Delta, PathLike
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
IS_CI = "CI" in environ
|
|
43
|
+
IS_CI_AND_NOT_LINUX = IS_CI and IS_NOT_LINUX
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
try: # WARNING: this package cannot use unguarded `pytest` imports
|
|
47
|
+
from pytest import mark
|
|
38
48
|
except ModuleNotFoundError: # pragma: no cover
|
|
39
|
-
from typing import Any as Config
|
|
40
|
-
from typing import Any as Function
|
|
41
|
-
from typing import Any as Parser
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
def skipif_ci[F: Callable](func: F) -> F:
|
|
51
|
+
return func
|
|
52
|
+
|
|
53
|
+
def skipif_mac[F: Callable](func: F) -> F:
|
|
54
|
+
return func
|
|
55
|
+
|
|
56
|
+
def skipif_linux[F: Callable](func: F) -> F:
|
|
57
|
+
return func
|
|
58
|
+
|
|
59
|
+
def skipif_not_mac[F: Callable](func: F) -> F:
|
|
60
|
+
return func
|
|
61
|
+
|
|
62
|
+
def skipif_not_linux[F: Callable](func: F) -> F:
|
|
63
|
+
return func
|
|
64
|
+
|
|
65
|
+
def skipif_ci_and_not_linux[F: Callable](func: F) -> F:
|
|
66
|
+
return func
|
|
67
|
+
|
|
46
68
|
else:
|
|
47
|
-
|
|
69
|
+
skipif_ci = mark.skipif(IS_CI, reason="Skipped for CI")
|
|
48
70
|
skipif_mac = mark.skipif(IS_MAC, reason="Skipped for Mac")
|
|
49
71
|
skipif_linux = mark.skipif(IS_LINUX, reason="Skipped for Linux")
|
|
50
|
-
skipif_not_windows = mark.skipif(IS_NOT_WINDOWS, reason="Skipped for non-Windows")
|
|
51
72
|
skipif_not_mac = mark.skipif(IS_NOT_MAC, reason="Skipped for non-Mac")
|
|
52
73
|
skipif_not_linux = mark.skipif(IS_NOT_LINUX, reason="Skipped for non-Linux")
|
|
74
|
+
skipif_ci_and_not_linux = mark.skipif(
|
|
75
|
+
IS_CI_AND_NOT_LINUX, reason="Skipped for CI/non-Linux"
|
|
76
|
+
)
|
|
53
77
|
|
|
54
78
|
|
|
55
|
-
def add_pytest_addoption(parser: Parser, options:
|
|
79
|
+
def add_pytest_addoption(parser: Parser, options: list[str], /) -> None:
|
|
56
80
|
"""Add the `--slow`, etc options to pytest.
|
|
57
81
|
|
|
58
82
|
Usage:
|
|
@@ -73,7 +97,7 @@ def add_pytest_addoption(parser: Parser, options: Sequence[str], /) -> None:
|
|
|
73
97
|
|
|
74
98
|
|
|
75
99
|
def add_pytest_collection_modifyitems(
|
|
76
|
-
config: Config, items: Iterable[Function], options:
|
|
100
|
+
config: Config, items: Iterable[Function], options: list[str], /
|
|
77
101
|
) -> None:
|
|
78
102
|
"""Add the @mark.skips as necessary.
|
|
79
103
|
|
|
@@ -82,6 +106,8 @@ def add_pytest_collection_modifyitems(
|
|
|
82
106
|
def pytest_collection_modifyitems(config, items):
|
|
83
107
|
add_pytest_collection_modifyitems(config, items, ["slow"])
|
|
84
108
|
"""
|
|
109
|
+
from pytest import mark
|
|
110
|
+
|
|
85
111
|
options = list(options)
|
|
86
112
|
missing = {opt for opt in options if not config.getoption(f"--{opt}")}
|
|
87
113
|
for item in items:
|
|
@@ -111,9 +137,10 @@ def add_pytest_configure(config: Config, options: Iterable[tuple[str, str]], /)
|
|
|
111
137
|
##
|
|
112
138
|
|
|
113
139
|
|
|
114
|
-
def
|
|
115
|
-
|
|
116
|
-
|
|
140
|
+
def make_ids(obj: Any, /) -> str:
|
|
141
|
+
if isinstance(obj, FunctionType):
|
|
142
|
+
return sub(r"\s+at +0x[0-9a-fA-F]+", "", repr(obj))
|
|
143
|
+
return repr(obj)
|
|
117
144
|
|
|
118
145
|
|
|
119
146
|
##
|
|
@@ -126,10 +153,15 @@ def node_id_path(
|
|
|
126
153
|
path_file, *parts = node_id.split("::")
|
|
127
154
|
path_file = Path(path_file)
|
|
128
155
|
if path_file.suffix != ".py":
|
|
129
|
-
raise
|
|
156
|
+
raise _NodeIdToPathNotPythonFileError(node_id=node_id)
|
|
130
157
|
path = path_file.with_suffix("")
|
|
131
158
|
if root is not None:
|
|
132
|
-
|
|
159
|
+
try:
|
|
160
|
+
path = get_tail(path, root)
|
|
161
|
+
except _GetTailEmptyError as error:
|
|
162
|
+
raise _NodeIdToPathNotGetTailError(
|
|
163
|
+
node_id=node_id, path=error.path, root=error.root
|
|
164
|
+
) from None
|
|
133
165
|
path = Path(module_path(path), "__".join(parts))
|
|
134
166
|
if suffix is not None:
|
|
135
167
|
path = ensure_suffix(path, suffix)
|
|
@@ -140,11 +172,86 @@ def node_id_path(
|
|
|
140
172
|
class NodeIdToPathError(Exception):
|
|
141
173
|
node_id: str
|
|
142
174
|
|
|
175
|
+
|
|
176
|
+
@dataclass(kw_only=True, slots=True)
|
|
177
|
+
class _NodeIdToPathNotPythonFileError(NodeIdToPathError):
|
|
143
178
|
@override
|
|
144
179
|
def __str__(self) -> str:
|
|
145
180
|
return f"Node ID must be a Python file; got {self.node_id!r}"
|
|
146
181
|
|
|
147
182
|
|
|
183
|
+
@dataclass(kw_only=True, slots=True)
|
|
184
|
+
class _NodeIdToPathNotGetTailError(NodeIdToPathError):
|
|
185
|
+
path: PathLike
|
|
186
|
+
root: PathLike
|
|
187
|
+
|
|
188
|
+
@override
|
|
189
|
+
def __str__(self) -> str:
|
|
190
|
+
return (
|
|
191
|
+
f"Unable to get the tail of {str(self.path)!r} with root {str(self.root)!r}"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
##
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def run_frac[F: Callable[..., MaybeCoro[None]]](
|
|
199
|
+
*,
|
|
200
|
+
predicate: MaybeCallableBoolLike | None = None,
|
|
201
|
+
frac: float = 0.5,
|
|
202
|
+
seed: Seed | None = None,
|
|
203
|
+
) -> Callable[[F], F]:
|
|
204
|
+
"""Run a test only a fraction of the time.."""
|
|
205
|
+
return cast(
|
|
206
|
+
"Any", partial(_run_frac_inner, predicate=predicate, frac=frac, seed=seed)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _run_frac_inner[F: Callable[..., MaybeCoro[None]]](
|
|
211
|
+
func: F,
|
|
212
|
+
/,
|
|
213
|
+
*,
|
|
214
|
+
predicate: MaybeCallableBoolLike | None = None,
|
|
215
|
+
frac: float = 0.5,
|
|
216
|
+
seed: Seed | None = None,
|
|
217
|
+
) -> F:
|
|
218
|
+
match bool(iscoroutinefunction(func)):
|
|
219
|
+
case False:
|
|
220
|
+
|
|
221
|
+
@wraps(func)
|
|
222
|
+
def run_frac_sync(*args: Any, **kwargs: Any) -> None:
|
|
223
|
+
_skipif_frac(predicate=predicate, frac=frac, seed=seed)
|
|
224
|
+
cast("Callable[..., None]", func)(*args, **kwargs)
|
|
225
|
+
|
|
226
|
+
return cast("Any", run_frac_sync)
|
|
227
|
+
|
|
228
|
+
case True:
|
|
229
|
+
|
|
230
|
+
@wraps(func)
|
|
231
|
+
async def run_frac_async(*args: Any, **kwargs: Any) -> None:
|
|
232
|
+
_skipif_frac(predicate=predicate, frac=frac, seed=seed)
|
|
233
|
+
await cast("Callable[..., Coro[None]]", func)(*args, **kwargs)
|
|
234
|
+
|
|
235
|
+
return cast("Any", run_frac_async)
|
|
236
|
+
|
|
237
|
+
case never:
|
|
238
|
+
assert_never(never)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _skipif_frac(
|
|
242
|
+
*,
|
|
243
|
+
predicate: MaybeCallableBoolLike | None = None,
|
|
244
|
+
frac: float = 0.5,
|
|
245
|
+
seed: Seed | None = None,
|
|
246
|
+
) -> None:
|
|
247
|
+
from pytest import skip
|
|
248
|
+
|
|
249
|
+
if ((predicate is None) or to_bool(predicate)) and bernoulli(
|
|
250
|
+
true=1 - frac, seed=seed
|
|
251
|
+
):
|
|
252
|
+
skip(reason=f"{_get_name()} skipped (run {frac:.0%})")
|
|
253
|
+
|
|
254
|
+
|
|
148
255
|
##
|
|
149
256
|
|
|
150
257
|
|
|
@@ -163,7 +270,6 @@ def _throttle_inner[F: Callable[..., MaybeCoro[None]]](
|
|
|
163
270
|
delta: Delta = SECOND,
|
|
164
271
|
on_try: bool = False,
|
|
165
272
|
) -> F:
|
|
166
|
-
"""Throttle a test function/method."""
|
|
167
273
|
if get_env_var("THROTTLE", nullable=True) is not None:
|
|
168
274
|
return func
|
|
169
275
|
match bool(iscoroutinefunction(func)), on_try:
|
|
@@ -173,7 +279,7 @@ def _throttle_inner[F: Callable[..., MaybeCoro[None]]](
|
|
|
173
279
|
def throttle_sync_on_pass(*args: Any, **kwargs: Any) -> None:
|
|
174
280
|
_skipif_recent(root=root, delta=delta)
|
|
175
281
|
cast("Callable[..., None]", func)(*args, **kwargs)
|
|
176
|
-
_write(root
|
|
282
|
+
_write(root)
|
|
177
283
|
|
|
178
284
|
return cast("Any", throttle_sync_on_pass)
|
|
179
285
|
|
|
@@ -182,7 +288,7 @@ def _throttle_inner[F: Callable[..., MaybeCoro[None]]](
|
|
|
182
288
|
@wraps(func)
|
|
183
289
|
def throttle_sync_on_try(*args: Any, **kwargs: Any) -> None:
|
|
184
290
|
_skipif_recent(root=root, delta=delta)
|
|
185
|
-
_write(root
|
|
291
|
+
_write(root)
|
|
186
292
|
cast("Callable[..., None]", func)(*args, **kwargs)
|
|
187
293
|
|
|
188
294
|
return cast("Any", throttle_sync_on_try)
|
|
@@ -193,7 +299,7 @@ def _throttle_inner[F: Callable[..., MaybeCoro[None]]](
|
|
|
193
299
|
async def throttle_async_on_pass(*args: Any, **kwargs: Any) -> None:
|
|
194
300
|
_skipif_recent(root=root, delta=delta)
|
|
195
301
|
await cast("Callable[..., Coro[None]]", func)(*args, **kwargs)
|
|
196
|
-
_write(root
|
|
302
|
+
_write(root)
|
|
197
303
|
|
|
198
304
|
return cast("Any", throttle_async_on_pass)
|
|
199
305
|
|
|
@@ -202,25 +308,25 @@ def _throttle_inner[F: Callable[..., MaybeCoro[None]]](
|
|
|
202
308
|
@wraps(func)
|
|
203
309
|
async def throttle_async_on_try(*args: Any, **kwargs: Any) -> None:
|
|
204
310
|
_skipif_recent(root=root, delta=delta)
|
|
205
|
-
_write(root
|
|
311
|
+
_write(root)
|
|
206
312
|
await cast("Callable[..., Coro[None]]", func)(*args, **kwargs)
|
|
207
313
|
|
|
208
314
|
return cast("Any", throttle_async_on_try)
|
|
209
315
|
|
|
210
|
-
case
|
|
316
|
+
case never:
|
|
211
317
|
assert_never(never)
|
|
212
318
|
|
|
213
319
|
|
|
214
320
|
def _skipif_recent(*, root: PathLike | None = None, delta: Delta = SECOND) -> None:
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
path = _get_path(root
|
|
321
|
+
from pytest import skip
|
|
322
|
+
|
|
323
|
+
path = _get_path(root)
|
|
218
324
|
try:
|
|
219
325
|
contents = path.read_text()
|
|
220
326
|
except FileNotFoundError:
|
|
221
327
|
return
|
|
222
328
|
try:
|
|
223
|
-
last = ZonedDateTime.
|
|
329
|
+
last = ZonedDateTime.parse_iso(contents)
|
|
224
330
|
except ValueError:
|
|
225
331
|
return
|
|
226
332
|
now = get_now_local()
|
|
@@ -229,7 +335,7 @@ def _skipif_recent(*, root: PathLike | None = None, delta: Delta = SECOND) -> No
|
|
|
229
335
|
_ = skip(reason=f"{_get_name()} throttled (age {age})")
|
|
230
336
|
|
|
231
337
|
|
|
232
|
-
def _get_path(
|
|
338
|
+
def _get_path(root: PathLike | None = None, /) -> Path:
|
|
233
339
|
if root is None:
|
|
234
340
|
root_use = get_root().joinpath(".pytest_cache", "throttle") # pragma: no cover
|
|
235
341
|
else:
|
|
@@ -246,24 +352,27 @@ def _get_name() -> str:
|
|
|
246
352
|
return environ["PYTEST_CURRENT_TEST"]
|
|
247
353
|
|
|
248
354
|
|
|
249
|
-
def _write(
|
|
250
|
-
path = _get_path(root
|
|
355
|
+
def _write(root: PathLike | None = None, /) -> None:
|
|
356
|
+
path = _get_path(root)
|
|
251
357
|
with writer(path, overwrite=True) as temp:
|
|
252
|
-
_ = temp.write_text(get_now_local().
|
|
358
|
+
_ = temp.write_text(get_now_local().format_iso())
|
|
253
359
|
|
|
254
360
|
|
|
255
361
|
__all__ = [
|
|
362
|
+
"IS_CI",
|
|
363
|
+
"IS_CI_AND_NOT_LINUX",
|
|
256
364
|
"NodeIdToPathError",
|
|
257
365
|
"add_pytest_addoption",
|
|
258
366
|
"add_pytest_collection_modifyitems",
|
|
259
367
|
"add_pytest_configure",
|
|
260
|
-
"
|
|
368
|
+
"make_ids",
|
|
261
369
|
"node_id_path",
|
|
370
|
+
"run_frac",
|
|
371
|
+
"skipif_ci",
|
|
372
|
+
"skipif_ci_and_not_linux",
|
|
262
373
|
"skipif_linux",
|
|
263
374
|
"skipif_mac",
|
|
264
375
|
"skipif_not_linux",
|
|
265
376
|
"skipif_not_mac",
|
|
266
|
-
"skipif_not_windows",
|
|
267
|
-
"skipif_windows",
|
|
268
377
|
"throttle",
|
|
269
378
|
]
|
|
@@ -41,12 +41,16 @@ else:
|
|
|
41
41
|
|
|
42
42
|
def _get_path(request: FixtureRequest, /) -> Path:
|
|
43
43
|
from utilities.pathlib import get_root
|
|
44
|
-
from utilities.pytest import node_id_path
|
|
44
|
+
from utilities.pytest import _NodeIdToPathNotGetTailError, node_id_path
|
|
45
45
|
|
|
46
46
|
path = Path(cast("Any", request).fspath)
|
|
47
47
|
root = Path("src", "tests")
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
try:
|
|
49
|
+
tail = node_id_path(request.node.nodeid, root=root)
|
|
50
|
+
except _NodeIdToPathNotGetTailError:
|
|
51
|
+
root = Path("tests")
|
|
52
|
+
tail = node_id_path(request.node.nodeid, root=root)
|
|
53
|
+
return get_root(path).joinpath(root, "regressions", tail)
|
|
50
54
|
|
|
51
55
|
|
|
52
56
|
__all__ = ["orjson_regression", "polars_regression"]
|
utilities/pytest_regressions.py
CHANGED
|
@@ -97,7 +97,6 @@ class PolarsRegressionFixture:
|
|
|
97
97
|
"describe": obj.describe(percentiles=[i / 10 for i in range(1, 10)]).rows(
|
|
98
98
|
named=True
|
|
99
99
|
),
|
|
100
|
-
"estimated_size": obj.estimated_size(),
|
|
101
100
|
"is_empty": obj.is_empty(),
|
|
102
101
|
"n_unique": obj.n_unique(),
|
|
103
102
|
}
|
|
@@ -115,9 +114,9 @@ class PolarsRegressionFixture:
|
|
|
115
114
|
col(column).approx_n_unique()
|
|
116
115
|
).item()
|
|
117
116
|
data["approx_n_unique"] = approx_n_unique
|
|
118
|
-
data["glimpse"] = df.glimpse(
|
|
117
|
+
data["glimpse"] = df.glimpse(return_type="string")
|
|
119
118
|
data["null_count"] = df.null_count().row(0, named=True)
|
|
120
|
-
case
|
|
119
|
+
case never:
|
|
121
120
|
assert_never(never)
|
|
122
121
|
self._fixture.check(data, suffix=suffix)
|
|
123
122
|
|
utilities/random.py
CHANGED
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
from random import Random, SystemRandom
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
|
+
from utilities.functools import cache
|
|
7
|
+
|
|
6
8
|
if TYPE_CHECKING:
|
|
7
9
|
from collections.abc import Iterable
|
|
8
10
|
|
|
@@ -17,16 +19,16 @@ SYSTEM_RANDOM = SystemRandom()
|
|
|
17
19
|
|
|
18
20
|
def bernoulli(*, true: float = 0.5, seed: Seed | None = None) -> bool:
|
|
19
21
|
"""Return a Bernoulli random variate."""
|
|
20
|
-
|
|
21
|
-
return bool(
|
|
22
|
+
state = get_state(seed)
|
|
23
|
+
return bool(state.binomialvariate(p=true))
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
##
|
|
25
27
|
|
|
26
28
|
|
|
27
|
-
def get_docker_name(
|
|
29
|
+
def get_docker_name(seed: Seed | None = None, /) -> str:
|
|
28
30
|
"""Get a docker name."""
|
|
29
|
-
state = get_state(seed
|
|
31
|
+
state = get_state(seed)
|
|
30
32
|
prefix = state.choice(_DOCKER_PREFIXES)
|
|
31
33
|
suffix = state.choice(_DOCKER_SUFFIXES)
|
|
32
34
|
digit = state.randint(0, 9)
|
|
@@ -47,16 +49,19 @@ _DOCKER_SUFFIXES = [
|
|
|
47
49
|
##
|
|
48
50
|
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
@cache
|
|
53
|
+
def get_state(seed: Seed | None = None, /) -> Random:
|
|
51
54
|
"""Get a random state."""
|
|
52
55
|
return seed if isinstance(seed, Random) else Random(x=seed)
|
|
53
56
|
|
|
54
57
|
|
|
55
58
|
##
|
|
59
|
+
|
|
60
|
+
|
|
56
61
|
def shuffle[T](iterable: Iterable[T], /, *, seed: Seed | None = None) -> list[T]:
|
|
57
62
|
"""Shuffle an iterable."""
|
|
58
63
|
copy = list(iterable).copy()
|
|
59
|
-
state = get_state(seed
|
|
64
|
+
state = get_state(seed)
|
|
60
65
|
state.shuffle(copy)
|
|
61
66
|
return copy
|
|
62
67
|
|