dycw-utilities 0.168.3__py3-none-any.whl → 0.168.4__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.

Potentially problematic release.


This version of dycw-utilities might be problematic. Click here for more details.

@@ -1,22 +1,22 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.168.3
3
+ Version: 0.168.4
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
7
7
  Requires-Dist: atomicwrites<1.5,>=1.4.1
8
8
  Requires-Dist: typing-extensions<4.16,>=4.15.0
9
9
  Requires-Dist: tzlocal<5.4,>=5.3.1
10
- Requires-Dist: whenever<0.10,>=0.9.0
10
+ Requires-Dist: whenever<0.10,>=0.9.2
11
11
  Provides-Extra: logging
12
12
  Requires-Dist: coloredlogs<15.1,>=15.0.1; extra == 'logging'
13
13
  Provides-Extra: test
14
14
  Requires-Dist: dycw-pytest-only<2.2,>=2.1.1; extra == 'test'
15
- Requires-Dist: hypothesis<6.141,>=6.140.2; extra == 'test'
15
+ Requires-Dist: hypothesis<6.141,>=6.140.3; extra == 'test'
16
16
  Requires-Dist: pytest-asyncio<1.3,>=1.2.0; extra == 'test'
17
17
  Requires-Dist: pytest-cov<7.1,>=7.0.0; extra == 'test'
18
18
  Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
19
- Requires-Dist: pytest-lazy-fixtures<1.4,>=1.3.4; extra == 'test'
19
+ Requires-Dist: pytest-lazy-fixtures<1.5,>=1.4.0; extra == 'test'
20
20
  Requires-Dist: pytest-randomly<4.1,>=4.0.1; extra == 'test'
21
21
  Requires-Dist: pytest-regressions<2.9,>=2.8.3; extra == 'test'
22
22
  Requires-Dist: pytest-repeat<0.10,>=0.9.4; extra == 'test'
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=akCN41GmYDQTP5HE40wt3_aYKHnVSsekCRG-YWzaPys,60
1
+ utilities/__init__.py,sha256=x6cOFdPHlRzAKGeDLMvNM_wBBnw0yuamoyX4OmYbECY,60
2
2
  utilities/aeventkit.py,sha256=OmDBhYGgbsKrB7cdC5FFpJHUatX9O76eTeKVVTksp2Y,12673
3
3
  utilities/altair.py,sha256=nHdpWt8ZwdUwRQN970MvHd5bRWokNqzHcZQEdSHKRuE,9033
4
4
  utilities/asyncio.py,sha256=60l1IwjnRGeaVphAFiwDIHyfKoZYKY-XGpptUxGiU-M,17034
@@ -79,7 +79,6 @@ utilities/text.py,sha256=7SvwcSR2l_5cOrm1samGnR4C-ZI6qyFLHLzSpO1zeHQ,13958
79
79
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
80
80
  utilities/timer.py,sha256=BGlwEVznx67scuLOUohyWJ4d5rTnwtk-IR4yLXFiNfo,2574
81
81
  utilities/traceback.py,sha256=B_sc0TRUv-mGDnF-ek05nbqjmBiHr3-wvxliAqIF5hI,9608
82
- utilities/typed_settings.py,sha256=WJikhgV_92GkjqdQQ2VxfVrwABE5d_Z6tczmcr3H57w,4581
83
82
  utilities/types.py,sha256=IlRrCtPdLkGYVfpe-QIg2qNUgBr8OJNN7BhTKxnhh-M,18817
84
83
  utilities/typing.py,sha256=QYoCIc71e_u5W-kBeiNzrD-GXxpLtMOGc9PqgZjMXeE,25274
85
84
  utilities/tzdata.py,sha256=fgNVj66yUbCSI_-vrRVzSD3gtf-L_8IEJEPjP_Jel5Y,266
@@ -93,8 +92,8 @@ utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
93
92
  utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
94
93
  utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
95
94
  utilities/pytest_plugins/pytest_regressions.py,sha256=mnHYBfdprz50UGVkVzV1bZERZN5CFfoF8YbokGxdFwU,1639
96
- dycw_utilities-0.168.3.dist-info/METADATA,sha256=C-u-XyBFD2YANapH8wgw8GOkrMgHTFbj0pzPd9U_Fvw,1699
97
- dycw_utilities-0.168.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
98
- dycw_utilities-0.168.3.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
99
- dycw_utilities-0.168.3.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
100
- dycw_utilities-0.168.3.dist-info/RECORD,,
95
+ dycw_utilities-0.168.4.dist-info/METADATA,sha256=Po9OuIZ1OfNbLNOHnI38-vG210E3jNV08z5fpw-lWXw,1699
96
+ dycw_utilities-0.168.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
97
+ dycw_utilities-0.168.4.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
98
+ dycw_utilities-0.168.4.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
99
+ dycw_utilities-0.168.4.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.168.3"
3
+ __version__ = "0.168.4"
@@ -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_iso),
61
- (DateDelta, DateDelta.parse_iso),
62
- (DateTimeDelta, DateTimeDelta.parse_iso),
63
- (IPv4Address, IPv4Address),
64
- (IPv6Address, IPv6Address),
65
- (MonthDay, MonthDay.parse_iso),
66
- (Path, partial(_parse_path, resolve=resolve_paths, pwd=Path.cwd())),
67
- (PlainDateTime, PlainDateTime.parse_iso),
68
- (Time, Time.parse_iso),
69
- (TimeDelta, TimeDelta.parse_iso),
70
- (UUID, UUID),
71
- (YearMonth, YearMonth.parse_iso),
72
- (ZonedDateTime, ZonedDateTime.parse_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"]