pydantic-settings-manager 0.1.0__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.
@@ -0,0 +1,37 @@
1
+ """
2
+ pydantic-settings-manager
3
+ ========================
4
+
5
+ A library for managing Pydantic settings objects.
6
+
7
+ This library provides two types of settings managers:
8
+
9
+ 1. SingleSettingsManager: For managing a single settings object
10
+ 2. MappedSettingsManager: For managing multiple settings objects mapped to keys
11
+
12
+ Both managers support:
13
+ - Loading settings from multiple sources
14
+ - Command line argument overrides
15
+ - Settings validation through Pydantic
16
+ """
17
+
18
+ from pydantic_settings import BaseSettings, SettingsConfigDict
19
+
20
+ from .base import BaseSettingsManager
21
+ from .mapped import MappedSettingsManager, SettingsMap
22
+ from .single import SingleSettingsManager
23
+
24
+ __version__ = "0.1.0"
25
+
26
+ __all__ = [
27
+ # Re-exports from pydantic_settings
28
+ "BaseSettings",
29
+ "SettingsConfigDict",
30
+ # Base manager
31
+ "BaseSettingsManager",
32
+ # Single settings manager
33
+ "SingleSettingsManager",
34
+ # Mapped settings manager
35
+ "MappedSettingsManager",
36
+ "SettingsMap",
37
+ ]
@@ -0,0 +1,52 @@
1
+ """
2
+ Base classes for settings managers.
3
+ """
4
+ from abc import ABC, abstractmethod
5
+ from typing import Any, Generic, Type, TypeVar
6
+
7
+ from pydantic_settings import BaseSettings
8
+
9
+ T = TypeVar("T", bound=BaseSettings)
10
+
11
+
12
+ class BaseSettingsManager(ABC, Generic[T]):
13
+ """
14
+ Base class for settings managers.
15
+
16
+ This abstract class defines the interface that all settings managers must implement.
17
+ It provides a generic way to manage Pydantic settings objects.
18
+
19
+ Type Parameters:
20
+ T: A type that inherits from BaseSettings
21
+ """
22
+
23
+ @abstractmethod
24
+ def __init__(self, settings_cls: Type[T]):
25
+ """
26
+ Initialize the settings manager.
27
+
28
+ Args:
29
+ settings_cls: The settings class to manage
30
+ """
31
+ self.settings_cls = settings_cls
32
+ """The settings class being managed"""
33
+
34
+ self.user_config: dict[str, Any] = {}
35
+ """User configuration dictionary"""
36
+
37
+ @property
38
+ @abstractmethod
39
+ def settings(self) -> T:
40
+ """
41
+ Get the current settings.
42
+
43
+ Returns:
44
+ The current settings object
45
+ """
46
+
47
+ @abstractmethod
48
+ def clear(self):
49
+ """
50
+ Clear the current settings.
51
+ This typically involves clearing any cached settings.
52
+ """
@@ -0,0 +1,282 @@
1
+ """
2
+ Mapped settings manager implementation.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ from typing import Any, Dict, Generic, Type
7
+
8
+ from pydantic import BaseModel, Field
9
+ from pydantic.main import create_model
10
+
11
+ from .base import BaseSettingsManager, T
12
+ from .utils import diff_dict, update_dict
13
+
14
+
15
+ class SettingsMap(BaseModel, Generic[T]):
16
+ """
17
+ A model that maps keys to settings objects.
18
+
19
+ This model is used by MappedSettingsManager to store multiple settings objects
20
+ and track which one is currently active.
21
+
22
+ Type Parameters:
23
+ T: A type that inherits from BaseSettings
24
+
25
+ Attributes:
26
+ key: The key of the currently active settings
27
+ map: A dictionary mapping keys to settings objects
28
+ """
29
+
30
+ key: str = ""
31
+ """The key of the currently active settings"""
32
+
33
+ map: Dict[str, T] = Field(default_factory=dict)
34
+ """A dictionary mapping keys to settings objects"""
35
+
36
+
37
+ class MappedSettingsManager(BaseSettingsManager[T]):
38
+ """
39
+ A settings manager that manages multiple settings objects mapped to keys.
40
+
41
+ This manager is useful when you need to switch between different configurations
42
+ at runtime. Each configuration is identified by a unique key.
43
+
44
+ Type Parameters:
45
+ T: A type that inherits from BaseSettings
46
+
47
+ Example:
48
+ ```python
49
+ from pydantic_settings import BaseSettings
50
+ from pydantic_settings_manager import MappedSettingsManager
51
+
52
+ class MySettings(BaseSettings):
53
+ name: str = "default"
54
+ value: int = 0
55
+
56
+ # Create a settings manager
57
+ manager = MappedSettingsManager(MySettings)
58
+
59
+ # Set up multiple configurations
60
+ manager.user_config = {
61
+ "map": {
62
+ "dev": {"name": "development", "value": 42},
63
+ "prod": {"name": "production", "value": 100}
64
+ }
65
+ }
66
+
67
+ # Select which configuration to use
68
+ manager.set_cli_args("dev")
69
+
70
+ # Get the current settings
71
+ settings = manager.settings
72
+ assert settings.name == "development"
73
+ assert settings.value == 42
74
+
75
+ # Switch to a different configuration
76
+ manager.set_cli_args("prod")
77
+ settings = manager.settings
78
+ assert settings.name == "production"
79
+ assert settings.value == 100
80
+ ```
81
+ """
82
+
83
+ def __init__(self, settings_cls: Type[T]):
84
+ """
85
+ Initialize the settings manager.
86
+
87
+ Args:
88
+ settings_cls: The settings class to manage
89
+ """
90
+ self.settings_cls: Type[T] = settings_cls
91
+ """The settings class being managed"""
92
+
93
+ self.cli_args: SettingsMap[T] = SettingsMap[T]()
94
+ """Command line arguments"""
95
+
96
+ self.user_config: Dict[str, Any] = {}
97
+ """User configuration"""
98
+
99
+ self.system_settings: T = settings_cls()
100
+ """System settings"""
101
+
102
+ self._map_settings: SettingsMap[T] | None = None
103
+ """Cached settings map"""
104
+
105
+ @property
106
+ def settings_cls_name(self) -> str:
107
+ """
108
+ Get the name of the settings class.
109
+
110
+ Returns:
111
+ The name of the settings class
112
+ """
113
+ return self.settings_cls.__name__
114
+
115
+ def set_cli_args(self, key: str, value: T | None = None):
116
+ """
117
+ Set the command line arguments.
118
+
119
+ Args:
120
+ key: The key to make active
121
+ value: Optional settings to associate with the key
122
+ """
123
+ self.cli_args.key = key
124
+
125
+ if value:
126
+ self.cli_args.map[key] = value
127
+
128
+ self.clear()
129
+
130
+ @property
131
+ def _cli_args_config(self) -> Dict[str, Any]:
132
+ """
133
+ Get the command line arguments as a dictionary.
134
+
135
+ Returns:
136
+ The command line arguments
137
+ """
138
+ vanilla = SettingsMap[T]()
139
+
140
+ for k, _ in self.cli_args.map.items():
141
+ vanilla.map[k] = self.settings_cls()
142
+
143
+ base = vanilla.model_dump()
144
+ target = self.cli_args.model_dump()
145
+
146
+ return diff_dict(base, target)
147
+
148
+ @property
149
+ def _system_settings_config(self) -> Dict[str, Any]:
150
+ """
151
+ Get the system settings as a dictionary.
152
+
153
+ Returns:
154
+ The system settings
155
+ """
156
+ base = self.settings_cls().model_dump()
157
+ target = self.system_settings.model_dump()
158
+ return diff_dict(base, target)
159
+
160
+ @property
161
+ def map_settings(self) -> SettingsMap[T]:
162
+ """
163
+ Get the settings map.
164
+
165
+ Returns:
166
+ The settings map
167
+ """
168
+ if not self._map_settings:
169
+ self._map_settings = self._get_map_settings()
170
+
171
+ return self._map_settings
172
+
173
+ def _get_map_settings(self) -> SettingsMap[T]:
174
+ """
175
+ Create a new settings map.
176
+
177
+ Returns:
178
+ A new settings map
179
+ """
180
+ DynamicMapSettings = create_model(
181
+ "DynamicMapSettings",
182
+ key=(str, ""),
183
+ map=(Dict[str, self.settings_cls], {}), # type: ignore[name-defined]
184
+ __base__=SettingsMap[T],
185
+ )
186
+
187
+ # Merge user configuration and command line arguments
188
+ config = update_dict(self.user_config, self._cli_args_config)
189
+
190
+ # Merge system settings into each configuration
191
+ if "map" in config:
192
+ for key, value in config["map"].items():
193
+ config["map"][key] = update_dict(value, self._system_settings_config)
194
+
195
+ return DynamicMapSettings(**config)
196
+
197
+ def clear(self):
198
+ """
199
+ Clear the cached settings map.
200
+ """
201
+ self._map_settings = None
202
+
203
+ @property
204
+ def settings(self) -> T:
205
+ """
206
+ Get the current settings.
207
+
208
+ Returns:
209
+ The current settings object
210
+
211
+ Raises:
212
+ ValueError: If the active key does not exist in the settings map
213
+ """
214
+ if not self.map_settings.key:
215
+ if not self.map_settings.map:
216
+ settings = self.settings_cls(**self.system_settings.model_dump())
217
+ else:
218
+ key = list(self.map_settings.map.keys())[0]
219
+ settings = self.map_settings.map[key]
220
+ else:
221
+ if self.map_settings.key not in self.map_settings.map:
222
+ raise ValueError(
223
+ f"Key does not exist in settings map: "
224
+ f"{self.settings_cls_name}, {self.map_settings.key}"
225
+ )
226
+ else:
227
+ settings = self.map_settings.map[self.map_settings.key]
228
+
229
+ return settings
230
+
231
+ def has_key(self, key: str) -> bool:
232
+ """
233
+ Check if a key exists in the settings map.
234
+
235
+ Args:
236
+ key: The key to check
237
+
238
+ Returns:
239
+ True if the key exists, False otherwise
240
+ """
241
+ return key in self.map_settings.map
242
+
243
+ def get_settings_by_key(self, key: str = "") -> T:
244
+ """
245
+ Get settings by key.
246
+
247
+ Args:
248
+ key: The key to get settings for. If empty, returns the active settings.
249
+
250
+ Returns:
251
+ The settings object for the specified key
252
+
253
+ Raises:
254
+ ValueError: If the specified key does not exist in the settings map
255
+ """
256
+ if not key:
257
+ return self.settings
258
+
259
+ if not self.has_key(key):
260
+ raise ValueError(f"Key does not exist in settings map: {key}")
261
+
262
+ return self.map_settings.map[key]
263
+
264
+ @property
265
+ def active_key(self) -> str:
266
+ """
267
+ Get the active key.
268
+
269
+ Returns:
270
+ The active key
271
+ """
272
+ return self.map_settings.key
273
+
274
+ @property
275
+ def all_settings(self) -> Dict[str, T]:
276
+ """
277
+ Get all settings.
278
+
279
+ Returns:
280
+ A dictionary mapping keys to settings objects
281
+ """
282
+ return self.map_settings.map
@@ -0,0 +1,96 @@
1
+ """
2
+ Single settings manager implementation.
3
+ """
4
+ from typing import Any, Dict, Type
5
+
6
+ from .base import BaseSettingsManager, T
7
+ from .utils import NestedDict, nested_dict, update_dict
8
+
9
+
10
+ class SingleSettingsManager(BaseSettingsManager[T]):
11
+ """
12
+ A settings manager that manages a single settings object.
13
+
14
+ This manager is useful when you need to manage a single configuration that can be
15
+ updated from multiple sources (e.g., command line arguments, configuration files).
16
+
17
+ Type Parameters:
18
+ T: A type that inherits from BaseSettings
19
+
20
+ Example:
21
+ ```python
22
+ from pydantic_settings import BaseSettings
23
+ from pydantic_settings_manager import SingleSettingsManager
24
+
25
+ class MySettings(BaseSettings):
26
+ name: str = "default"
27
+ value: int = 0
28
+
29
+ # Create a settings manager
30
+ manager = SingleSettingsManager(MySettings)
31
+
32
+ # Update settings from a configuration file
33
+ manager.user_config = {"name": "from_file", "value": 42}
34
+
35
+ # Update settings from command line arguments
36
+ manager.cli_args = {"value": 100}
37
+
38
+ # Get the current settings (combines both sources)
39
+ settings = manager.settings
40
+ assert settings.name == "from_file" # from user_config
41
+ assert settings.value == 100 # from cli_args (overrides user_config)
42
+ ```
43
+ """
44
+
45
+ def __init__(self, settings_cls: Type[T]):
46
+ """
47
+ Initialize the settings manager.
48
+
49
+ Args:
50
+ settings_cls: The settings class to manage
51
+ """
52
+ self.settings_cls: Type[T] = settings_cls
53
+ """The settings class being managed"""
54
+
55
+ self.cli_args: NestedDict = nested_dict()
56
+ """Command line arguments"""
57
+
58
+ self.user_config: Dict[str, Any] = {}
59
+ """User configuration"""
60
+
61
+ self._settings: T | None = None
62
+ """Cached settings"""
63
+
64
+ @property
65
+ def settings(self) -> T:
66
+ """
67
+ Get the current settings.
68
+
69
+ The settings are created by combining the user configuration and command line
70
+ arguments, with command line arguments taking precedence.
71
+
72
+ Returns:
73
+ The current settings object
74
+ """
75
+ if not self._settings:
76
+ self._settings = self._get_settings()
77
+
78
+ return self._settings
79
+
80
+ def _get_settings(self) -> T:
81
+ """
82
+ Create a new settings object.
83
+
84
+ Returns:
85
+ A new settings object
86
+ """
87
+ config = update_dict(self.user_config, self.cli_args)
88
+ return self.settings_cls(**config)
89
+
90
+ def clear(self):
91
+ """
92
+ Clear the cached settings.
93
+
94
+ This forces the next access to settings to create a new settings object.
95
+ """
96
+ self._settings = None
@@ -0,0 +1,6 @@
1
+ from typing import Any, DefaultDict, Union
2
+
3
+ NestedDict = DefaultDict[str, Union["NestedDict", Any]]
4
+ """
5
+ ネストした辞書型
6
+ """
@@ -0,0 +1,50 @@
1
+ """
2
+ Utility functions for dictionary operations.
3
+ """
4
+ from collections import defaultdict
5
+ from typing import Any, Dict
6
+
7
+ from .types import NestedDict
8
+
9
+
10
+ def diff_dict(base: Dict[str, Any], target: Dict[str, Any]) -> Dict[str, Any]:
11
+ """
12
+ Get the difference between two dictionaries.
13
+ Only includes keys where the values are different.
14
+ """
15
+ result = {}
16
+
17
+ for key in target:
18
+ if key not in base:
19
+ result[key] = target[key]
20
+ elif isinstance(target[key], dict) and isinstance(base[key], dict):
21
+ nested = diff_dict(base[key], target[key])
22
+ if nested:
23
+ result[key] = nested
24
+ elif target[key] != base[key]:
25
+ result[key] = target[key]
26
+
27
+ return result
28
+
29
+
30
+ def update_dict(base: Dict[str, Any], update: Dict[str, Any]) -> Dict[str, Any]:
31
+ """
32
+ Update a dictionary with another dictionary.
33
+ Performs a deep update.
34
+ """
35
+ result = base.copy()
36
+
37
+ for key, value in update.items():
38
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
39
+ result[key] = update_dict(result[key], value)
40
+ else:
41
+ result[key] = value
42
+
43
+ return result
44
+
45
+
46
+ def nested_dict() -> NestedDict:
47
+ """
48
+ ネストした dict を作成する
49
+ """
50
+ return defaultdict(nested_dict)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Aki
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.1
2
+ Name: pydantic-settings-manager
3
+ Version: 0.1.0
4
+ Summary: A library for managing Pydantic settings objects
5
+ Home-page: https://github.com/kiarina/pydantic-settings-manager
6
+ License: MIT
7
+ Keywords: pydantic,settings,configuration
8
+ Author: kiarina
9
+ Author-email: kiarinadawa@gmail.com
10
+ Requires-Python: >=3.8,<4.0
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Dist: pydantic (>=2.0.0,<3.0.0)
23
+ Requires-Dist: pydantic-settings (>=2.0.0,<3.0.0)
24
+ Project-URL: Documentation, https://github.com/kiarina/pydantic-settings-manager
25
+ Project-URL: Repository, https://github.com/kiarina/pydantic-settings-manager
26
+ Description-Content-Type: text/markdown
27
+
28
+ # pydantic-settings-manager
29
+
30
+ A library for managing Pydantic settings objects.
31
+
32
+ ## Features
33
+
34
+ - Two types of settings managers:
35
+ - `SingleSettingsManager`: For managing a single settings object
36
+ - `MappedSettingsManager`: For managing multiple settings objects mapped to keys
37
+ - Support for loading settings from multiple sources
38
+ - Command line argument overrides
39
+ - Settings validation through Pydantic
40
+ - Type hints and documentation
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install pydantic-settings-manager
46
+ ```
47
+
48
+ ## Quick Start
49
+
50
+ ### Single Settings Manager
51
+
52
+ ```python
53
+ from pydantic_settings import BaseSettings
54
+ from pydantic_settings_manager import SingleSettingsManager
55
+
56
+ class MySettings(BaseSettings):
57
+ name: str = "default"
58
+ value: int = 0
59
+
60
+ # Create a settings manager
61
+ manager = SingleSettingsManager(MySettings)
62
+
63
+ # Update settings from a configuration file
64
+ manager.user_config = {"name": "from_file", "value": 42}
65
+
66
+ # Update settings from command line arguments
67
+ manager.cli_args = {"value": 100}
68
+
69
+ # Get the current settings (combines both sources)
70
+ settings = manager.settings
71
+ assert settings.name == "from_file" # from user_config
72
+ assert settings.value == 100 # from cli_args (overrides user_config)
73
+ ```
74
+
75
+ ### Mapped Settings Manager
76
+
77
+ ```python
78
+ from pydantic_settings import BaseSettings
79
+ from pydantic_settings_manager import MappedSettingsManager
80
+
81
+ class MySettings(BaseSettings):
82
+ name: str = "default"
83
+ value: int = 0
84
+
85
+ # Create a settings manager
86
+ manager = MappedSettingsManager(MySettings)
87
+
88
+ # Set up multiple configurations
89
+ manager.user_config = {
90
+ "map": {
91
+ "dev": {"name": "development", "value": 42},
92
+ "prod": {"name": "production", "value": 100}
93
+ }
94
+ }
95
+
96
+ # Select which configuration to use
97
+ manager.set_cli_args("dev")
98
+
99
+ # Get the current settings
100
+ settings = manager.settings
101
+ assert settings.name == "development"
102
+ assert settings.value == 42
103
+
104
+ # Switch to a different configuration
105
+ manager.set_cli_args("prod")
106
+ settings = manager.settings
107
+ assert settings.name == "production"
108
+ assert settings.value == 100
109
+ ```
110
+
111
+ ## Documentation
112
+
113
+ For more detailed documentation, please see the [GitHub repository](https://github.com/kiarina/pydantic-settings-manager).
114
+
115
+ ## License
116
+
117
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
118
+
@@ -0,0 +1,10 @@
1
+ pydantic_settings_manager/__init__.py,sha256=6EYixuoo-sI93B6zv4z-WcgQ8CpSboSGKOQgozbBcTo,955
2
+ pydantic_settings_manager/base.py,sha256=_FS-G_-bFf5oHuQFXVRdmHHlq17KdsPnax7QzcnIRmU,1264
3
+ pydantic_settings_manager/mapped.py,sha256=mJJxqCUrFe6vPdZDnsfDNwwyC2dn3b4i2yR6G57JNZg,7715
4
+ pydantic_settings_manager/single.py,sha256=OkoGnNXuOAbOsmyhcJ70S6FrMMc7DC2CORHzKU1J_Hk,2758
5
+ pydantic_settings_manager/types.py,sha256=XvZoUmmNDjzAr5ns77uq1YhZPocT2BFkLMPZVZt1Lt8,133
6
+ pydantic_settings_manager/utils.py,sha256=ciBAczruqEiaudUvk38pCzrjAUz8liIl9JO6QS8hUg0,1322
7
+ pydantic_settings_manager-0.1.0.dist-info/LICENSE,sha256=GtwfENGLMnpE6iSmuiVTC1AhFCLGvBYycR--RKNSs80,1060
8
+ pydantic_settings_manager-0.1.0.dist-info/METADATA,sha256=boPYoapJPuHB34xQPf6Jdn-02uJ4EeOKC3vN4HkdGxA,3463
9
+ pydantic_settings_manager-0.1.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
10
+ pydantic_settings_manager-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.9.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any