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/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
- ]
utilities/atomicwrites.py DELETED
@@ -1,182 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import gzip
4
- import shutil
5
- from contextlib import ExitStack, contextmanager
6
- from dataclasses import dataclass
7
- from pathlib import Path
8
- from shutil import copyfileobj, rmtree
9
- from typing import TYPE_CHECKING, assert_never, override
10
-
11
- from atomicwrites import replace_atomic
12
-
13
- from utilities.errors import ImpossibleCaseError
14
- from utilities.iterables import transpose
15
- from utilities.tempfile import TemporaryDirectory
16
-
17
- if TYPE_CHECKING:
18
- from collections.abc import Iterator
19
-
20
- from utilities.types import PathLike
21
-
22
-
23
- def move(
24
- source: PathLike, destination: PathLike, /, *, overwrite: bool = False
25
- ) -> None:
26
- """Move/replace a file/directory atomically."""
27
- source, destination = map(Path, [source, destination])
28
- match (
29
- source.is_file(),
30
- source.is_dir(),
31
- destination.is_file(),
32
- destination.is_dir(),
33
- overwrite,
34
- ):
35
- case False, False, _, _, _:
36
- raise _MoveSourceNotFoundError(source=source)
37
- # files
38
- case (True, False, True, False, False) | (True, False, False, True, False):
39
- raise _MoveFileExistsError(source=source, destination=destination) from None
40
- case True, False, False, True, _:
41
- rmtree(destination, ignore_errors=True)
42
- return replace_atomic(str(source), str(destination)) # must be `str`s
43
- case True, False, _, _, _:
44
- return replace_atomic(str(source), str(destination)) # must be `str`s
45
- # directories
46
- case (False, True, True, False, False) | (False, True, False, True, False):
47
- raise _MoveDirectoryExistsError(source=source, destination=destination)
48
- case False, True, False, True, _:
49
- rmtree(destination, ignore_errors=True)
50
- _ = shutil.move(source, destination)
51
- return None
52
- case False, True, _, _, _:
53
- destination.unlink(missing_ok=True)
54
- _ = shutil.move(source, destination)
55
- return None
56
- case True, True, _, _, _: # pragma: no cover
57
- raise ImpossibleCaseError(
58
- case=[f"{source.is_file()=}", f"{source.is_dir()=}"]
59
- )
60
- case never:
61
- assert_never(never)
62
-
63
-
64
- @dataclass(kw_only=True, slots=True)
65
- class MoveError(Exception): ...
66
-
67
-
68
- @dataclass(kw_only=True, slots=True)
69
- class _MoveSourceNotFoundError(MoveError):
70
- source: Path
71
-
72
- @override
73
- def __str__(self) -> str:
74
- return f"Source {str(self.source)!r} does not exist"
75
-
76
-
77
- @dataclass(kw_only=True, slots=True)
78
- class _MoveFileExistsError(MoveError):
79
- source: Path
80
- destination: Path
81
-
82
- @override
83
- def __str__(self) -> str:
84
- return f"Cannot move file {str(self.source)!r} as destination {str(self.destination)!r} already exists"
85
-
86
-
87
- @dataclass(kw_only=True, slots=True)
88
- class _MoveDirectoryExistsError(MoveError):
89
- source: Path
90
- destination: Path
91
-
92
- @override
93
- def __str__(self) -> str:
94
- return f"Cannot move directory {str(self.source)!r} as destination {str(self.destination)!r} already exists"
95
-
96
-
97
- ##
98
-
99
-
100
- def move_many(*paths: tuple[PathLike, PathLike], overwrite: bool = False) -> None:
101
- """Move a set of files concurrently."""
102
- sources, destinations = transpose(paths)
103
- with ExitStack() as stack:
104
- temp_paths = [
105
- stack.enter_context(writer(p, overwrite=overwrite)) for p in destinations
106
- ]
107
- for source, temp_path in zip(sources, temp_paths, strict=True):
108
- move(source, temp_path, overwrite=overwrite)
109
-
110
-
111
- ##
112
-
113
-
114
- @contextmanager
115
- def writer(
116
- path: PathLike, /, *, compress: bool = False, overwrite: bool = False
117
- ) -> Iterator[Path]:
118
- """Yield a path for atomically writing files to disk."""
119
- path = Path(path)
120
- parent = path.parent
121
- parent.mkdir(parents=True, exist_ok=True)
122
- name = path.name
123
- with TemporaryDirectory(suffix=".tmp", prefix=name, dir=parent) as temp_dir:
124
- temp_path1 = Path(temp_dir, name)
125
- try:
126
- yield temp_path1
127
- except KeyboardInterrupt:
128
- rmtree(temp_dir)
129
- else:
130
- if compress:
131
- temp_path2 = Path(temp_dir, f"{name}.gz")
132
- with (
133
- temp_path1.open("rb") as source,
134
- gzip.open(temp_path2, mode="wb") as dest,
135
- ):
136
- copyfileobj(source, dest)
137
- else:
138
- temp_path2 = temp_path1
139
- try:
140
- move(temp_path2, path, overwrite=overwrite)
141
- except _MoveSourceNotFoundError as error:
142
- raise _WriterTemporaryPathEmptyError(temp_path=error.source) from None
143
- except _MoveFileExistsError as error:
144
- raise _WriterFileExistsError(destination=error.destination) from None
145
- except _MoveDirectoryExistsError as error:
146
- raise _WriterDirectoryExistsError(
147
- destination=error.destination
148
- ) from None
149
-
150
-
151
- @dataclass(kw_only=True, slots=True)
152
- class WriterError(Exception): ...
153
-
154
-
155
- @dataclass(kw_only=True, slots=True)
156
- class _WriterTemporaryPathEmptyError(WriterError):
157
- temp_path: Path
158
-
159
- @override
160
- def __str__(self) -> str:
161
- return f"Temporary path {str(self.temp_path)!r} is empty"
162
-
163
-
164
- @dataclass(kw_only=True, slots=True)
165
- class _WriterFileExistsError(WriterError):
166
- destination: Path
167
-
168
- @override
169
- def __str__(self) -> str:
170
- return f"Cannot write to {str(self.destination)!r} as file already exists"
171
-
172
-
173
- @dataclass(kw_only=True, slots=True)
174
- class _WriterDirectoryExistsError(WriterError):
175
- destination: Path
176
-
177
- @override
178
- def __str__(self) -> str:
179
- return f"Cannot write to {str(self.destination)!r} as directory already exists"
180
-
181
-
182
- __all__ = ["MoveError", "WriterError", "move", "move_many", "writer"]
utilities/cryptography.py DELETED
@@ -1,41 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from os import getenv
5
- from typing import override
6
-
7
- from cryptography.fernet import Fernet
8
-
9
- _ENV_VAR = "FERNET_KEY"
10
-
11
-
12
- def encrypt(text: str, /, *, env_var: str = _ENV_VAR) -> bytes:
13
- """Encrypt a string."""
14
- return get_fernet(env_var).encrypt(text.encode())
15
-
16
-
17
- def decrypt(text: bytes, /, *, env_var: str = _ENV_VAR) -> str:
18
- """Encrypt a string."""
19
- return get_fernet(env_var).decrypt(text).decode()
20
-
21
-
22
- ##
23
-
24
-
25
- def get_fernet(env_var: str = _ENV_VAR, /) -> Fernet:
26
- """Get the Fernet key."""
27
- if (key := getenv(env_var)) is None:
28
- raise GetFernetError(env_var=env_var)
29
- return Fernet(key.encode())
30
-
31
-
32
- @dataclass(kw_only=True, slots=True)
33
- class GetFernetError(Exception):
34
- env_var: str
35
-
36
- @override
37
- def __str__(self) -> str:
38
- return f"Environment variable {self.env_var!r} is None"
39
-
40
-
41
- __all__ = ["GetFernetError", "decrypt", "encrypt", "get_fernet"]
utilities/getpass.py DELETED
@@ -1,8 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from getpass import getuser
4
-
5
- USER = getuser()
6
-
7
-
8
- __all__ = ["USER"]
utilities/git.py DELETED
@@ -1,19 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from pathlib import Path
4
- from typing import TYPE_CHECKING
5
-
6
- from git import Repo
7
-
8
- from utilities.pathlib import to_path
9
-
10
- if TYPE_CHECKING:
11
- from utilities.types import MaybeCallablePathLike
12
-
13
-
14
- def get_repo(path: MaybeCallablePathLike = Path.cwd, /) -> Repo:
15
- """Get the repo object."""
16
- return Repo(to_path(path), search_parent_directories=True)
17
-
18
-
19
- __all__ = ["get_repo"]
utilities/gzip.py DELETED
@@ -1,31 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import gzip
4
- from pathlib import Path
5
- from typing import TYPE_CHECKING
6
-
7
- from utilities.atomicwrites import writer
8
-
9
- if TYPE_CHECKING:
10
- from utilities.types import PathLike
11
-
12
-
13
- def read_binary(path: PathLike, /, *, decompress: bool = False) -> bytes:
14
- """Read a byte string from disk."""
15
- path = Path(path)
16
- if decompress:
17
- with gzip.open(path) as gz:
18
- return gz.read()
19
- else:
20
- return path.read_bytes()
21
-
22
-
23
- def write_binary(
24
- data: bytes, path: PathLike, /, *, compress: bool = False, overwrite: bool = False
25
- ) -> None:
26
- """Write a byte string to disk."""
27
- with writer(path, compress=compress, overwrite=overwrite) as temp:
28
- _ = temp.write_bytes(data)
29
-
30
-
31
- __all__ = ["read_binary", "write_binary"]