dycw-utilities 0.166.30__py3-none-any.whl → 0.175.17__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 (45) hide show
  1. dycw_utilities-0.175.17.dist-info/METADATA +34 -0
  2. {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.175.17.dist-info}/RECORD +43 -38
  3. dycw_utilities-0.175.17.dist-info/WHEEL +4 -0
  4. {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.175.17.dist-info}/entry_points.txt +1 -0
  5. utilities/__init__.py +1 -1
  6. utilities/altair.py +9 -4
  7. utilities/asyncio.py +10 -16
  8. utilities/cachetools.py +9 -6
  9. utilities/click.py +76 -20
  10. utilities/docker.py +293 -0
  11. utilities/functions.py +1 -1
  12. utilities/grp.py +28 -0
  13. utilities/hypothesis.py +38 -6
  14. utilities/importlib.py +17 -1
  15. utilities/jinja2.py +148 -0
  16. utilities/logging.py +7 -9
  17. utilities/orjson.py +18 -18
  18. utilities/os.py +38 -0
  19. utilities/parse.py +2 -2
  20. utilities/pathlib.py +18 -1
  21. utilities/permissions.py +298 -0
  22. utilities/platform.py +1 -1
  23. utilities/polars.py +4 -1
  24. utilities/postgres.py +28 -29
  25. utilities/pwd.py +28 -0
  26. utilities/pydantic.py +11 -0
  27. utilities/pydantic_settings.py +81 -8
  28. utilities/pydantic_settings_sops.py +13 -0
  29. utilities/pytest.py +60 -30
  30. utilities/pytest_regressions.py +26 -7
  31. utilities/shutil.py +25 -0
  32. utilities/sqlalchemy.py +15 -0
  33. utilities/subprocess.py +1572 -0
  34. utilities/tempfile.py +60 -1
  35. utilities/text.py +48 -32
  36. utilities/timer.py +2 -2
  37. utilities/traceback.py +1 -1
  38. utilities/types.py +5 -0
  39. utilities/typing.py +8 -2
  40. utilities/whenever.py +36 -5
  41. dycw_utilities-0.166.30.dist-info/METADATA +0 -41
  42. dycw_utilities-0.166.30.dist-info/WHEEL +0 -4
  43. dycw_utilities-0.166.30.dist-info/licenses/LICENSE +0 -21
  44. utilities/aeventkit.py +0 -388
  45. utilities/typed_settings.py +0 -152
utilities/aeventkit.py DELETED
@@ -1,388 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from functools import wraps
5
- from inspect import iscoroutinefunction
6
- from typing import TYPE_CHECKING, Any, Self, assert_never, cast, override
7
-
8
- from eventkit import (
9
- Constant,
10
- Count,
11
- DropWhile,
12
- Enumerate,
13
- Event,
14
- Filter,
15
- Fork,
16
- Iterate,
17
- Map,
18
- Pack,
19
- Partial,
20
- PartialRight,
21
- Pluck,
22
- Skip,
23
- Star,
24
- Take,
25
- TakeUntil,
26
- TakeWhile,
27
- Timestamp,
28
- )
29
-
30
- from utilities.functions import apply_decorators
31
- from utilities.iterables import always_iterable
32
- from utilities.logging import to_logger
33
-
34
- if TYPE_CHECKING:
35
- from collections.abc import Callable
36
-
37
- from utilities.types import Coro, LoggerLike, MaybeCoro, MaybeIterable, TypeLike
38
-
39
-
40
- ##
41
-
42
-
43
- def add_listener[E: Event, F: Callable](
44
- event: E,
45
- listener: Callable[..., MaybeCoro[None]],
46
- /,
47
- *,
48
- error: Callable[[Event, BaseException], MaybeCoro[None]] | None = None,
49
- ignore: TypeLike[BaseException] | None = None,
50
- logger: LoggerLike | None = None,
51
- decorators: MaybeIterable[Callable[[F], F]] | None = None,
52
- done: Callable[..., MaybeCoro[None]] | None = None,
53
- keep_ref: bool = False,
54
- ) -> E:
55
- """Connect a listener to an event."""
56
- lifted = lift_listener(
57
- listener,
58
- event,
59
- error=error,
60
- ignore=ignore,
61
- logger=logger,
62
- decorators=decorators,
63
- )
64
- return cast("E", event.connect(lifted, done=done, keep_ref=keep_ref))
65
-
66
-
67
- ##
68
-
69
-
70
- @dataclass(repr=False, kw_only=True)
71
- class LiftedEvent[F: Callable[..., MaybeCoro[None]]]:
72
- """A lifted version of `Event`."""
73
-
74
- event: Event
75
-
76
- def name(self) -> str:
77
- return self.event.name() # pragma: no cover
78
-
79
- def done(self) -> bool:
80
- return self.event.done() # pragma: no cover
81
-
82
- def set_done(self) -> None:
83
- self.event.set_done() # pragma: no cover
84
-
85
- def value(self) -> Any:
86
- return self.event.value() # pragma: no cover
87
-
88
- def connect[F2: Callable](
89
- self,
90
- listener: F,
91
- /,
92
- *,
93
- error: Callable[[Event, BaseException], MaybeCoro[None]] | None = None,
94
- ignore: TypeLike[BaseException] | None = None,
95
- logger: LoggerLike | None = None,
96
- decorators: MaybeIterable[Callable[[F2], F2]] | None = None,
97
- done: Callable[..., MaybeCoro[None]] | None = None,
98
- keep_ref: bool = False,
99
- ) -> Event:
100
- return add_listener(
101
- self.event,
102
- listener,
103
- error=error,
104
- ignore=ignore,
105
- logger=logger,
106
- decorators=decorators,
107
- done=done,
108
- keep_ref=keep_ref,
109
- )
110
-
111
- def disconnect(
112
- self, listener: Any, /, *, error: Any = None, done: Any = None
113
- ) -> Any:
114
- return self.event.disconnect( # pragma: no cover
115
- listener, error=error, done=done
116
- )
117
-
118
- def disconnect_obj(self, obj: Any, /) -> None:
119
- self.event.disconnect_obj(obj) # pragma: no cover
120
-
121
- def emit(self, *args: Any) -> None:
122
- self.event.emit(*args) # pragma: no cover
123
-
124
- def emit_threadsafe(self, *args: Any) -> None:
125
- self.event.emit_threadsafe(*args) # pragma: no cover
126
-
127
- def clear(self) -> None:
128
- self.event.clear() # pragma: no cover
129
-
130
- def run(self) -> list[Any]:
131
- return self.event.run() # pragma: no cover
132
-
133
- def pipe(self, *targets: Event) -> Event:
134
- return self.event.pipe(*targets) # pragma: no cover
135
-
136
- def fork(self, *targets: Event) -> Fork:
137
- return self.event.fork(*targets) # pragma: no cover
138
-
139
- def set_source(self, source: Any, /) -> None:
140
- self.event.set_source(source) # pragma: no cover
141
-
142
- def _onFinalize(self, ref: Any) -> None: # noqa: N802
143
- self.event._onFinalize(ref) # noqa: SLF001 # pragma: no cover
144
-
145
- async def aiter(self, *, skip_to_last: bool = False, tuples: bool = False) -> Any:
146
- async for i in self.event.aiter( # pragma: no cover
147
- skip_to_last=skip_to_last, tuples=tuples
148
- ):
149
- yield i
150
-
151
- __iadd__ = connect
152
- __isub__ = disconnect
153
- __call__ = emit
154
- __or__ = pipe
155
-
156
- @override
157
- def __repr__(self) -> str:
158
- return self.event.__repr__() # pragma: no cover
159
-
160
- def __len__(self) -> int:
161
- return self.event.__len__() # pragma: no cover
162
-
163
- def __bool__(self) -> bool:
164
- return self.event.__bool__() # pragma: no cover
165
-
166
- def __getitem__(self, fork_targets: Any, /) -> Fork:
167
- return self.event.__getitem__(fork_targets) # pragma: no cover
168
-
169
- def __await__(self) -> Any:
170
- return self.event.__await__() # pragma: no cover
171
-
172
- __aiter__ = aiter
173
-
174
- def __contains__(self, c: Any, /) -> bool:
175
- return self.event.__contains__(c) # pragma: no cover
176
-
177
- @override
178
- def __reduce__(self) -> Any:
179
- return self.event.__reduce__() # pragma: no cover
180
-
181
- def filter(self, *, predicate: Any = bool) -> Filter:
182
- return self.event.filter(predicate=predicate) # pragma: no cover
183
-
184
- def skip(self, *, count: int = 1) -> Skip:
185
- return self.event.skip(count=count) # pragma: no cover
186
-
187
- def take(self, *, count: int = 1) -> Take:
188
- return self.event.take(count=count) # pragma: no cover
189
-
190
- def takewhile(self, *, predicate: Any = bool) -> TakeWhile:
191
- return self.event.takewhile(predicate=predicate) # pragma: no cover
192
-
193
- def dropwhile(self, *, predicate: Any = lambda x: not x) -> DropWhile: # pyright: ignore[reportUnknownLambdaType]
194
- return self.event.dropwhile(predicate=predicate) # pragma: no cover
195
-
196
- def takeuntil(self, notifier: Event, /) -> TakeUntil:
197
- return self.event.takeuntil(notifier) # pragma: no cover
198
-
199
- def constant(self, constant: Any, /) -> Constant:
200
- return self.event.constant(constant) # pragma: no cover
201
-
202
- def iterate(self, it: Any, /) -> Iterate:
203
- return self.event.iterate(it) # pragma: no cover
204
-
205
- def count(self, *, start: int = 0, step: int = 1) -> Count:
206
- return self.event.count(start=start, step=step) # pragma: no cover
207
-
208
- def enumerate(self, *, start: int = 0, step: int = 1) -> Enumerate:
209
- return self.event.enumerate(start=start, step=step) # pragma: no cover
210
-
211
- def timestamp(self) -> Timestamp:
212
- return self.event.timestamp() # pragma: no cover
213
-
214
- def partial(self, *left_args: Any) -> Partial:
215
- return self.event.partial(*left_args) # pragma: no cover
216
-
217
- def partial_right(self, *right_args: Any) -> PartialRight:
218
- return self.event.partial_right(*right_args) # pragma: no cover
219
-
220
- def star(self) -> Star:
221
- return self.event.star() # pragma: no cover
222
-
223
- def pack(self) -> Pack:
224
- return self.event.pack() # pragma: no cover
225
-
226
- def pluck(self, *selections: int | str) -> Pluck:
227
- return self.event.pluck(*selections) # pragma: no cover
228
-
229
- def map(
230
- self,
231
- func: Any,
232
- /,
233
- *,
234
- timeout: float | None = None,
235
- ordered: bool = True,
236
- task_limit: int | None = None,
237
- ) -> Map:
238
- return self.event.map( # pragma: no cover
239
- func, timeout=timeout, ordered=ordered, task_limit=task_limit
240
- )
241
-
242
-
243
- ##
244
-
245
-
246
- class TypedEvent[F: Callable[..., MaybeCoro[None]]](Event):
247
- """A typed version of `Event`."""
248
-
249
- @override
250
- def connect[F2: Callable](
251
- self,
252
- listener: F,
253
- error: Callable[[Self, BaseException], MaybeCoro[None]] | None = None,
254
- done: Callable[[Self], MaybeCoro[None]] | None = None,
255
- keep_ref: bool = False,
256
- *,
257
- ignore: TypeLike[BaseException] | None = None,
258
- logger: LoggerLike | None = None,
259
- decorators: MaybeIterable[Callable[[F2], F2]] | None = None,
260
- ) -> Self:
261
- lifted = lift_listener(
262
- listener,
263
- self,
264
- error=cast(
265
- "Callable[[Event, BaseException], MaybeCoro[None]] | None", error
266
- ),
267
- ignore=ignore,
268
- logger=logger,
269
- decorators=decorators,
270
- )
271
- return cast(
272
- "Self", super().connect(lifted, error=error, done=done, keep_ref=keep_ref)
273
- )
274
-
275
-
276
- ##
277
-
278
-
279
- def lift_listener[F1: Callable[..., MaybeCoro[None]], F2: Callable](
280
- listener: F1,
281
- event: Event,
282
- /,
283
- *,
284
- error: Callable[[Event, BaseException], MaybeCoro[None]] | None = None,
285
- ignore: TypeLike[BaseException] | None = None,
286
- logger: LoggerLike | None = None,
287
- decorators: MaybeIterable[Callable[[F2], F2]] | None = None,
288
- ) -> F1:
289
- match error, bool(iscoroutinefunction(listener)):
290
- case None, False:
291
- listener_typed = cast("Callable[..., None]", listener)
292
-
293
- @wraps(listener)
294
- def listener_no_error_sync(*args: Any, **kwargs: Any) -> None:
295
- try:
296
- listener_typed(*args, **kwargs)
297
- except Exception as exc: # noqa: BLE001
298
- if (ignore is not None) and isinstance(exc, ignore):
299
- return
300
- to_logger(logger).exception("")
301
-
302
- lifted = listener_no_error_sync
303
-
304
- case None, True:
305
- listener_typed = cast("Callable[..., Coro[None]]", listener)
306
-
307
- @wraps(listener)
308
- async def listener_no_error_async(*args: Any, **kwargs: Any) -> None:
309
- try:
310
- await listener_typed(*args, **kwargs)
311
- except Exception as exc: # noqa: BLE001
312
- if (ignore is not None) and isinstance(exc, ignore):
313
- return
314
- to_logger(logger).exception("")
315
-
316
- lifted = listener_no_error_async
317
- case _, _:
318
- match bool(iscoroutinefunction(listener)), bool(iscoroutinefunction(error)):
319
- case False, False:
320
- listener_typed = cast("Callable[..., None]", listener)
321
- error_typed = cast("Callable[[Event, Exception], None]", error)
322
-
323
- @wraps(listener)
324
- def listener_have_error_sync(*args: Any, **kwargs: Any) -> None:
325
- try:
326
- listener_typed(*args, **kwargs)
327
- except Exception as exc: # noqa: BLE001
328
- if (ignore is not None) and isinstance(exc, ignore):
329
- return
330
- error_typed(event, exc)
331
-
332
- lifted = listener_have_error_sync
333
- case False, True:
334
- listener_typed = cast("Callable[..., None]", listener)
335
- error_typed = cast(
336
- "Callable[[Event, Exception], Coro[None]]", error
337
- )
338
- raise LiftListenerError(listener=listener_typed, error=error_typed)
339
- case True, _:
340
- listener_typed = cast("Callable[..., Coro[None]]", listener)
341
-
342
- @wraps(listener)
343
- async def listener_have_error_async(
344
- *args: Any, **kwargs: Any
345
- ) -> None:
346
- try:
347
- await listener_typed(*args, **kwargs)
348
- except Exception as exc: # noqa: BLE001
349
- if (ignore is not None) and isinstance(exc, ignore):
350
- return None
351
- if iscoroutinefunction(error):
352
- error_typed = cast(
353
- "Callable[[Event, Exception], Coro[None]]", error
354
- )
355
- return await error_typed(event, exc)
356
- error_typed = cast(
357
- "Callable[[Event, Exception], None]", error
358
- )
359
- error_typed(event, exc)
360
-
361
- lifted = listener_have_error_async
362
- case never:
363
- assert_never(never)
364
- case never:
365
- assert_never(never)
366
-
367
- if decorators is not None:
368
- lifted = apply_decorators(lifted, *always_iterable(decorators))
369
- return cast("F1", lifted)
370
-
371
-
372
- @dataclass(kw_only=True, slots=True)
373
- class LiftListenerError(Exception):
374
- listener: Callable[..., None]
375
- error: Callable[[Event, Exception], Coro[None]]
376
-
377
- @override
378
- def __str__(self) -> str:
379
- return f"Synchronous listener {self.listener} cannot be paired with an asynchronous error handler {self.error}"
380
-
381
-
382
- __all__ = [
383
- "LiftListenerError",
384
- "LiftedEvent",
385
- "TypedEvent",
386
- "add_listener",
387
- "lift_listener",
388
- ]
@@ -1,152 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from functools import partial
5
- from ipaddress import IPv4Address, IPv6Address
6
- from os import environ
7
- from pathlib import Path
8
- from re import search
9
- from typing import TYPE_CHECKING, Any, assert_never, override
10
- from uuid import UUID
11
-
12
- import typed_settings
13
- from typed_settings import EnvLoader, FileLoader, find
14
- from typed_settings.converters import TSConverter
15
- from typed_settings.loaders import TomlFormat
16
- from whenever import (
17
- Date,
18
- DateDelta,
19
- DateTimeDelta,
20
- MonthDay,
21
- PlainDateTime,
22
- Time,
23
- TimeDelta,
24
- YearMonth,
25
- ZonedDateTime,
26
- )
27
-
28
- from utilities.iterables import always_iterable
29
- from utilities.pathlib import to_path
30
- from utilities.string import substitute_environ
31
-
32
- if TYPE_CHECKING:
33
- from collections.abc import Callable, Iterable
34
-
35
- from typed_settings.loaders import Loader
36
- from typed_settings.processors import Processor
37
-
38
- from utilities.types import MaybeCallablePathLike, MaybeIterable, PathLike
39
-
40
-
41
- type _ConverterItem = tuple[type[Any], Callable[..., Any]]
42
-
43
-
44
- ##
45
-
46
-
47
- class ExtendedTSConverter(TSConverter):
48
- """An extension of the TSConverter for custom types."""
49
-
50
- @override
51
- def __init__(
52
- self,
53
- *,
54
- resolve_paths: bool = True,
55
- strlist_sep: str | Callable[[str], list] | None = ":",
56
- extra: Iterable[_ConverterItem] = (),
57
- ) -> None:
58
- super().__init__(resolve_paths=resolve_paths, strlist_sep=strlist_sep)
59
- cases: list[_ConverterItem] = [
60
- (Date, Date.parse_common_iso),
61
- (DateDelta, DateDelta.parse_common_iso),
62
- (DateTimeDelta, DateTimeDelta.parse_common_iso),
63
- (IPv4Address, IPv4Address),
64
- (IPv6Address, IPv6Address),
65
- (MonthDay, MonthDay.parse_common_iso),
66
- (Path, partial(_parse_path, resolve=resolve_paths, pwd=Path.cwd())),
67
- (PlainDateTime, PlainDateTime.parse_common_iso),
68
- (Time, Time.parse_common_iso),
69
- (TimeDelta, TimeDelta.parse_common_iso),
70
- (UUID, UUID),
71
- (YearMonth, YearMonth.parse_common_iso),
72
- (ZonedDateTime, ZonedDateTime.parse_common_iso),
73
- *extra,
74
- ]
75
- extras = {cls: _make_converter(cls, func) for cls, func in cases}
76
- self.scalar_converters |= extras
77
-
78
-
79
- def _make_converter[T](
80
- cls: type[T], parser: Callable[[str], T], /
81
- ) -> Callable[[Any, type[Any]], Any]:
82
- def hook(value: T | str, _: type[T] = cls, /) -> Any:
83
- if not isinstance(value, (cls, str)): # pragma: no cover
84
- msg = f"Invalid type {type(value).__name__!r}; expected '{cls.__name__}' or 'str'"
85
- raise TypeError(msg)
86
- if isinstance(value, str):
87
- return parser(value)
88
- return value
89
-
90
- return hook
91
-
92
-
93
- def _parse_path(
94
- path: str, /, *, resolve: bool = False, pwd: MaybeCallablePathLike = Path.cwd
95
- ) -> Path:
96
- path = substitute_environ(path, **environ)
97
- match resolve:
98
- case True:
99
- return to_path(pwd).joinpath(path).resolve()
100
- case False:
101
- return Path(path)
102
- case never:
103
- assert_never(never)
104
-
105
-
106
- ##
107
-
108
-
109
- _BASE_DIR: Path = Path()
110
-
111
-
112
- def load_settings[T](
113
- cls: type[T],
114
- app_name: str,
115
- /,
116
- *,
117
- filenames: MaybeIterable[str] = "settings.toml",
118
- start_dir: PathLike | None = None,
119
- loaders: MaybeIterable[Loader] | None = None,
120
- processors: MaybeIterable[Processor] = (),
121
- converters: Iterable[_ConverterItem] = (),
122
- base_dir: Path = _BASE_DIR,
123
- ) -> T:
124
- if not search(r"^[A-Za-z]+(?:_[A-Za-z]+)*$", app_name):
125
- raise LoadSettingsError(appname=app_name)
126
- filenames_use = list(always_iterable(filenames))
127
- start_dir_use = None if start_dir is None else Path(start_dir)
128
- files = [find(filename, start_dir=start_dir_use) for filename in filenames_use]
129
- file_loader = FileLoader(formats={"*.toml": TomlFormat(app_name)}, files=files)
130
- env_loader = EnvLoader(f"{app_name.upper()}__", nested_delimiter="__")
131
- loaders_use: list[Loader] = [file_loader, env_loader]
132
- if loaders is not None:
133
- loaders_use.extend(always_iterable(loaders))
134
- return typed_settings.load_settings(
135
- cls,
136
- loaders_use,
137
- processors=list(always_iterable(processors)),
138
- converter=ExtendedTSConverter(extra=converters),
139
- base_dir=base_dir,
140
- )
141
-
142
-
143
- @dataclass(kw_only=True, slots=True)
144
- class LoadSettingsError(Exception):
145
- appname: str
146
-
147
- @override
148
- def __str__(self) -> str:
149
- return f"Invalid app name; got {self.appname!r}"
150
-
151
-
152
- __all__ = ["ExtendedTSConverter", "LoadSettingsError", "load_settings"]