dycw-utilities 0.133.7__py3-none-any.whl → 0.134.1__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.
utilities/pudb.py ADDED
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ from asyncio import iscoroutinefunction
4
+ from functools import partial, wraps
5
+ from typing import TYPE_CHECKING, Any, NoReturn, cast, overload
6
+
7
+ from pudb import post_mortem
8
+
9
+ from utilities.os import GetEnvVarError, get_env_var
10
+
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Callable
13
+
14
+
15
+ _ENV_VAR = "DEBUG"
16
+
17
+
18
+ @overload
19
+ def call_pudb[F: Callable](func: F, /, *, env_var: str = _ENV_VAR) -> F: ...
20
+ @overload
21
+ def call_pudb[F: Callable](
22
+ func: None = None, /, *, env_var: str = _ENV_VAR
23
+ ) -> Callable[[F], F]: ...
24
+ def call_pudb[F: Callable](
25
+ func: F | None = None, /, *, env_var: str = _ENV_VAR
26
+ ) -> F | Callable[[F], F]:
27
+ """Call `pudb` upon failure, if the required environment variable is set."""
28
+ if func is None:
29
+ result = partial(call_pudb, env_var=env_var)
30
+ return cast("Callable[[F], F]", result)
31
+
32
+ if not iscoroutinefunction(func):
33
+
34
+ @wraps(func)
35
+ def wrapped_sync(*args: Any, **kwargs: Any) -> Any:
36
+ try:
37
+ return func(*args, **kwargs)
38
+ except Exception as error: # noqa: BLE001
39
+ _call_pudb(error, env_var=env_var)
40
+
41
+ return cast("F", wrapped_sync)
42
+
43
+ @wraps(func)
44
+ async def wrapped_async(*args: Any, **kwargs: Any) -> Any:
45
+ try:
46
+ return await func(*args, **kwargs)
47
+ except Exception as error: # noqa: BLE001
48
+ _call_pudb(error, env_var=env_var)
49
+
50
+ return cast("F", wrapped_async)
51
+
52
+
53
+ def _call_pudb(error: Exception, /, *, env_var: str = _ENV_VAR) -> NoReturn:
54
+ try:
55
+ _ = get_env_var(env_var)
56
+ except GetEnvVarError:
57
+ raise error from None
58
+ post_mortem() # pragma: no cover
59
+ raise error # pragma: no cover
60
+
61
+
62
+ __all__ = ["call_pudb"]
utilities/pydantic.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
4
  from pathlib import Path
5
- from typing import TYPE_CHECKING, TypeVar, override
5
+ from typing import TYPE_CHECKING, override
6
6
 
7
7
  from pydantic import BaseModel
8
8
 
@@ -11,8 +11,6 @@ from utilities.atomicwrites import writer
11
11
  if TYPE_CHECKING:
12
12
  from utilities.types import PathLike
13
13
 
14
- _TBaseModel = TypeVar("_TBaseModel", bound=BaseModel)
15
-
16
14
 
17
15
  class HashableBaseModel(BaseModel):
18
16
  """Subclass of BaseModel which is hashable."""
@@ -22,7 +20,7 @@ class HashableBaseModel(BaseModel):
22
20
  return hash((type(self), *self.__dict__.values()))
23
21
 
24
22
 
25
- def load_model(model: type[_TBaseModel], path: PathLike, /) -> _TBaseModel:
23
+ def load_model[T: BaseModel](model: type[T], path: PathLike, /) -> T:
26
24
  path = Path(path)
27
25
  try:
28
26
  with path.open() as fh:
utilities/pytest.py CHANGED
@@ -5,7 +5,7 @@ from functools import partial, wraps
5
5
  from inspect import iscoroutinefunction
6
6
  from os import environ
7
7
  from pathlib import Path
8
- from typing import TYPE_CHECKING, Any, ParamSpec, assert_never, cast, override
8
+ from typing import TYPE_CHECKING, Any, assert_never, cast, override
9
9
 
10
10
  from pytest import fixture
11
11
  from whenever import ZonedDateTime
@@ -24,6 +24,7 @@ from utilities.platform import (
24
24
  IS_WINDOWS,
25
25
  )
26
26
  from utilities.random import get_state
27
+ from utilities.types import MaybeCoro
27
28
  from utilities.whenever import SECOND, get_now_local
28
29
 
29
30
  if TYPE_CHECKING:
@@ -32,7 +33,7 @@ if TYPE_CHECKING:
32
33
 
33
34
  from whenever import TimeDelta
34
35
 
35
- from utilities.types import Coroutine1, PathLike, TCallableMaybeCoroutine1None
36
+ from utilities.types import Coro, PathLike
36
37
 
37
38
  try: # WARNING: this package cannot use unguarded `pytest` imports
38
39
  from _pytest.config import Config
@@ -56,12 +57,6 @@ else:
56
57
  skipif_not_linux = mark.skipif(IS_NOT_LINUX, reason="Skipped for non-Linux")
57
58
 
58
59
 
59
- _P = ParamSpec("_P")
60
-
61
-
62
- ##
63
-
64
-
65
60
  def add_pytest_addoption(parser: Parser, options: Sequence[str], /) -> None:
66
61
  """Add the `--slow`, etc options to pytest.
67
62
 
@@ -167,27 +162,27 @@ def random_state(*, seed: int) -> Random:
167
162
  ##
168
163
 
169
164
 
170
- def throttle(
165
+ def throttle[F: Callable[..., MaybeCoro[None]]](
171
166
  *, root: PathLike | None = None, delta: TimeDelta = SECOND, on_try: bool = False
172
- ) -> Callable[[TCallableMaybeCoroutine1None], TCallableMaybeCoroutine1None]:
167
+ ) -> Callable[[F], F]:
173
168
  """Throttle a test. On success by default, on try otherwise."""
174
169
  return cast("Any", partial(_throttle_inner, root=root, delta=delta, on_try=on_try))
175
170
 
176
171
 
177
- def _throttle_inner(
178
- func: TCallableMaybeCoroutine1None,
172
+ def _throttle_inner[F: Callable[..., MaybeCoro[None]]](
173
+ func: F,
179
174
  /,
180
175
  *,
181
176
  root: PathLike | None = None,
182
177
  delta: TimeDelta = SECOND,
183
178
  on_try: bool = False,
184
- ) -> TCallableMaybeCoroutine1None:
179
+ ) -> F:
185
180
  """Throttle a test function/method."""
186
181
  match bool(iscoroutinefunction(func)), on_try:
187
182
  case False, False:
188
183
 
189
184
  @wraps(func)
190
- def throttle_sync_on_pass(*args: _P.args, **kwargs: _P.kwargs) -> None:
185
+ def throttle_sync_on_pass(*args: Any, **kwargs: Any) -> None:
191
186
  _skipif_recent(root=root, delta=delta)
192
187
  cast("Callable[..., None]", func)(*args, **kwargs)
193
188
  _write(root=root)
@@ -197,7 +192,7 @@ def _throttle_inner(
197
192
  case False, True:
198
193
 
199
194
  @wraps(func)
200
- def throttle_sync_on_try(*args: _P.args, **kwargs: _P.kwargs) -> None:
195
+ def throttle_sync_on_try(*args: Any, **kwargs: Any) -> None:
201
196
  _skipif_recent(root=root, delta=delta)
202
197
  _write(root=root)
203
198
  cast("Callable[..., None]", func)(*args, **kwargs)
@@ -207,11 +202,9 @@ def _throttle_inner(
207
202
  case True, False:
208
203
 
209
204
  @wraps(func)
210
- async def throttle_async_on_pass(
211
- *args: _P.args, **kwargs: _P.kwargs
212
- ) -> None:
205
+ async def throttle_async_on_pass(*args: Any, **kwargs: Any) -> None:
213
206
  _skipif_recent(root=root, delta=delta)
214
- await cast("Callable[..., Coroutine1[None]]", func)(*args, **kwargs)
207
+ await cast("Callable[..., Coro[None]]", func)(*args, **kwargs)
215
208
  _write(root=root)
216
209
 
217
210
  return cast("Any", throttle_async_on_pass)
@@ -219,12 +212,10 @@ def _throttle_inner(
219
212
  case True, True:
220
213
 
221
214
  @wraps(func)
222
- async def throttle_async_on_try(
223
- *args: _P.args, **kwargs: _P.kwargs
224
- ) -> None:
215
+ async def throttle_async_on_try(*args: Any, **kwargs: Any) -> None:
225
216
  _skipif_recent(root=root, delta=delta)
226
217
  _write(root=root)
227
- await cast("Callable[..., Coroutine1[None]]", func)(*args, **kwargs)
218
+ await cast("Callable[..., Coro[None]]", func)(*args, **kwargs)
228
219
 
229
220
  return cast("Any", throttle_async_on_try)
230
221
 
@@ -11,21 +11,17 @@ from utilities.dataclasses import _ParseDataClassMissingValuesError, parse_datac
11
11
  from utilities.iterables import MergeStrMappingsError, merge_str_mappings
12
12
  from utilities.pathlib import get_root
13
13
  from utilities.reprlib import get_repr
14
+ from utilities.types import Dataclass
14
15
 
15
16
  if TYPE_CHECKING:
16
17
  from collections.abc import Mapping
17
18
  from collections.abc import Set as AbstractSet
18
19
 
19
- from utilities.types import (
20
- MaybeCallablePathLike,
21
- ParseObjectExtra,
22
- StrMapping,
23
- TDataclass,
24
- )
20
+ from utilities.types import MaybeCallablePathLike, ParseObjectExtra, StrMapping
25
21
 
26
22
 
27
- def load_settings(
28
- cls: type[TDataclass],
23
+ def load_settings[T: Dataclass](
24
+ cls: type[T],
29
25
  /,
30
26
  *,
31
27
  path: MaybeCallablePathLike | None = Path.cwd,
@@ -35,7 +31,7 @@ def load_settings(
35
31
  head: bool = False,
36
32
  case_sensitive: bool = False,
37
33
  extra_parsers: ParseObjectExtra | None = None,
38
- ) -> TDataclass:
34
+ ) -> T:
39
35
  """Load a set of settings from the `.env` file."""
40
36
  path = get_root(path=path).joinpath(".env")
41
37
  if not path.exists():
utilities/random.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from random import Random, SystemRandom
4
- from typing import TYPE_CHECKING, TypeVar
4
+ from typing import TYPE_CHECKING
5
5
 
6
6
  if TYPE_CHECKING:
7
7
  from collections.abc import Iterable
@@ -9,7 +9,6 @@ if TYPE_CHECKING:
9
9
  from utilities.types import Seed
10
10
 
11
11
 
12
- _T = TypeVar("_T")
13
12
  SYSTEM_RANDOM = SystemRandom()
14
13
 
15
14
 
@@ -54,7 +53,7 @@ def get_state(*, seed: Seed | None = None) -> Random:
54
53
 
55
54
 
56
55
  ##
57
- def shuffle(iterable: Iterable[_T], /, *, seed: Seed | None = None) -> list[_T]:
56
+ def shuffle[T](iterable: Iterable[T], /, *, seed: Seed | None = None) -> list[T]:
58
57
  """Shuffle an iterable."""
59
58
  copy = list(iterable).copy()
60
59
  state = get_state(seed=seed)