dycw-utilities 0.129.10__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 (103) hide show
  1. dycw_utilities-0.175.17.dist-info/METADATA +34 -0
  2. dycw_utilities-0.175.17.dist-info/RECORD +103 -0
  3. dycw_utilities-0.175.17.dist-info/WHEEL +4 -0
  4. dycw_utilities-0.175.17.dist-info/entry_points.txt +4 -0
  5. utilities/__init__.py +1 -1
  6. utilities/altair.py +14 -14
  7. utilities/asyncio.py +350 -819
  8. utilities/atomicwrites.py +18 -6
  9. utilities/atools.py +77 -22
  10. utilities/cachetools.py +24 -29
  11. utilities/click.py +393 -237
  12. utilities/concurrent.py +8 -11
  13. utilities/contextlib.py +216 -17
  14. utilities/contextvars.py +20 -1
  15. utilities/cryptography.py +3 -3
  16. utilities/dataclasses.py +83 -118
  17. utilities/docker.py +293 -0
  18. utilities/enum.py +26 -23
  19. utilities/errors.py +17 -3
  20. utilities/fastapi.py +29 -65
  21. utilities/fpdf2.py +3 -3
  22. utilities/functions.py +169 -416
  23. utilities/functools.py +18 -19
  24. utilities/git.py +9 -30
  25. utilities/grp.py +28 -0
  26. utilities/gzip.py +31 -0
  27. utilities/http.py +3 -2
  28. utilities/hypothesis.py +738 -589
  29. utilities/importlib.py +17 -1
  30. utilities/inflect.py +25 -0
  31. utilities/iterables.py +194 -262
  32. utilities/jinja2.py +148 -0
  33. utilities/json.py +70 -0
  34. utilities/libcst.py +38 -17
  35. utilities/lightweight_charts.py +5 -9
  36. utilities/logging.py +345 -543
  37. utilities/math.py +18 -13
  38. utilities/memory_profiler.py +11 -15
  39. utilities/more_itertools.py +200 -131
  40. utilities/operator.py +33 -29
  41. utilities/optuna.py +6 -6
  42. utilities/orjson.py +272 -137
  43. utilities/os.py +61 -4
  44. utilities/parse.py +59 -61
  45. utilities/pathlib.py +281 -40
  46. utilities/permissions.py +298 -0
  47. utilities/pickle.py +2 -2
  48. utilities/platform.py +24 -5
  49. utilities/polars.py +1214 -430
  50. utilities/polars_ols.py +1 -1
  51. utilities/postgres.py +408 -0
  52. utilities/pottery.py +113 -26
  53. utilities/pqdm.py +10 -11
  54. utilities/psutil.py +6 -57
  55. utilities/pwd.py +28 -0
  56. utilities/pydantic.py +4 -54
  57. utilities/pydantic_settings.py +240 -0
  58. utilities/pydantic_settings_sops.py +76 -0
  59. utilities/pyinstrument.py +8 -10
  60. utilities/pytest.py +227 -121
  61. utilities/pytest_plugins/__init__.py +1 -0
  62. utilities/pytest_plugins/pytest_randomly.py +23 -0
  63. utilities/pytest_plugins/pytest_regressions.py +56 -0
  64. utilities/pytest_regressions.py +26 -46
  65. utilities/random.py +13 -9
  66. utilities/re.py +58 -28
  67. utilities/redis.py +401 -550
  68. utilities/scipy.py +1 -1
  69. utilities/sentinel.py +10 -0
  70. utilities/shelve.py +4 -1
  71. utilities/shutil.py +25 -0
  72. utilities/slack_sdk.py +36 -106
  73. utilities/sqlalchemy.py +502 -473
  74. utilities/sqlalchemy_polars.py +38 -94
  75. utilities/string.py +2 -3
  76. utilities/subprocess.py +1572 -0
  77. utilities/tempfile.py +86 -4
  78. utilities/testbook.py +50 -0
  79. utilities/text.py +165 -42
  80. utilities/timer.py +37 -65
  81. utilities/traceback.py +158 -929
  82. utilities/types.py +146 -116
  83. utilities/typing.py +531 -71
  84. utilities/tzdata.py +1 -53
  85. utilities/tzlocal.py +6 -23
  86. utilities/uuid.py +43 -5
  87. utilities/version.py +27 -26
  88. utilities/whenever.py +1776 -386
  89. utilities/zoneinfo.py +84 -22
  90. dycw_utilities-0.129.10.dist-info/METADATA +0 -241
  91. dycw_utilities-0.129.10.dist-info/RECORD +0 -96
  92. dycw_utilities-0.129.10.dist-info/WHEEL +0 -4
  93. dycw_utilities-0.129.10.dist-info/licenses/LICENSE +0 -21
  94. utilities/datetime.py +0 -1409
  95. utilities/eventkit.py +0 -402
  96. utilities/loguru.py +0 -144
  97. utilities/luigi.py +0 -228
  98. utilities/period.py +0 -324
  99. utilities/pyrsistent.py +0 -89
  100. utilities/python_dotenv.py +0 -105
  101. utilities/streamlit.py +0 -105
  102. utilities/sys.py +0 -87
  103. utilities/tenacity.py +0 -145
utilities/concurrent.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
4
4
  from functools import partial
5
- from typing import TYPE_CHECKING, Any, TypeVar, assert_never
5
+ from typing import TYPE_CHECKING, Any, assert_never
6
6
 
7
7
  from utilities.iterables import apply_to_tuple
8
8
  from utilities.os import get_cpu_use
@@ -15,11 +15,8 @@ if TYPE_CHECKING:
15
15
  from utilities.os import IntOrAll
16
16
 
17
17
 
18
- _T = TypeVar("_T")
19
-
20
-
21
- def concurrent_map(
22
- func: Callable[..., _T],
18
+ def concurrent_map[T](
19
+ func: Callable[..., T],
23
20
  /,
24
21
  *iterables: Iterable[Any],
25
22
  parallelism: Parallelism = "processes",
@@ -31,7 +28,7 @@ def concurrent_map(
31
28
  thread_name_prefix: str = "",
32
29
  timeout: float | None = None,
33
30
  chunksize: int = 1,
34
- ) -> list[_T]:
31
+ ) -> list[T]:
35
32
  """Concurrent map."""
36
33
  return concurrent_starmap(
37
34
  func,
@@ -51,8 +48,8 @@ def concurrent_map(
51
48
  ##
52
49
 
53
50
 
54
- def concurrent_starmap(
55
- func: Callable[..., _T],
51
+ def concurrent_starmap[T](
52
+ func: Callable[..., T],
56
53
  iterable: Iterable[tuple[Any, ...]],
57
54
  /,
58
55
  *,
@@ -65,7 +62,7 @@ def concurrent_starmap(
65
62
  thread_name_prefix: str = "",
66
63
  timeout: float | None = None,
67
64
  chunksize: int = 1,
68
- ) -> list[_T]:
65
+ ) -> list[T]:
69
66
  """Concurrent map."""
70
67
  max_workers_use = get_cpu_use(n=max_workers)
71
68
  apply = partial(apply_to_tuple, func)
@@ -87,7 +84,7 @@ def concurrent_starmap(
87
84
  initargs=initargs,
88
85
  ) as pool:
89
86
  result = pool.map(apply, iterable, timeout=timeout, chunksize=chunksize)
90
- case _ as never:
87
+ case never:
91
88
  assert_never(never)
92
89
  return list(result)
93
90
 
utilities/contextlib.py CHANGED
@@ -1,28 +1,223 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
- from contextlib import contextmanager
5
- from typing import TYPE_CHECKING
4
+ from asyncio import create_task, get_event_loop
5
+ from contextlib import (
6
+ _AsyncGeneratorContextManager,
7
+ _GeneratorContextManager,
8
+ asynccontextmanager,
9
+ contextmanager,
10
+ )
11
+ from functools import partial, wraps
12
+ from signal import SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, getsignal, signal
13
+ from typing import TYPE_CHECKING, Any, assert_never, cast, overload
6
14
 
7
15
  if TYPE_CHECKING:
8
- from collections.abc import Iterator
9
- from types import TracebackType
16
+ from collections.abc import AsyncIterator, Callable, Iterator
17
+ from signal import _HANDLER, _SIGNUM
18
+ from types import FrameType
10
19
 
11
20
 
12
- class NoOpContextManager:
13
- """Context-manager for no-op."""
21
+ @overload
22
+ def enhanced_context_manager[**P, T_co](
23
+ func: Callable[P, Iterator[T_co]],
24
+ /,
25
+ *,
26
+ sigabrt: bool = True,
27
+ sigfpe: bool = True,
28
+ sigill: bool = True,
29
+ sigint: bool = True,
30
+ sigsegv: bool = True,
31
+ sigterm: bool = True,
32
+ ) -> Callable[P, _GeneratorContextManager[T_co]]: ...
33
+ @overload
34
+ def enhanced_context_manager[**P, T_co](
35
+ func: None = None,
36
+ /,
37
+ *,
38
+ sigabrt: bool = True,
39
+ sigfpe: bool = True,
40
+ sigill: bool = True,
41
+ sigint: bool = True,
42
+ sigsegv: bool = True,
43
+ sigterm: bool = True,
44
+ ) -> Callable[
45
+ [Callable[P, Iterator[T_co]]], Callable[P, _GeneratorContextManager[T_co]]
46
+ ]: ...
47
+ def enhanced_context_manager[**P, T_co](
48
+ func: Callable[P, Iterator[T_co]] | None = None,
49
+ /,
50
+ *,
51
+ sigabrt: bool = True,
52
+ sigfpe: bool = True,
53
+ sigill: bool = True,
54
+ sigint: bool = True,
55
+ sigsegv: bool = True,
56
+ sigterm: bool = True,
57
+ ) -> (
58
+ Callable[P, _GeneratorContextManager[T_co]]
59
+ | Callable[
60
+ [Callable[P, Iterator[T_co]]], Callable[P, _GeneratorContextManager[T_co]]
61
+ ]
62
+ ):
63
+ if func is None:
64
+ result = partial(
65
+ enhanced_context_manager,
66
+ sigabrt=sigabrt,
67
+ sigfpe=sigfpe,
68
+ sigill=sigill,
69
+ sigint=sigint,
70
+ sigsegv=sigsegv,
71
+ sigterm=sigterm,
72
+ )
73
+ return cast(
74
+ "Callable[[Callable[P, Iterator[T_co]]], Callable[P, _GeneratorContextManager[T_co]]]",
75
+ result,
76
+ )
77
+ make_gcm = contextmanager(func)
14
78
 
15
- def __enter__(self) -> None:
16
- return None
79
+ @contextmanager
80
+ @wraps(func)
81
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> Iterator[T_co]:
82
+ gcm = make_gcm(*args, **kwargs)
83
+ sigabrt0 = _swap_handler(SIGABRT, gcm) if sigabrt else None
84
+ sigfpe0 = _swap_handler(SIGFPE, gcm) if sigfpe else None
85
+ sigill0 = _swap_handler(SIGILL, gcm) if sigill else None
86
+ sigint0 = _swap_handler(SIGINT, gcm) if sigint else None
87
+ sigsegv0 = _swap_handler(SIGSEGV, gcm) if sigsegv else None
88
+ sigterm0 = _swap_handler(SIGTERM, gcm) if sigterm else None
89
+ try:
90
+ with gcm as value:
91
+ yield value
92
+ finally:
93
+ _ = signal(SIGABRT, sigabrt0) if sigabrt else None
94
+ _ = signal(SIGFPE, sigfpe0) if sigfpe else None
95
+ _ = signal(SIGILL, sigill0) if sigill else None
96
+ _ = signal(SIGINT, sigint0) if sigint else None
97
+ _ = signal(SIGSEGV, sigsegv0) if sigsegv else None
98
+ _ = signal(SIGTERM, sigterm0) if sigterm else None
17
99
 
18
- def __exit__(
19
- self,
20
- exc_type: type[BaseException] | None,
21
- exc_val: BaseException | None,
22
- traceback: TracebackType | None,
23
- ) -> bool:
24
- _ = (exc_type, exc_val, traceback)
25
- return False
100
+ return wrapped
101
+
102
+
103
+ @overload
104
+ def enhanced_async_context_manager[**P, T_co](
105
+ func: Callable[P, AsyncIterator[T_co]],
106
+ /,
107
+ *,
108
+ sigabrt: bool = True,
109
+ sigfpe: bool = True,
110
+ sigill: bool = True,
111
+ sigint: bool = True,
112
+ sigsegv: bool = True,
113
+ sigterm: bool = True,
114
+ ) -> Callable[P, _AsyncGeneratorContextManager[T_co]]: ...
115
+ @overload
116
+ def enhanced_async_context_manager[**P, T_co](
117
+ func: None = None,
118
+ /,
119
+ *,
120
+ sigabrt: bool = True,
121
+ sigfpe: bool = True,
122
+ sigill: bool = True,
123
+ sigint: bool = True,
124
+ sigsegv: bool = True,
125
+ sigterm: bool = True,
126
+ ) -> Callable[
127
+ [Callable[P, AsyncIterator[T_co]]], Callable[P, _AsyncGeneratorContextManager[T_co]]
128
+ ]: ...
129
+ def enhanced_async_context_manager[**P, T_co](
130
+ func: Callable[P, AsyncIterator[T_co]] | None = None,
131
+ /,
132
+ *,
133
+ sigabrt: bool = True,
134
+ sigfpe: bool = True,
135
+ sigill: bool = True,
136
+ sigint: bool = True,
137
+ sigsegv: bool = True,
138
+ sigterm: bool = True,
139
+ ) -> (
140
+ Callable[P, _AsyncGeneratorContextManager[T_co]]
141
+ | Callable[
142
+ [Callable[P, AsyncIterator[T_co]]],
143
+ Callable[P, _AsyncGeneratorContextManager[T_co]],
144
+ ]
145
+ ):
146
+ if func is None:
147
+ result = partial(
148
+ enhanced_async_context_manager,
149
+ sigabrt=sigabrt,
150
+ sigfpe=sigfpe,
151
+ sigill=sigill,
152
+ sigint=sigint,
153
+ sigsegv=sigsegv,
154
+ sigterm=sigterm,
155
+ )
156
+ return cast(
157
+ "Callable[[Callable[P, AsyncIterator[T_co]]], Callable[P, _AsyncGeneratorContextManager[T_co]]]",
158
+ result,
159
+ )
160
+ make_agcm = asynccontextmanager(func)
161
+
162
+ @asynccontextmanager
163
+ @wraps(func)
164
+ async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncIterator[T_co]:
165
+ agcm = make_agcm(*args, **kwargs)
166
+ sigabrt0 = _swap_handler(SIGABRT, agcm) if sigabrt else None
167
+ sigfpe0 = _swap_handler(SIGFPE, agcm) if sigfpe else None
168
+ sigill0 = _swap_handler(SIGILL, agcm) if sigill else None
169
+ sigint0 = _swap_handler(SIGINT, agcm) if sigint else None
170
+ sigsegv0 = _swap_handler(SIGSEGV, agcm) if sigsegv else None
171
+ sigterm0 = _swap_handler(SIGTERM, agcm) if sigterm else None
172
+ try:
173
+ async with agcm as value:
174
+ yield value
175
+ finally:
176
+ _ = signal(SIGABRT, sigabrt0) if sigabrt else None
177
+ _ = signal(SIGFPE, sigfpe0) if sigfpe else None
178
+ _ = signal(SIGILL, sigill0) if sigill else None
179
+ _ = signal(SIGINT, sigint0) if sigint else None
180
+ _ = signal(SIGSEGV, sigsegv0) if sigsegv else None
181
+ _ = signal(SIGTERM, sigterm0) if sigterm else None
182
+
183
+ return wrapped
184
+
185
+
186
+ def _swap_handler(
187
+ signum: _SIGNUM,
188
+ obj: _GeneratorContextManager[Any, None, None]
189
+ | _AsyncGeneratorContextManager[Any, None],
190
+ /,
191
+ ) -> _HANDLER:
192
+ orig_handler = getsignal(signum)
193
+ new_handler = _make_handler(signum, obj)
194
+ _ = signal(signum, new_handler)
195
+ return orig_handler
196
+
197
+
198
+ def _make_handler(
199
+ signum: _SIGNUM,
200
+ obj: _GeneratorContextManager[Any, None, None]
201
+ | _AsyncGeneratorContextManager[Any, None],
202
+ /,
203
+ ) -> Callable[[int, FrameType | None], None]:
204
+ orig_handler = getsignal(signum)
205
+
206
+ def new_handler(signum: int, frame: FrameType | None) -> None:
207
+ match obj: # pragma: no cover
208
+ case _GeneratorContextManager() as gcm:
209
+ _ = gcm.__exit__(None, None, None)
210
+ case _AsyncGeneratorContextManager() as agcm:
211
+ loop = get_event_loop()
212
+ _ = loop.call_soon_threadsafe(
213
+ create_task, agcm.__aexit__(None, None, None)
214
+ )
215
+ case never:
216
+ assert_never(never)
217
+ if callable(orig_handler): # pragma: no cover
218
+ orig_handler(signum, frame)
219
+
220
+ return new_handler
26
221
 
27
222
 
28
223
  ##
@@ -41,4 +236,8 @@ def suppress_super_object_attribute_error() -> Iterator[None]:
41
236
  raise
42
237
 
43
238
 
44
- __all__ = ["NoOpContextManager", "suppress_super_object_attribute_error"]
239
+ __all__ = [
240
+ "enhanced_async_context_manager",
241
+ "enhanced_context_manager",
242
+ "suppress_super_object_attribute_error",
243
+ ]
utilities/contextvars.py CHANGED
@@ -1,6 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from contextlib import contextmanager
3
4
  from contextvars import ContextVar
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from collections.abc import Iterator
9
+
4
10
 
5
11
  ##
6
12
 
@@ -19,4 +25,17 @@ def set_global_breakpoint() -> None:
19
25
  _ = _GLOBAL_BREAKPOINT.set(True)
20
26
 
21
27
 
22
- __all__ = ["global_breakpoint", "set_global_breakpoint"]
28
+ ##
29
+
30
+
31
+ @contextmanager
32
+ def yield_set_context(var: ContextVar[bool], /) -> Iterator[None]:
33
+ """Yield a context var as being set."""
34
+ token = var.set(True)
35
+ try:
36
+ yield
37
+ finally:
38
+ _ = var.reset(token)
39
+
40
+
41
+ __all__ = ["global_breakpoint", "set_global_breakpoint", "yield_set_context"]
utilities/cryptography.py CHANGED
@@ -11,18 +11,18 @@ _ENV_VAR = "FERNET_KEY"
11
11
 
12
12
  def encrypt(text: str, /, *, env_var: str = _ENV_VAR) -> bytes:
13
13
  """Encrypt a string."""
14
- return get_fernet(env_var=env_var).encrypt(text.encode())
14
+ return get_fernet(env_var).encrypt(text.encode())
15
15
 
16
16
 
17
17
  def decrypt(text: bytes, /, *, env_var: str = _ENV_VAR) -> str:
18
18
  """Encrypt a string."""
19
- return get_fernet(env_var=env_var).decrypt(text).decode()
19
+ return get_fernet(env_var).decrypt(text).decode()
20
20
 
21
21
 
22
22
  ##
23
23
 
24
24
 
25
- def get_fernet(*, env_var: str = _ENV_VAR) -> Fernet:
25
+ def get_fernet(env_var: str = _ENV_VAR, /) -> Fernet:
26
26
  """Get the Fernet key."""
27
27
  if (key := getenv(env_var)) is None:
28
28
  raise GetFernetError(env_var=env_var)