dycw-utilities 0.132.1__py3-none-any.whl → 0.132.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.132.1
3
+ Version: 0.132.3
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -17,7 +17,6 @@ Requires-Dist: pytest-asyncio<1.1,>=1.0.0; extra == 'test'
17
17
  Requires-Dist: pytest-cov<6.2,>=6.1.1; extra == 'test'
18
18
  Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
19
19
  Requires-Dist: pytest-lazy-fixtures<1.2,>=1.1.4; extra == 'test'
20
- Requires-Dist: pytest-randomly<3.17,>=3.16.0; extra == 'test'
21
20
  Requires-Dist: pytest-regressions<2.9,>=2.8.0; extra == 'test'
22
21
  Requires-Dist: pytest-rerunfailures<16,>=15.1; extra == 'test'
23
22
  Requires-Dist: pytest-rng<1.1,>=1.0.0; extra == 'test'
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=PDNEVCX07_lBTxtKe5hvd9DUvYwh0iPoKmXmtdlxLdM,60
1
+ utilities/__init__.py,sha256=9NsY4uZT1cOp7_D5RHpPZYyj4cpKLbQLR7K0gu5KJfU,60
2
2
  utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
3
3
  utilities/altair.py,sha256=HeZBVUocjkrTNwwKrClppsIqgNFF-ykv05HfZSoHYno,9104
4
4
  utilities/asyncio.py,sha256=USWMMrHqPVRr20vlIn_n5JLimyqa-5xLhuqDYWJed8A,37586
@@ -55,7 +55,6 @@ utilities/psutil.py,sha256=0j4YxtVb8VjaaKKiHg6UEK95SUPkEcENgPtLgPJsNv0,3760
55
55
  utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
56
  utilities/pydantic.py,sha256=aP6OKowg2Md4rgQuQ5qTSF4bTbBuq7WtRb7zS3JSRGY,1841
57
57
  utilities/pyinstrument.py,sha256=_Rfq6Gg4NKV2NF0dRYOpK2IRyyePxI7-3UmHIQLYrlQ,852
58
- utilities/pyrsistent.py,sha256=wVOVIe_68AAaa-lUE9y-TEzDawVp1uEIc_zfoDgr5ww,2287
59
58
  utilities/pytest.py,sha256=xSDybvkvdj7Ix-Tpc1INctKZV07qwrQvJlQonSimB7o,8273
60
59
  utilities/pytest_regressions.py,sha256=YI55B7EtLjhz7zPJZ6NK9bWrxrKCKabWZJe1cwcbA5o,5082
61
60
  utilities/python_dotenv.py,sha256=edXsvHZhZnYeqfMfrsRRpj7_9eJI6uizh3xLx8Q9B3w,3228
@@ -78,7 +77,7 @@ utilities/text.py,sha256=ymBFlP_cA8OgNnZRVNs7FAh7OG8HxE6YkiLEMZv5g_A,11297
78
77
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
79
78
  utilities/timer.py,sha256=oYqRQ-G-DMOOHB6a4yP5-PJDVimLnbNkMnkOj_jUmFg,2474
80
79
  utilities/traceback.py,sha256=i-790AQbTrDA8MiYyOcYPFpm48I558VR_kL_7x4ypfY,8503
81
- utilities/typed_settings.py,sha256=zUA0_CmVJT5rwrm3e-dZO83OdPXEel4NfVK24NAD5Vk,1779
80
+ utilities/typed_settings.py,sha256=DqJsJjSit9wd_OA3KyMDpx2zatKIi5QhuARI9TPl3rk,3701
82
81
  utilities/types.py,sha256=fuJQiVjKYKL9g3F5H7oW_98Xm1-R5Xq4t4kU-aHxW0M,18826
83
82
  utilities/typing.py,sha256=kVWK6ciV8T0MKxnFQcMSEr_XlRisspH5aBTTosMUh30,13872
84
83
  utilities/tzdata.py,sha256=fgNVj66yUbCSI_-vrRVzSD3gtf-L_8IEJEPjP_Jel5Y,266
@@ -89,7 +88,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
89
88
  utilities/whenever.py,sha256=tArX9unVEKhRYdvbUFa83e4hrzdtMKKCEN4QWTaYd8c,19524
90
89
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
91
90
  utilities/zoneinfo.py,sha256=oEH-nL3t4h9uawyZqWDtNtDAl6M-CLpLYGI_nI6DulM,1971
92
- dycw_utilities-0.132.1.dist-info/METADATA,sha256=cYm5yRE6A9tEMFSetIrL_VxIf2AaSpV6MFz_e29eHWs,1584
93
- dycw_utilities-0.132.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
94
- dycw_utilities-0.132.1.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
95
- dycw_utilities-0.132.1.dist-info/RECORD,,
91
+ dycw_utilities-0.132.3.dist-info/METADATA,sha256=zfgSgg9oCTc9xP-C8UDS_9rET8qgWp9M5dOW9aZzhI0,1522
92
+ dycw_utilities-0.132.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
+ dycw_utilities-0.132.3.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
+ dycw_utilities-0.132.3.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.132.1"
3
+ __version__ = "0.132.3"
@@ -1,8 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from dataclasses import dataclass
4
+ from ipaddress import IPv4Address, IPv6Address
5
+ from pathlib import Path
6
+ from re import search
3
7
  from typing import TYPE_CHECKING, Any, TypeVar, override
4
8
 
9
+ import typed_settings
10
+ from typed_settings import EnvLoader, FileLoader, find
5
11
  from typed_settings.converters import TSConverter
12
+ from typed_settings.loaders import TomlFormat
6
13
  from whenever import (
7
14
  Date,
8
15
  DateDelta,
@@ -13,13 +20,23 @@ from whenever import (
13
20
  ZonedDateTime,
14
21
  )
15
22
 
23
+ from utilities.iterables import always_iterable
24
+
16
25
  if TYPE_CHECKING:
17
26
  from collections.abc import Callable
18
27
 
28
+ from typed_settings.loaders import Loader
29
+ from typed_settings.processors import Processor
30
+
31
+ from utilities.types import MaybeIterable, PathLike
32
+
19
33
 
20
34
  _T = TypeVar("_T")
21
35
 
22
36
 
37
+ ##
38
+
39
+
23
40
  class ExtendedTSConverter(TSConverter):
24
41
  """An extension of the TSConverter for custom types."""
25
42
 
@@ -35,6 +52,8 @@ class ExtendedTSConverter(TSConverter):
35
52
  (Date, Date.parse_common_iso),
36
53
  (DateDelta, DateDelta.parse_common_iso),
37
54
  (DateTimeDelta, DateTimeDelta.parse_common_iso),
55
+ (IPv4Address, IPv4Address),
56
+ (IPv6Address, IPv6Address),
38
57
  (PlainDateTime, PlainDateTime.parse_common_iso),
39
58
  (Time, Time.parse_common_iso),
40
59
  (TimeDelta, TimeDelta.parse_common_iso),
@@ -58,4 +77,48 @@ def _make_converter(
58
77
  return hook
59
78
 
60
79
 
61
- __all__ = ["ExtendedTSConverter"]
80
+ ##
81
+
82
+ _BASE_DIR: Path = Path()
83
+
84
+
85
+ def load_settings(
86
+ cls: type[_T],
87
+ app_name: str,
88
+ /,
89
+ *,
90
+ filenames: MaybeIterable[str] = "settings.toml",
91
+ start_dir: PathLike | None = None,
92
+ loaders: MaybeIterable[Loader] | None = None,
93
+ processors: MaybeIterable[Processor] = (),
94
+ base_dir: Path = _BASE_DIR,
95
+ ) -> _T:
96
+ if not search(r"^[A-Za-z]+(?:_[A-Za-z]+)*$", app_name):
97
+ raise LoadSettingsError(appname=app_name)
98
+ filenames_use = list(always_iterable(filenames))
99
+ start_dir_use = None if start_dir is None else Path(start_dir)
100
+ files = [find(filename, start_dir=start_dir_use) for filename in filenames_use]
101
+ file_loader = FileLoader(formats={"*.toml": TomlFormat(app_name)}, files=files)
102
+ env_loader = EnvLoader(f"{app_name.upper()}__", nested_delimiter="__")
103
+ loaders_use: list[Loader] = [file_loader, env_loader]
104
+ if loaders is not None:
105
+ loaders_use.extend(always_iterable(loaders))
106
+ return typed_settings.load_settings(
107
+ cls,
108
+ loaders_use,
109
+ processors=list(always_iterable(processors)),
110
+ converter=ExtendedTSConverter(),
111
+ base_dir=base_dir,
112
+ )
113
+
114
+
115
+ @dataclass(kw_only=True, slots=True)
116
+ class LoadSettingsError(Exception):
117
+ appname: str
118
+
119
+ @override
120
+ def __str__(self) -> str:
121
+ return f"Invalid app name; got {self.appname!r}"
122
+
123
+
124
+ __all__ = ["ExtendedTSConverter", "LoadSettingsError", "load_settings"]
utilities/pyrsistent.py DELETED
@@ -1,89 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING, Any, TypeVar, dataclass_transform, overload
4
-
5
- from pyrsistent import PRecord as _PRecord
6
- from pyrsistent import field as _field
7
- from pyrsistent._field_common import (
8
- PFIELD_NO_FACTORY,
9
- PFIELD_NO_INITIAL,
10
- PFIELD_NO_INVARIANT,
11
- PFIELD_NO_SERIALIZER,
12
- PFIELD_NO_TYPE,
13
- )
14
-
15
- if TYPE_CHECKING:
16
- from collections.abc import Callable
17
-
18
- from utilities.types import TypeLike
19
-
20
-
21
- _T = TypeVar("_T")
22
- _U = TypeVar("_U")
23
-
24
-
25
- @overload
26
- def field(
27
- *,
28
- type: type[_T],
29
- invariant: Callable[[Any], tuple[bool, Any]] = ...,
30
- default: Any = ...,
31
- mandatory: bool = ...,
32
- factory: Callable[[_U], _U] = ...,
33
- serializer: Callable[[Any, Any], Any] = ...,
34
- ) -> _T: ...
35
- @overload
36
- def field(
37
- *,
38
- type: tuple[type[_T]],
39
- invariant: Callable[[Any], tuple[bool, Any]] = ...,
40
- default: Any = ...,
41
- mandatory: bool = ...,
42
- factory: Callable[[_U], _U] = ...,
43
- serializer: Callable[[Any, Any], Any] = ...,
44
- ) -> _T: ...
45
- @overload
46
- def field(
47
- *,
48
- type: tuple[type[_T], type[_U]],
49
- invariant: Callable[[Any], tuple[bool, Any]] = ...,
50
- default: Any = ...,
51
- mandatory: bool = ...,
52
- factory: Callable[[_U], _U] = ...,
53
- serializer: Callable[[Any, Any], Any] = ...,
54
- ) -> _T | _U: ...
55
- @overload
56
- def field(
57
- *,
58
- type: tuple[Any, ...] = ...,
59
- invariant: Callable[[Any], tuple[bool, Any]] = ...,
60
- default: Any = ...,
61
- mandatory: bool = ...,
62
- factory: Callable[[_U], _U] = ...,
63
- serializer: Callable[[Any, Any], Any] = ...,
64
- ) -> Any: ...
65
- def field(
66
- *,
67
- type: TypeLike[_T] = PFIELD_NO_TYPE, # noqa: A002
68
- invariant: Callable[[Any], tuple[bool, Any]] = PFIELD_NO_INVARIANT,
69
- default: Any = PFIELD_NO_INITIAL,
70
- mandatory: bool = False,
71
- factory: Callable[[_U], _U] = PFIELD_NO_FACTORY,
72
- serializer: Callable[[Any, Any], Any] = PFIELD_NO_SERIALIZER,
73
- ) -> Any:
74
- """Field specification factory for :py:class:`PRecord`."""
75
- return _field(
76
- type=type,
77
- invariant=invariant,
78
- initial=default,
79
- mandatory=mandatory,
80
- factory=factory,
81
- serializer=serializer,
82
- )
83
-
84
-
85
- @dataclass_transform(kw_only_default=True, field_specifiers=(field,))
86
- class PRecord(_PRecord): ...
87
-
88
-
89
- __all__ = ["PRecord", "field"]