dycw-utilities 0.166.30__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.
Files changed (96) hide show
  1. dycw_utilities-0.185.8.dist-info/METADATA +33 -0
  2. dycw_utilities-0.185.8.dist-info/RECORD +90 -0
  3. {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +1 -1
  4. {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/entry_points.txt +1 -0
  5. utilities/__init__.py +1 -1
  6. utilities/altair.py +17 -10
  7. utilities/asyncio.py +50 -72
  8. utilities/atools.py +9 -11
  9. utilities/cachetools.py +16 -11
  10. utilities/click.py +76 -19
  11. utilities/concurrent.py +1 -1
  12. utilities/constants.py +492 -0
  13. utilities/contextlib.py +23 -30
  14. utilities/contextvars.py +1 -23
  15. utilities/core.py +2581 -0
  16. utilities/dataclasses.py +16 -119
  17. utilities/docker.py +387 -0
  18. utilities/enum.py +1 -1
  19. utilities/errors.py +2 -16
  20. utilities/fastapi.py +5 -5
  21. utilities/fpdf2.py +2 -1
  22. utilities/functions.py +34 -265
  23. utilities/http.py +2 -3
  24. utilities/hypothesis.py +84 -29
  25. utilities/importlib.py +17 -1
  26. utilities/iterables.py +39 -575
  27. utilities/jinja2.py +145 -0
  28. utilities/jupyter.py +5 -3
  29. utilities/libcst.py +1 -1
  30. utilities/lightweight_charts.py +4 -6
  31. utilities/logging.py +24 -24
  32. utilities/math.py +1 -36
  33. utilities/more_itertools.py +4 -6
  34. utilities/numpy.py +2 -1
  35. utilities/operator.py +2 -2
  36. utilities/orjson.py +42 -43
  37. utilities/os.py +4 -147
  38. utilities/packaging.py +129 -0
  39. utilities/parse.py +35 -15
  40. utilities/pathlib.py +3 -120
  41. utilities/platform.py +8 -90
  42. utilities/polars.py +38 -32
  43. utilities/postgres.py +37 -33
  44. utilities/pottery.py +20 -18
  45. utilities/pqdm.py +3 -4
  46. utilities/psutil.py +2 -3
  47. utilities/pydantic.py +25 -0
  48. utilities/pydantic_settings.py +87 -16
  49. utilities/pydantic_settings_sops.py +16 -3
  50. utilities/pyinstrument.py +4 -4
  51. utilities/pytest.py +96 -125
  52. utilities/pytest_plugins/pytest_regressions.py +2 -2
  53. utilities/pytest_regressions.py +32 -11
  54. utilities/random.py +2 -8
  55. utilities/redis.py +98 -94
  56. utilities/reprlib.py +11 -118
  57. utilities/shellingham.py +66 -0
  58. utilities/shutil.py +25 -0
  59. utilities/slack_sdk.py +13 -12
  60. utilities/sqlalchemy.py +57 -30
  61. utilities/sqlalchemy_polars.py +16 -25
  62. utilities/subprocess.py +2590 -0
  63. utilities/tabulate.py +32 -0
  64. utilities/testbook.py +8 -8
  65. utilities/text.py +24 -99
  66. utilities/throttle.py +159 -0
  67. utilities/time.py +18 -0
  68. utilities/timer.py +31 -14
  69. utilities/traceback.py +16 -23
  70. utilities/types.py +42 -2
  71. utilities/typing.py +26 -14
  72. utilities/uuid.py +1 -1
  73. utilities/version.py +202 -45
  74. utilities/whenever.py +53 -150
  75. dycw_utilities-0.166.30.dist-info/METADATA +0 -41
  76. dycw_utilities-0.166.30.dist-info/RECORD +0 -98
  77. dycw_utilities-0.166.30.dist-info/licenses/LICENSE +0 -21
  78. utilities/aeventkit.py +0 -388
  79. utilities/atomicwrites.py +0 -182
  80. utilities/cryptography.py +0 -41
  81. utilities/getpass.py +0 -8
  82. utilities/git.py +0 -19
  83. utilities/gzip.py +0 -31
  84. utilities/json.py +0 -70
  85. utilities/pickle.py +0 -25
  86. utilities/re.py +0 -156
  87. utilities/sentinel.py +0 -73
  88. utilities/socket.py +0 -8
  89. utilities/string.py +0 -20
  90. utilities/tempfile.py +0 -77
  91. utilities/typed_settings.py +0 -152
  92. utilities/tzdata.py +0 -11
  93. utilities/tzlocal.py +0 -28
  94. utilities/warnings.py +0 -65
  95. utilities/zipfile.py +0 -25
  96. utilities/zoneinfo.py +0 -133
utilities/dataclasses.py CHANGED
@@ -2,17 +2,27 @@ from __future__ import annotations
2
2
 
3
3
  from collections.abc import Mapping
4
4
  from contextlib import suppress
5
- from dataclasses import MISSING, dataclass, field, fields, replace
6
- from typing import TYPE_CHECKING, Any, Literal, assert_never, overload, override
5
+ from dataclasses import MISSING, dataclass, field, fields
6
+ from typing import TYPE_CHECKING, Any, assert_never, overload, override
7
7
 
8
- from utilities.errors import ImpossibleCaseError
9
- from utilities.functions import get_class_name
10
- from utilities.iterables import (
8
+ from utilities.constants import (
9
+ BRACKETS,
10
+ LIST_SEPARATOR,
11
+ PAIR_SEPARATOR,
12
+ Sentinel,
13
+ sentinel,
14
+ )
15
+ from utilities.core import (
16
+ ExtractGroupError,
11
17
  OneStrEmptyError,
12
18
  OneStrNonUniqueError,
13
- cmp_nullable,
19
+ extract_group,
20
+ get_class_name,
21
+ is_sentinel,
14
22
  one_str,
15
23
  )
24
+ from utilities.errors import ImpossibleCaseError
25
+ from utilities.iterables import cmp_nullable
16
26
  from utilities.operator import is_equal
17
27
  from utilities.parse import (
18
28
  _ParseObjectExtraNonUniqueError,
@@ -20,12 +30,7 @@ from utilities.parse import (
20
30
  parse_object,
21
31
  serialize_object,
22
32
  )
23
- from utilities.re import ExtractGroupError, extract_group
24
- from utilities.sentinel import Sentinel, is_sentinel, sentinel
25
33
  from utilities.text import (
26
- BRACKETS,
27
- LIST_SEPARATOR,
28
- PAIR_SEPARATOR,
29
34
  _SplitKeyValuePairsDuplicateKeysError,
30
35
  _SplitKeyValuePairsSplitError,
31
36
  split_key_value_pairs,
@@ -46,85 +51,6 @@ if TYPE_CHECKING:
46
51
  )
47
52
 
48
53
 
49
- def dataclass_repr[T](
50
- obj: Dataclass,
51
- /,
52
- *,
53
- globalns: StrMapping | None = None,
54
- localns: StrMapping | None = None,
55
- warn_name_errors: bool = False,
56
- include: Iterable[str] | None = None,
57
- exclude: Iterable[str] | None = None,
58
- rel_tol: float | None = None,
59
- abs_tol: float | None = None,
60
- extra: Mapping[type[T], Callable[[T, T], bool]] | None = None,
61
- defaults: bool = False,
62
- recursive: bool = False,
63
- ) -> str:
64
- """Repr a dataclass, without its defaults."""
65
- out: dict[str, str] = {}
66
- for fld in yield_fields(
67
- obj, globalns=globalns, localns=localns, warn_name_errors=warn_name_errors
68
- ):
69
- if (
70
- fld.keep(
71
- include=include,
72
- exclude=exclude,
73
- rel_tol=rel_tol,
74
- abs_tol=abs_tol,
75
- extra=extra,
76
- defaults=defaults,
77
- )
78
- and fld.repr
79
- ):
80
- if recursive:
81
- if is_dataclass_instance(fld.value):
82
- repr_ = dataclass_repr(
83
- fld.value,
84
- globalns=globalns,
85
- localns=localns,
86
- warn_name_errors=warn_name_errors,
87
- include=include,
88
- exclude=exclude,
89
- rel_tol=rel_tol,
90
- abs_tol=abs_tol,
91
- extra=extra,
92
- defaults=defaults,
93
- recursive=recursive,
94
- )
95
- elif isinstance(fld.value, list):
96
- repr_ = [
97
- dataclass_repr(
98
- v,
99
- globalns=globalns,
100
- localns=localns,
101
- warn_name_errors=warn_name_errors,
102
- include=include,
103
- exclude=exclude,
104
- rel_tol=rel_tol,
105
- abs_tol=abs_tol,
106
- extra=extra,
107
- defaults=defaults,
108
- recursive=recursive,
109
- )
110
- if is_dataclass_instance(v)
111
- else repr(v)
112
- for v in fld.value
113
- ]
114
- repr_ = f"[{', '.join(repr_)}]"
115
- else:
116
- repr_ = repr(fld.value)
117
- else:
118
- repr_ = repr(fld.value)
119
- out[fld.name] = repr_
120
- cls = get_class_name(obj)
121
- joined = ", ".join(f"{k}={v}" for k, v in out.items())
122
- return f"{cls}({joined})"
123
-
124
-
125
- ##
126
-
127
-
128
54
  def dataclass_to_dict[T](
129
55
  obj: Dataclass,
130
56
  /,
@@ -411,33 +337,6 @@ class _OneFieldNonUniqueError(OneFieldError):
411
337
  ##
412
338
 
413
339
 
414
- @overload
415
- def replace_non_sentinel(
416
- obj: Dataclass, /, *, in_place: Literal[True], **kwargs: Any
417
- ) -> None: ...
418
- @overload
419
- def replace_non_sentinel[T: Dataclass](
420
- obj: T, /, *, in_place: Literal[False] = False, **kwargs: Any
421
- ) -> T: ...
422
- @overload
423
- def replace_non_sentinel[T: Dataclass](
424
- obj: T, /, *, in_place: bool = False, **kwargs: Any
425
- ) -> T | None: ...
426
- def replace_non_sentinel[T: Dataclass](
427
- obj: T, /, *, in_place: bool = False, **kwargs: Any
428
- ) -> T | None:
429
- """Replace attributes on a dataclass, filtering out sentinel values."""
430
- if in_place:
431
- for k, v in kwargs.items():
432
- if not is_sentinel(v):
433
- setattr(obj, k, v)
434
- return None
435
- return replace(obj, **{k: v for k, v in kwargs.items() if not is_sentinel(v)})
436
-
437
-
438
- ##
439
-
440
-
441
340
  def serialize_dataclass[T](
442
341
  obj: Dataclass,
443
342
  /,
@@ -1041,13 +940,11 @@ __all__ = [
1041
940
  "ParseDataClassError",
1042
941
  "StrMappingToFieldMappingError",
1043
942
  "YieldFieldsError",
1044
- "dataclass_repr",
1045
943
  "dataclass_to_dict",
1046
944
  "is_nullable_lt",
1047
945
  "mapping_to_dataclass",
1048
946
  "one_field",
1049
947
  "parse_dataclass",
1050
- "replace_non_sentinel",
1051
948
  "str_mapping_to_field_mapping",
1052
949
  "yield_fields",
1053
950
  ]
utilities/docker.py ADDED
@@ -0,0 +1,387 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING, Literal, overload
5
+
6
+ from utilities.contextlib import enhanced_context_manager
7
+ from utilities.core import always_iterable
8
+ from utilities.errors import ImpossibleCaseError
9
+ from utilities.logging import to_logger
10
+ from utilities.subprocess import (
11
+ MKTEMP_DIR_CMD,
12
+ maybe_sudo_cmd,
13
+ mkdir,
14
+ mkdir_cmd,
15
+ rm_cmd,
16
+ run,
17
+ )
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Iterator
21
+
22
+ from utilities.types import (
23
+ LoggerLike,
24
+ MaybeIterable,
25
+ PathLike,
26
+ Retry,
27
+ StrStrMapping,
28
+ )
29
+
30
+
31
+ def docker_compose_down(
32
+ *,
33
+ files: MaybeIterable[PathLike] | None = None,
34
+ print: bool = False, # noqa: A002
35
+ print_stdout: bool = False,
36
+ print_stderr: bool = False,
37
+ ) -> None:
38
+ """Stop and remove containers."""
39
+ args = docker_compose_down_cmd(files=files) # pragma: no cover
40
+ run( # pragma: no cover
41
+ *args, print=print, print_stdout=print_stdout, print_stderr=print_stderr
42
+ )
43
+
44
+
45
+ def docker_compose_down_cmd(
46
+ *, files: MaybeIterable[PathLike] | None = None
47
+ ) -> list[str]:
48
+ """Command to use 'docker compose down' to stop and remove containers."""
49
+ return _docker_compose_cmd("down", files=files)
50
+
51
+
52
+ def docker_compose_pull(
53
+ *,
54
+ files: MaybeIterable[PathLike] | None = None,
55
+ print: bool = False, # noqa: A002
56
+ print_stdout: bool = False,
57
+ print_stderr: bool = False,
58
+ ) -> None:
59
+ """Pull service images."""
60
+ args = docker_compose_pull_cmd(files=files) # pragma: no cover
61
+ run( # pragma: no cover
62
+ *args, print=print, print_stdout=print_stdout, print_stderr=print_stderr
63
+ )
64
+
65
+
66
+ def docker_compose_pull_cmd(
67
+ *, files: MaybeIterable[PathLike] | None = None
68
+ ) -> list[str]:
69
+ """Command to use 'docker compose pull' to pull service images."""
70
+ return _docker_compose_cmd("pull", files=files)
71
+
72
+
73
+ def docker_compose_up(
74
+ *,
75
+ files: MaybeIterable[PathLike] | None = None,
76
+ detach: bool = True,
77
+ print: bool = False, # noqa: A002
78
+ print_stdout: bool = False,
79
+ print_stderr: bool = False,
80
+ ) -> None:
81
+ """Create and start containers."""
82
+ args = docker_compose_up_cmd(files=files, detach=detach) # pragma: no cover
83
+ run( # pragma: no cover
84
+ *args, print=print, print_stdout=print_stdout, print_stderr=print_stderr
85
+ )
86
+
87
+
88
+ def docker_compose_up_cmd(
89
+ *, files: MaybeIterable[PathLike] | None = None, detach: bool = True
90
+ ) -> list[str]:
91
+ """Command to use 'docker compose up' to create and start containers."""
92
+ args: list[str] = []
93
+ if detach:
94
+ args.append("--detach")
95
+ return _docker_compose_cmd("up", *args, files=files)
96
+
97
+
98
+ def _docker_compose_cmd(
99
+ cmd: str, /, *args: str, files: MaybeIterable[PathLike] | None = None
100
+ ) -> list[str]:
101
+ all_args: list[str] = ["docker", "compose"]
102
+ if files is not None:
103
+ for file in always_iterable(files):
104
+ all_args.extend(["--file", str(file)])
105
+ return [*all_args, cmd, *args]
106
+
107
+
108
+ ##
109
+
110
+
111
+ @overload
112
+ def docker_cp(
113
+ src: tuple[str, PathLike],
114
+ dest: PathLike,
115
+ /,
116
+ *,
117
+ sudo: bool = False,
118
+ logger: LoggerLike | None = None,
119
+ ) -> None: ...
120
+ @overload
121
+ def docker_cp(
122
+ src: PathLike,
123
+ dest: tuple[str, PathLike],
124
+ /,
125
+ *,
126
+ sudo: bool = False,
127
+ logger: LoggerLike | None = None,
128
+ ) -> None: ...
129
+ def docker_cp(
130
+ src: PathLike | tuple[str, PathLike],
131
+ dest: PathLike | tuple[str, PathLike],
132
+ /,
133
+ *,
134
+ sudo: bool = False,
135
+ logger: LoggerLike | None = None,
136
+ ) -> None:
137
+ """Copy between a container and the local file system."""
138
+ match src, dest: # skipif-ci
139
+ case Path() | str(), (str() as cont, Path() | str() as dest_path):
140
+ docker_exec(
141
+ cont, *maybe_sudo_cmd(*mkdir_cmd(dest_path, parent=True), sudo=sudo)
142
+ )
143
+ run(*maybe_sudo_cmd(*docker_cp_cmd(src, dest), sudo=sudo), logger=logger)
144
+ case (str(), Path() | str()), Path() | str():
145
+ mkdir(dest, parent=True, sudo=sudo)
146
+ run(*maybe_sudo_cmd(*docker_cp_cmd(src, dest), sudo=sudo), logger=logger)
147
+ case _: # pragma: no cover
148
+ raise ImpossibleCaseError(case=[f"{src}", f"{dest=}"])
149
+
150
+
151
+ @overload
152
+ def docker_cp_cmd(src: tuple[str, PathLike], dest: PathLike, /) -> list[str]: ...
153
+ @overload
154
+ def docker_cp_cmd(src: PathLike, dest: tuple[str, PathLike], /) -> list[str]: ...
155
+ def docker_cp_cmd(
156
+ src: PathLike | tuple[str, PathLike], dest: PathLike | tuple[str, PathLike], /
157
+ ) -> list[str]:
158
+ """Command to use 'docker cp' to copy between a container and the local file system."""
159
+ args: list[str] = ["docker", "cp"]
160
+ match src, dest:
161
+ case ((Path() | str()), (str() as cont, Path() | str() as path)):
162
+ return [*args, str(src), f"{cont}:{path}"]
163
+ case (str() as cont, (Path() | str()) as path), (Path() | str() as dest):
164
+ return [*args, f"{cont}:{path}", str(dest)]
165
+ case _: # pragma: no cover
166
+ raise ImpossibleCaseError(case=[f"{src}", f"{dest=}"])
167
+
168
+
169
+ ##
170
+
171
+
172
+ @overload
173
+ def docker_exec(
174
+ container: str,
175
+ cmd: str,
176
+ /,
177
+ *args: str,
178
+ env: StrStrMapping | None = None,
179
+ user: str | None = None,
180
+ workdir: PathLike | None = None,
181
+ input: str | None = None,
182
+ print: bool = False,
183
+ print_stdout: bool = False,
184
+ print_stderr: bool = False,
185
+ return_: Literal[True],
186
+ return_stdout: bool = False,
187
+ return_stderr: bool = False,
188
+ retry: Retry | None = None,
189
+ logger: LoggerLike | None = None,
190
+ **env_kwargs: str,
191
+ ) -> str: ...
192
+ @overload
193
+ def docker_exec(
194
+ container: str,
195
+ cmd: str,
196
+ /,
197
+ *args: str,
198
+ env: StrStrMapping | None = None,
199
+ user: str | None = None,
200
+ workdir: PathLike | None = None,
201
+ input: str | None = None,
202
+ print: bool = False,
203
+ print_stdout: bool = False,
204
+ print_stderr: bool = False,
205
+ return_: bool = False,
206
+ return_stdout: Literal[True],
207
+ return_stderr: bool = False,
208
+ retry: Retry | None = None,
209
+ logger: LoggerLike | None = None,
210
+ **env_kwargs: str,
211
+ ) -> str: ...
212
+ @overload
213
+ def docker_exec(
214
+ container: str,
215
+ cmd: str,
216
+ /,
217
+ *args: str,
218
+ env: StrStrMapping | None = None,
219
+ user: str | None = None,
220
+ workdir: PathLike | None = None,
221
+ input: str | None = None,
222
+ print: bool = False,
223
+ print_stdout: bool = False,
224
+ print_stderr: bool = False,
225
+ return_: bool = False,
226
+ return_stdout: bool = False,
227
+ return_stderr: Literal[True],
228
+ retry: Retry | None = None,
229
+ logger: LoggerLike | None = None,
230
+ **env_kwargs: str,
231
+ ) -> str: ...
232
+ @overload
233
+ def docker_exec(
234
+ container: str,
235
+ cmd: str,
236
+ /,
237
+ *args: str,
238
+ env: StrStrMapping | None = None,
239
+ user: str | None = None,
240
+ workdir: PathLike | None = None,
241
+ input: str | None = None,
242
+ print: bool = False,
243
+ print_stdout: bool = False,
244
+ print_stderr: bool = False,
245
+ return_: Literal[False] = False,
246
+ return_stdout: Literal[False] = False,
247
+ return_stderr: Literal[False] = False,
248
+ retry: Retry | None = None,
249
+ logger: LoggerLike | None = None,
250
+ **env_kwargs: str,
251
+ ) -> None: ...
252
+ @overload
253
+ def docker_exec(
254
+ container: str,
255
+ cmd: str,
256
+ /,
257
+ *args: str,
258
+ env: StrStrMapping | None = None,
259
+ user: str | None = None,
260
+ workdir: PathLike | None = None,
261
+ input: str | None = None,
262
+ print: bool = False,
263
+ print_stdout: bool = False,
264
+ print_stderr: bool = False,
265
+ return_: bool = False,
266
+ return_stdout: bool = False,
267
+ return_stderr: bool = False,
268
+ retry: Retry | None = None,
269
+ logger: LoggerLike | None = None,
270
+ **env_kwargs: str,
271
+ ) -> str | None: ...
272
+ def docker_exec(
273
+ container: str,
274
+ cmd: str,
275
+ /,
276
+ *args: str,
277
+ env: StrStrMapping | None = None,
278
+ user: str | None = None,
279
+ workdir: PathLike | None = None,
280
+ input: str | None = None, # noqa: A002
281
+ print: bool = False, # noqa: A002
282
+ print_stdout: bool = False,
283
+ print_stderr: bool = False,
284
+ return_: bool = False,
285
+ return_stdout: bool = False,
286
+ return_stderr: bool = False,
287
+ retry: Retry | None = None,
288
+ logger: LoggerLike | None = None,
289
+ **env_kwargs: str,
290
+ ) -> str | None:
291
+ """Execute a command in a container."""
292
+ run_cmd_and_args = docker_exec_cmd( # skipif-ci
293
+ container,
294
+ cmd,
295
+ *args,
296
+ env=env,
297
+ interactive=input is not None,
298
+ user=user,
299
+ workdir=workdir,
300
+ **env_kwargs,
301
+ )
302
+ return run( # skipif-ci
303
+ *run_cmd_and_args,
304
+ input=input,
305
+ print=print,
306
+ print_stdout=print_stdout,
307
+ print_stderr=print_stderr,
308
+ return_=return_,
309
+ return_stdout=return_stdout,
310
+ return_stderr=return_stderr,
311
+ retry=retry,
312
+ logger=logger,
313
+ )
314
+
315
+
316
+ def docker_exec_cmd(
317
+ container: str,
318
+ cmd: str,
319
+ /,
320
+ *args: str,
321
+ env: StrStrMapping | None = None,
322
+ interactive: bool = False,
323
+ user: str | None = None,
324
+ workdir: PathLike | None = None,
325
+ **env_kwargs: str,
326
+ ) -> list[str]:
327
+ """Command to use `docker exec` to execute a command in a container."""
328
+ all_args: list[str] = ["docker", "exec"]
329
+ mapping: dict[str, str] = ({} if env is None else dict(env)) | env_kwargs
330
+ for key, value in mapping.items():
331
+ all_args.extend(["--env", f"{key}={value}"])
332
+ if interactive:
333
+ all_args.append("--interactive")
334
+ if user is not None:
335
+ all_args.extend(["--user", user])
336
+ if workdir is not None:
337
+ all_args.extend(["--workdir", str(workdir)])
338
+ return [*all_args, container, cmd, *args]
339
+
340
+
341
+ ##
342
+
343
+
344
+ @enhanced_context_manager
345
+ def yield_docker_temp_dir(
346
+ container: str,
347
+ /,
348
+ *,
349
+ user: str | None = None,
350
+ retry: Retry | None = None,
351
+ logger: LoggerLike | None = None,
352
+ keep: bool = False,
353
+ ) -> Iterator[Path]:
354
+ """Yield a temporary directory in a Docker container."""
355
+ path = Path( # skipif-ci
356
+ docker_exec(
357
+ container,
358
+ *MKTEMP_DIR_CMD,
359
+ user=user,
360
+ return_=True,
361
+ retry=retry,
362
+ logger=logger,
363
+ )
364
+ )
365
+ try: # skipif-ci
366
+ yield path
367
+ finally: # skipif-ci
368
+ if keep:
369
+ if logger is not None:
370
+ to_logger(logger).info("Keeping temporary directory '%s'...", path)
371
+ else:
372
+ docker_exec(container, *rm_cmd(path), user=user, retry=retry, logger=logger)
373
+
374
+
375
+ __all__ = [
376
+ "docker_compose_down",
377
+ "docker_compose_down_cmd",
378
+ "docker_compose_pull",
379
+ "docker_compose_pull_cmd",
380
+ "docker_compose_up",
381
+ "docker_compose_up_cmd",
382
+ "docker_cp",
383
+ "docker_cp_cmd",
384
+ "docker_exec",
385
+ "docker_exec_cmd",
386
+ "yield_docker_temp_dir",
387
+ ]
utilities/enum.py CHANGED
@@ -4,8 +4,8 @@ from dataclasses import dataclass
4
4
  from enum import Enum, StrEnum
5
5
  from typing import TYPE_CHECKING, Literal, assert_never, overload, override
6
6
 
7
+ from utilities.core import OneStrEmptyError, OneStrNonUniqueError, one_str
7
8
  from utilities.functions import ensure_str
8
- from utilities.iterables import OneStrEmptyError, OneStrNonUniqueError, one_str
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  from utilities.types import EnumLike
utilities/errors.py CHANGED
@@ -4,7 +4,7 @@ from dataclasses import dataclass
4
4
  from typing import TYPE_CHECKING, assert_never, override
5
5
 
6
6
  if TYPE_CHECKING:
7
- from utilities.types import ExceptionTypeLike, MaybeType
7
+ from utilities.types import MaybeType
8
8
 
9
9
 
10
10
  @dataclass(kw_only=True, slots=True)
@@ -21,20 +21,6 @@ class ImpossibleCaseError(Exception):
21
21
  ##
22
22
 
23
23
 
24
- def is_instance_error(
25
- error: BaseException, class_or_tuple: ExceptionTypeLike[Exception], /
26
- ) -> bool:
27
- """Check if an instance relationship holds, allowing for groups."""
28
- if isinstance(error, class_or_tuple):
29
- return True
30
- if not isinstance(error, BaseExceptionGroup):
31
- return False
32
- return any(is_instance_error(e, class_or_tuple) for e in error.exceptions)
33
-
34
-
35
- ##
36
-
37
-
38
24
  def repr_error(error: MaybeType[BaseException], /) -> str:
39
25
  """Get a string representation of an error."""
40
26
  match error:
@@ -50,4 +36,4 @@ def repr_error(error: MaybeType[BaseException], /) -> str:
50
36
  assert_never(never)
51
37
 
52
38
 
53
- __all__ = ["ImpossibleCaseError", "is_instance_error", "repr_error"]
39
+ __all__ = ["ImpossibleCaseError", "repr_error"]
utilities/fastapi.py CHANGED
@@ -6,14 +6,14 @@ from typing import TYPE_CHECKING, Any, override
6
6
  from fastapi import FastAPI
7
7
  from uvicorn import Config, Server
8
8
 
9
- from utilities.asyncio import timeout_td
9
+ import utilities.asyncio
10
10
  from utilities.contextlib import enhanced_async_context_manager
11
- from utilities.whenever import get_now_local
11
+ from utilities.core import get_now_local
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from collections.abc import AsyncIterator
15
15
 
16
- from utilities.types import Delta, MaybeType
16
+ from utilities.types import Duration, MaybeType
17
17
 
18
18
 
19
19
  _TASKS: list[Task[None]] = []
@@ -39,7 +39,7 @@ async def yield_ping_receiver(
39
39
  /,
40
40
  *,
41
41
  host: str = "localhost",
42
- timeout: Delta | None = None,
42
+ timeout: Duration | None = None,
43
43
  error: MaybeType[BaseException] = TimeoutError,
44
44
  ) -> AsyncIterator[None]:
45
45
  """Yield the ping receiver."""
@@ -47,7 +47,7 @@ async def yield_ping_receiver(
47
47
  server = Server(Config(app, host=host, port=port)) # skipif-ci
48
48
  _TASKS.append(create_task(server.serve())) # skipif-ci
49
49
  try: # skipif-ci
50
- async with timeout_td(timeout, error=error):
50
+ async with utilities.asyncio.timeout(timeout, error=error):
51
51
  yield
52
52
  finally: # skipif-ci
53
53
  await server.shutdown()
utilities/fpdf2.py CHANGED
@@ -6,7 +6,8 @@ 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 format_compact, get_now_local
9
+ from utilities.core import get_now_local
10
+ from utilities.whenever import format_compact
10
11
 
11
12
  if TYPE_CHECKING:
12
13
  from collections.abc import Iterator