dycw-utilities 0.166.16__py3-none-any.whl → 0.166.18__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.166.16
3
+ Version: 0.166.18
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -18,13 +18,13 @@ Requires-Dist: pytest-cov<6.3,>=6.2.1; extra == 'test'
18
18
  Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
19
19
  Requires-Dist: pytest-lazy-fixtures<1.4,>=1.3.4; extra == 'test'
20
20
  Requires-Dist: pytest-randomly<3.17,>=3.16.0; extra == 'test'
21
- Requires-Dist: pytest-regressions<2.9,>=2.8.2; extra == 'test'
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'
23
23
  Requires-Dist: pytest-rerunfailures<16.1,>=16.0.1; extra == 'test'
24
24
  Requires-Dist: pytest-rng<1.1,>=1.0.0; extra == 'test'
25
25
  Requires-Dist: pytest-timeout<2.5,>=2.4.0; extra == 'test'
26
26
  Requires-Dist: pytest-xdist<3.9,>=3.8.0; extra == 'test'
27
- Requires-Dist: pytest<8.5,>=8.4.1; extra == 'test'
27
+ Requires-Dist: pytest<8.5,>=8.4.2; extra == 'test'
28
28
  Requires-Dist: testbook<0.5,>=0.4.2; extra == 'test'
29
29
  Description-Content-Type: text/markdown
30
30
 
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=FemsKd9UtEJaP-BgHzGaJoLRpH-_mhv6Y7fl8XXOKCY,61
1
+ utilities/__init__.py,sha256=eLNz3cP1mG4UANDG5gmXTTw10EMSDNNxdbNlerf7XNk,61
2
2
  utilities/aeventkit.py,sha256=ddoleSwW9zdc2tjX5Ge0pMKtYwV_JMxhHYOxnWX2AGM,12609
3
3
  utilities/altair.py,sha256=nHdpWt8ZwdUwRQN970MvHd5bRWokNqzHcZQEdSHKRuE,9033
4
4
  utilities/asyncio.py,sha256=PUedzQ5deqlSECQ33sam9cRzI9TnygHz3FdOqWJWPTM,15288
@@ -53,8 +53,8 @@ utilities/pottery.py,sha256=ggMN72Y7wx7Js8VN6eyNyodpm8TIYqZHGghkDPXIVWk,3949
53
53
  utilities/pqdm.py,sha256=idv2seRVP2f6NeSfpeEnT5A-tQezaHZKDyeu16g2-0E,3091
54
54
  utilities/psutil.py,sha256=KUlu4lrUw9Zg1V7ZGetpWpGb9DB8l_SSDWGbANFNCPU,2104
55
55
  utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
- utilities/pydantic_settings.py,sha256=oUosQ7KExpXTC1PioIc4bju6C6Yj6gMZQLBpOKRL4Xc,2166
57
- utilities/pydantic_settings_sops.py,sha256=3RGZbRgfJjAxveMUNpdf7TNBtGuEBYZZ5_TkIxf1mNE,1194
56
+ utilities/pydantic_settings.py,sha256=CWrTtzupGxpSsImjDCuVtQ2HcGmF8nHA4W4hGq8WvXY,5688
57
+ utilities/pydantic_settings_sops.py,sha256=LWI3NTQP8hSaGkoRbahyqaalF0HuBg7o63-p7boHgpQ,1119
58
58
  utilities/pyinstrument.py,sha256=hnXaL-4HE7wWBI5JKaPfYTpsrXe68YiuZKahHV0VJn8,841
59
59
  utilities/pytest.py,sha256=Pu8jmeNQq659uQmZsmFj6lb0sHMDsNN3fcd6M21J-ww,7723
60
60
  utilities/pytest_regressions.py,sha256=ocjHTtfOeiGfQAKIei8pKNd61sxN9dawrJJ9gPt2wzA,4097
@@ -91,8 +91,8 @@ utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
91
91
  utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
92
92
  utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
93
93
  utilities/pytest_plugins/pytest_regressions.py,sha256=9v8kAXDM2ycIXJBimoiF4EgrwbUvxTycFWJiGR_GHhM,1466
94
- dycw_utilities-0.166.16.dist-info/METADATA,sha256=mRIYx_yS0XbJdgF91-6wfLFf0a8T4bRtJC_v3cLoFO4,1702
95
- dycw_utilities-0.166.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
- dycw_utilities-0.166.16.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
97
- dycw_utilities-0.166.16.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
98
- dycw_utilities-0.166.16.dist-info/RECORD,,
94
+ dycw_utilities-0.166.18.dist-info/METADATA,sha256=NPBYGvJB7ozKQT3NMI0xHlTtFcCcchzIBb5YmxnoptU,1702
95
+ dycw_utilities-0.166.18.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
+ dycw_utilities-0.166.18.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
97
+ dycw_utilities-0.166.18.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
98
+ dycw_utilities-0.166.18.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.166.16"
3
+ __version__ = "0.166.18"
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, ClassVar, override
3
+ from functools import reduce
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, Any, ClassVar, assert_never, override
4
6
 
5
7
  from pydantic_settings import (
6
8
  BaseSettings,
@@ -10,22 +12,28 @@ from pydantic_settings import (
10
12
  TomlConfigSettingsSource,
11
13
  YamlConfigSettingsSource,
12
14
  )
15
+ from pydantic_settings.sources import DEFAULT_PATH
13
16
 
14
17
  from utilities.iterables import always_iterable
15
18
 
16
19
  if TYPE_CHECKING:
17
- from collections.abc import Iterator
20
+ from collections.abc import Iterator, Sequence
18
21
 
19
- from utilities.types import MaybeIterable, PathLike
22
+ from pydantic_settings.sources import PathType
23
+
24
+ from utilities.types import MaybeSequenceStr, PathLike
25
+
26
+
27
+ type PathLikeOrWithSection = PathLike | tuple[PathLike, MaybeSequenceStr]
20
28
 
21
29
 
22
30
  class CustomBaseSettings(BaseSettings):
23
31
  """Base settings for loading JSON files."""
24
32
 
25
33
  # paths
26
- json_files: ClassVar[MaybeIterable[PathLike]] = ()
27
- toml_files: ClassVar[MaybeIterable[PathLike]] = ()
28
- yaml_files: ClassVar[MaybeIterable[PathLike]] = ()
34
+ json_files: ClassVar[Sequence[PathLikeOrWithSection]] = []
35
+ toml_files: ClassVar[Sequence[PathLikeOrWithSection]] = []
36
+ yaml_files: ClassVar[Sequence[PathLikeOrWithSection]] = []
29
37
 
30
38
  # config
31
39
  model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(
@@ -53,12 +61,36 @@ class CustomBaseSettings(BaseSettings):
53
61
  /,
54
62
  ) -> Iterator[PydanticBaseSettingsSource]:
55
63
  yield env_settings
56
- for file in always_iterable(cls.json_files):
57
- yield JsonConfigSettingsSource(settings_cls, json_file=file)
58
- for file in always_iterable(cls.toml_files):
59
- yield TomlConfigSettingsSource(settings_cls, toml_file=file)
60
- for file in always_iterable(cls.yaml_files):
61
- yield YamlConfigSettingsSource(settings_cls, yaml_file=file)
64
+ for json in cls.json_files:
65
+ match json:
66
+ case Path() | str():
67
+ yield JsonConfigSettingsSource(settings_cls, json_file=json)
68
+ case Path() | str() as file, str() | list() | tuple() as section:
69
+ yield JsonConfigSectionSettingsSource(
70
+ settings_cls, json_file=file, section=section
71
+ )
72
+ case never:
73
+ assert_never(never)
74
+ for toml in cls.toml_files:
75
+ match toml:
76
+ case Path() | str():
77
+ yield TomlConfigSettingsSource(settings_cls, toml_file=toml)
78
+ case Path() | str() as file, str() | list() | tuple() as section:
79
+ yield TomlConfigSectionSettingsSource(
80
+ settings_cls, toml_file=file, section=section
81
+ )
82
+ case never:
83
+ assert_never(never)
84
+ for yaml in cls.yaml_files:
85
+ match yaml:
86
+ case Path() | str():
87
+ yield YamlConfigSettingsSource(settings_cls, yaml_file=yaml)
88
+ case Path() | str() as file, str() | list() | tuple() as section:
89
+ yield YamlConfigSectionSettingsSource(
90
+ settings_cls, yaml_file=file, section=section
91
+ )
92
+ case never:
93
+ assert_never(never)
62
94
 
63
95
 
64
96
  def load_settings[T: BaseSettings](cls: type[T], /) -> T:
@@ -66,4 +98,83 @@ def load_settings[T: BaseSettings](cls: type[T], /) -> T:
66
98
  return cls()
67
99
 
68
100
 
69
- __all__ = ["CustomBaseSettings", "load_settings"]
101
+ class JsonConfigSectionSettingsSource(JsonConfigSettingsSource):
102
+ @override
103
+ def __init__(
104
+ self,
105
+ settings_cls: type[BaseSettings],
106
+ json_file: PathType | None = DEFAULT_PATH,
107
+ json_file_encoding: str | None = None,
108
+ *,
109
+ section: MaybeSequenceStr,
110
+ ) -> None:
111
+ super().__init__(
112
+ settings_cls, json_file=json_file, json_file_encoding=json_file_encoding
113
+ )
114
+ self.section = section
115
+
116
+ @override
117
+ def __call__(self) -> dict[str, Any]:
118
+ return reduce(
119
+ lambda acc, el: acc.get(el, {}),
120
+ always_iterable(self.section),
121
+ super().__call__(),
122
+ )
123
+
124
+
125
+ class TomlConfigSectionSettingsSource(TomlConfigSettingsSource):
126
+ @override
127
+ def __init__(
128
+ self,
129
+ settings_cls: type[BaseSettings],
130
+ toml_file: PathType | None = DEFAULT_PATH,
131
+ *,
132
+ section: MaybeSequenceStr,
133
+ ) -> None:
134
+ super().__init__(settings_cls, toml_file=toml_file)
135
+ self.section = section
136
+
137
+ @override
138
+ def __call__(self) -> dict[str, Any]:
139
+ return reduce(
140
+ lambda acc, el: acc.get(el, {}),
141
+ always_iterable(self.section),
142
+ super().__call__(),
143
+ )
144
+
145
+
146
+ class YamlConfigSectionSettingsSource(YamlConfigSettingsSource):
147
+ @override
148
+ def __init__(
149
+ self,
150
+ settings_cls: type[BaseSettings],
151
+ yaml_file: PathType | None = DEFAULT_PATH,
152
+ yaml_file_encoding: str | None = None,
153
+ yaml_config_section: str | None = None,
154
+ *,
155
+ section: MaybeSequenceStr,
156
+ ) -> None:
157
+ super().__init__(
158
+ settings_cls,
159
+ yaml_file=yaml_file,
160
+ yaml_file_encoding=yaml_file_encoding,
161
+ yaml_config_section=yaml_config_section,
162
+ )
163
+ self.section = section
164
+
165
+ @override
166
+ def __call__(self) -> dict[str, Any]:
167
+ return reduce(
168
+ lambda acc, el: acc.get(el, {}),
169
+ always_iterable(self.section),
170
+ super().__call__(),
171
+ )
172
+
173
+
174
+ __all__ = [
175
+ "CustomBaseSettings",
176
+ "JsonConfigSectionSettingsSource",
177
+ "TomlConfigSectionSettingsSource",
178
+ "YamlConfigSectionSettingsSource",
179
+ "load_settings",
180
+ ]
@@ -4,22 +4,21 @@ from typing import TYPE_CHECKING, ClassVar, override
4
4
 
5
5
  from pydantic_settings_sops import SOPSConfigSettingsSource
6
6
 
7
- from utilities.iterables import always_iterable
8
7
  from utilities.pydantic_settings import CustomBaseSettings
9
8
 
10
9
  if TYPE_CHECKING:
11
- from collections.abc import Iterator
10
+ from collections.abc import Iterator, Sequence
12
11
 
13
12
  from pydantic_settings import BaseSettings, PydanticBaseSettingsSource
14
13
 
15
- from utilities.types import MaybeIterable, PathLike
14
+ from utilities.types import PathLike
16
15
 
17
16
 
18
17
  class SopsBaseSettings(CustomBaseSettings):
19
18
  """Base settings for loading secrets using `sops/age`."""
20
19
 
21
20
  # paths
22
- secret_files: ClassVar[MaybeIterable[PathLike]] = ()
21
+ secret_files: ClassVar[Sequence[PathLike]] = ()
23
22
 
24
23
  @classmethod
25
24
  @override
@@ -30,7 +29,7 @@ class SopsBaseSettings(CustomBaseSettings):
30
29
  /,
31
30
  ) -> Iterator[PydanticBaseSettingsSource]:
32
31
  yield from super()._yield_base_settings_sources(settings_cls, env_settings)
33
- for file in always_iterable(cls.secret_files):
32
+ for file in cls.secret_files:
34
33
  yield SOPSConfigSettingsSource(
35
34
  settings_cls, # pyright: ignore[reportArgumentType],
36
35
  json_file=file,