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
@@ -1,105 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from os import environ
5
- from pathlib import Path
6
- from typing import TYPE_CHECKING, override
7
-
8
- from dotenv import dotenv_values
9
-
10
- from utilities.dataclasses import _ParseDataClassMissingValuesError, parse_dataclass
11
- from utilities.iterables import MergeStrMappingsError, merge_str_mappings
12
- from utilities.pathlib import get_root
13
- from utilities.reprlib import get_repr
14
-
15
- if TYPE_CHECKING:
16
- from collections.abc import Mapping
17
- from collections.abc import Set as AbstractSet
18
-
19
- from utilities.types import (
20
- MaybeCallablePathLike,
21
- ParseObjectExtra,
22
- StrMapping,
23
- TDataclass,
24
- )
25
-
26
-
27
- def load_settings(
28
- cls: type[TDataclass],
29
- /,
30
- *,
31
- path: MaybeCallablePathLike | None = Path.cwd,
32
- globalns: StrMapping | None = None,
33
- localns: StrMapping | None = None,
34
- warn_name_errors: bool = False,
35
- head: bool = False,
36
- case_sensitive: bool = False,
37
- extra_parsers: ParseObjectExtra | None = None,
38
- ) -> TDataclass:
39
- """Load a set of settings from the `.env` file."""
40
- path = get_root(path=path).joinpath(".env")
41
- if not path.exists():
42
- raise _LoadSettingsFileNotFoundError(path=path) from None
43
- maybe_values_dotenv = dotenv_values(path)
44
- try:
45
- maybe_values: Mapping[str, str | None] = merge_str_mappings(
46
- maybe_values_dotenv, environ, case_sensitive=case_sensitive
47
- )
48
- except MergeStrMappingsError as error:
49
- raise _LoadSettingsDuplicateKeysError(
50
- path=path,
51
- values=error.mapping,
52
- counts=error.counts,
53
- case_sensitive=case_sensitive,
54
- ) from None
55
- values = {k: v for k, v in maybe_values.items() if v is not None}
56
- try:
57
- return parse_dataclass(
58
- values,
59
- cls,
60
- globalns=globalns,
61
- localns=localns,
62
- warn_name_errors=warn_name_errors,
63
- head=head,
64
- case_sensitive=case_sensitive,
65
- allow_extra_keys=True,
66
- extra_parsers=extra_parsers,
67
- )
68
- except _ParseDataClassMissingValuesError as error:
69
- raise _LoadSettingsMissingKeysError(path=path, fields=error.fields) from None
70
-
71
-
72
- @dataclass(kw_only=True, slots=True)
73
- class LoadSettingsError(Exception):
74
- path: Path
75
-
76
-
77
- @dataclass(kw_only=True, slots=True)
78
- class _LoadSettingsDuplicateKeysError(LoadSettingsError):
79
- values: StrMapping
80
- counts: Mapping[str, int]
81
- case_sensitive: bool = False
82
-
83
- @override
84
- def __str__(self) -> str:
85
- return f"Mapping {get_repr(dict(self.values))} keys must not contain duplicates (modulo case); got {get_repr(self.counts)}"
86
-
87
-
88
- @dataclass(kw_only=True, slots=True)
89
- class _LoadSettingsFileNotFoundError(LoadSettingsError):
90
- @override
91
- def __str__(self) -> str:
92
- return f"Path {str(self.path)!r} must exist"
93
-
94
-
95
- @dataclass(kw_only=True, slots=True)
96
- class _LoadSettingsMissingKeysError(LoadSettingsError):
97
- fields: AbstractSet[str]
98
-
99
- @override
100
- def __str__(self) -> str:
101
- desc = ", ".join(map(repr, sorted(self.fields)))
102
- return f"Unable to load {str(self.path)!r}; missing value(s) for {desc}"
103
-
104
-
105
- __all__ = ["LoadSettingsError", "load_settings"]
utilities/streamlit.py DELETED
@@ -1,105 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from hmac import compare_digest
4
- from typing import TYPE_CHECKING, Literal
5
-
6
- from streamlit import (
7
- button,
8
- empty,
9
- error,
10
- form,
11
- form_submit_button,
12
- markdown,
13
- secrets,
14
- session_state,
15
- stop,
16
- text_input,
17
- )
18
-
19
- if TYPE_CHECKING:
20
- from collections.abc import Callable
21
-
22
- from streamlit.elements.lib.utils import Key
23
- from streamlit.runtime.state import WidgetArgs, WidgetCallback, WidgetKwargs
24
-
25
-
26
- def centered_button(
27
- label: str,
28
- /,
29
- *,
30
- key: Key | None = None,
31
- help: str | None = None, # noqa: A002
32
- on_click: WidgetCallback | None = None,
33
- args: WidgetArgs | None = None,
34
- kwargs: WidgetKwargs | None = None,
35
- type: Literal["primary", "secondary"] = "secondary", # noqa: A002
36
- disabled: bool = False,
37
- use_container_width: bool = False,
38
- ) -> bool:
39
- """Create a centered button."""
40
- style = r"<style>.row-widget.stButton {text-align: center;}</style>"
41
- _ = markdown(style, unsafe_allow_html=True)
42
- with empty():
43
- return button(
44
- label,
45
- key=key,
46
- help=help,
47
- on_click=on_click,
48
- args=args,
49
- kwargs=kwargs,
50
- type=type,
51
- disabled=disabled,
52
- use_container_width=use_container_width,
53
- )
54
-
55
-
56
- _USERNAME = "username"
57
- _PASSWORD = "password" # noqa: S105
58
- _PASSWORD_CORRECT = "password_correct" # noqa: S105
59
-
60
-
61
- def ensure_logged_in(
62
- *,
63
- skip: bool = False,
64
- before_form: Callable[..., None] | None = None,
65
- after_form: Callable[..., None] | None = None,
66
- ) -> None:
67
- """Ensure the user is logged in."""
68
- if not (skip or _check_password(before_form=before_form, after_form=after_form)):
69
- stop()
70
-
71
-
72
- def _check_password(
73
- *,
74
- before_form: Callable[..., None] | None = None,
75
- after_form: Callable[..., None] | None = None,
76
- ) -> bool:
77
- """Return `True` if the user had a correct password."""
78
- if session_state.get("password_correct", False):
79
- return True
80
- if before_form is not None:
81
- before_form()
82
- with form("Credentials"):
83
- _ = text_input("Username", key=_USERNAME)
84
- _ = text_input("Password", type="password", key=_PASSWORD)
85
- _ = form_submit_button("Log in", on_click=_password_entered)
86
- if after_form is not None:
87
- after_form()
88
- if _PASSWORD_CORRECT in session_state:
89
- _ = error("Username/password combination invalid or incorrect")
90
- return False
91
-
92
-
93
- def _password_entered() -> None:
94
- """Check whether a password entered by the user is correct."""
95
- if (session_state[_USERNAME] in secrets["passwords"]) and compare_digest(
96
- session_state[_PASSWORD], secrets.passwords[session_state[_USERNAME]]
97
- ):
98
- session_state[_PASSWORD_CORRECT] = True
99
- del session_state[_PASSWORD]
100
- del session_state[_USERNAME]
101
- else:
102
- session_state[_PASSWORD_CORRECT] = False
103
-
104
-
105
- __all__ = ["ensure_logged_in"]
utilities/sys.py DELETED
@@ -1,87 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from asyncio import TaskGroup, run
4
- from dataclasses import dataclass
5
- from functools import partial
6
- from inspect import iscoroutinefunction
7
- from sys import version_info
8
- from typing import TYPE_CHECKING, Any, cast, override
9
-
10
- from utilities.logging import get_logger
11
-
12
- if TYPE_CHECKING:
13
- from collections.abc import Callable, Iterable
14
- from types import TracebackType
15
-
16
- from utilities.types import Coroutine1, LoggerOrName, MaybeCoroutine1, StrMapping
17
-
18
-
19
- VERSION_MAJOR_MINOR = (version_info.major, version_info.minor)
20
-
21
-
22
- def make_except_hook(
23
- *,
24
- logger: LoggerOrName | None = None,
25
- message: object = "",
26
- extra: StrMapping | None = None,
27
- callbacks: Iterable[Callable[[], MaybeCoroutine1[None]]] | None = None,
28
- ) -> Callable[
29
- [type[BaseException] | None, BaseException | None, TracebackType | None], None
30
- ]:
31
- """Create an exception hook with various features."""
32
- return partial(
33
- _make_except_hook_inner,
34
- logger=logger,
35
- message=message,
36
- extra=extra,
37
- callbacks=callbacks,
38
- )
39
-
40
-
41
- def _make_except_hook_inner(
42
- exc_type: type[BaseException] | None,
43
- exc_val: BaseException | None,
44
- traceback: TracebackType | None,
45
- /,
46
- *,
47
- logger: LoggerOrName | None = None,
48
- message: object = "",
49
- extra: StrMapping | None = None,
50
- callbacks: Iterable[Callable[[], MaybeCoroutine1[None]]] | None = None,
51
- ) -> None:
52
- """Exception hook to log the traceback."""
53
- _ = (exc_type, traceback)
54
- if exc_val is None:
55
- raise MakeExceptHookError
56
- logger_use = get_logger(logger=logger)
57
- exc_info = (exc_type, exc_val, traceback)
58
- logger_use.exception(message, exc_info=cast("Any", exc_info), extra=extra)
59
- async_callbacks: list[Callable[[], Coroutine1[None]]] = []
60
- if callbacks is not None:
61
- for callback in callbacks:
62
- if not iscoroutinefunction(callback):
63
- cast("Callable[[], None]", callback)()
64
- else: # skipif-ci
65
- async_callback = cast("Callable[[], Coroutine1[None]]", callback)
66
- async_callbacks.append(async_callback)
67
- if len(async_callbacks) >= 1: # skipif-ci
68
- run(_run_async_callbacks(async_callbacks))
69
-
70
-
71
- @dataclass(kw_only=True, slots=True)
72
- class MakeExceptHookError(Exception):
73
- @override
74
- def __str__(self) -> str:
75
- return "No exception to log"
76
-
77
-
78
- async def _run_async_callbacks(
79
- callbacks: Iterable[Callable[[], Coroutine1[None]]], /
80
- ) -> None:
81
- """Run all asynchronous callbacks."""
82
- async with TaskGroup() as tg: # skipif-ci
83
- for callback in callbacks:
84
- _ = tg.create_task(callback())
85
-
86
-
87
- __all__ = ["VERSION_MAJOR_MINOR", "MakeExceptHookError", "make_except_hook"]
utilities/tenacity.py DELETED
@@ -1,145 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from contextlib import AbstractAsyncContextManager, asynccontextmanager
4
- from typing import TYPE_CHECKING, Any, override
5
-
6
- from tenacity import (
7
- AsyncRetrying,
8
- AttemptManager,
9
- RetryCallState,
10
- RetryError,
11
- after_nothing,
12
- before_nothing,
13
- retry_if_exception_type,
14
- stop_never,
15
- wait_none,
16
- )
17
- from tenacity import wait_exponential_jitter as _wait_exponential_jitter
18
- from tenacity._utils import MAX_WAIT
19
- from tenacity.asyncio import _portable_async_sleep
20
-
21
- from utilities.asyncio import timeout_dur
22
- from utilities.contextlib import NoOpContextManager
23
- from utilities.datetime import datetime_duration_to_float
24
-
25
- if TYPE_CHECKING:
26
- from collections.abc import AsyncIterator, Callable
27
-
28
- from tenacity.retry import RetryBaseT
29
- from tenacity.stop import StopBaseT
30
- from tenacity.wait import WaitBaseT
31
-
32
- from utilities.types import Duration, MaybeAwaitable
33
-
34
-
35
- type MaybeAttemptManager = NoOpContextManager | AttemptManager
36
- type MaybeAttemptContextManager = AbstractAsyncContextManager[MaybeAttemptManager]
37
-
38
-
39
- class wait_exponential_jitter(_wait_exponential_jitter): # noqa: N801
40
- """Subclass of `wait_exponential_jitter` accepting durations."""
41
-
42
- @override
43
- def __init__(
44
- self,
45
- initial: Duration = 1,
46
- max: Duration = MAX_WAIT,
47
- exp_base: float = 2,
48
- jitter: Duration = 1,
49
- ) -> None:
50
- super().__init__(
51
- initial=datetime_duration_to_float(initial),
52
- max=datetime_duration_to_float(max),
53
- exp_base=exp_base,
54
- jitter=datetime_duration_to_float(jitter),
55
- )
56
-
57
-
58
- async def yield_attempts(
59
- *,
60
- sleep: Callable[[int | float], MaybeAwaitable[None]] | None = None,
61
- stop: StopBaseT | None = None,
62
- wait: WaitBaseT | None = None,
63
- retry: RetryBaseT | None = None,
64
- before: Callable[[RetryCallState], MaybeAwaitable[None]] | None = None,
65
- after: Callable[[RetryCallState], MaybeAwaitable[None]] | None = None,
66
- before_sleep: Callable[[RetryCallState], MaybeAwaitable[None]] | None = None,
67
- reraise: bool | None = None,
68
- retry_error_cls: type[RetryError] | None = None,
69
- retry_error_callback: Callable[[RetryCallState], MaybeAwaitable[Any]] | None = None,
70
- ) -> AsyncIterator[MaybeAttemptManager]:
71
- """Yield the attempts."""
72
- if (
73
- (sleep is None)
74
- and (stop is None)
75
- and (wait is None)
76
- and (retry is None)
77
- and (before is None)
78
- and (after is None)
79
- and (before_sleep is None)
80
- and (reraise is None)
81
- and (retry_error_cls is None)
82
- ):
83
- yield NoOpContextManager()
84
- else:
85
- retrying = AsyncRetrying(
86
- sleep=_portable_async_sleep if sleep is None else sleep,
87
- stop=stop_never if stop is None else stop,
88
- wait=wait_none() if wait is None else wait,
89
- retry=retry_if_exception_type() if retry is None else retry,
90
- before=before_nothing if before is None else before,
91
- after=after_nothing if after is None else after,
92
- before_sleep=None if before_sleep is None else before_sleep,
93
- reraise=False if reraise is None else reraise,
94
- retry_error_cls=RetryError if retry_error_cls is None else retry_error_cls,
95
- retry_error_callback=retry_error_callback,
96
- )
97
- async for attempt in retrying:
98
- yield attempt
99
-
100
-
101
- async def yield_timeout_attempts(
102
- *,
103
- sleep: Callable[[int | float], MaybeAwaitable[None]] | None = None,
104
- stop: StopBaseT | None = None,
105
- wait: WaitBaseT | None = None,
106
- retry: RetryBaseT | None = None,
107
- before: Callable[[RetryCallState], MaybeAwaitable[None]] | None = None,
108
- after: Callable[[RetryCallState], MaybeAwaitable[None]] | None = None,
109
- before_sleep: Callable[[RetryCallState], MaybeAwaitable[None]] | None = None,
110
- reraise: bool | None = None,
111
- retry_error_cls: type[RetryError] | None = None,
112
- retry_error_callback: Callable[[RetryCallState], MaybeAwaitable[Any]] | None = None,
113
- timeout: Duration | None = None,
114
- ) -> AsyncIterator[MaybeAttemptContextManager]:
115
- """Yield the attempts, with timeout."""
116
- async for attempt in yield_attempts(
117
- sleep=sleep,
118
- stop=stop,
119
- wait=wait,
120
- retry=retry,
121
- before=before,
122
- after=after,
123
- before_sleep=before_sleep,
124
- reraise=reraise,
125
- retry_error_cls=retry_error_cls,
126
- retry_error_callback=retry_error_callback,
127
- ):
128
-
129
- @asynccontextmanager
130
- async def new(
131
- attempt: MaybeAttemptManager, /
132
- ) -> AsyncIterator[MaybeAttemptManager]:
133
- with attempt:
134
- async with timeout_dur(duration=timeout):
135
- yield attempt
136
-
137
- yield new(attempt)
138
-
139
-
140
- __all__ = [
141
- "MaybeAttemptManager",
142
- "wait_exponential_jitter",
143
- "yield_attempts",
144
- "yield_timeout_attempts",
145
- ]