# pydantic-settings-manager
A modern, thread-safe library for managing Pydantic settings with support for multiple configurations and runtime overrides.
## Features
- **Unified API**: Single `SettingsManager` class handles both simple and complex configurations
- **Thread-safe**: Built-in thread safety for concurrent applications
- **Type-safe**: Full type hints and Pydantic validation
- **Flexible**: Support for single settings or multiple named configurations
- **Runtime overrides**: Command-line arguments and dynamic configuration changes
- **Easy migration**: Simple upgrade path from configuration files and environment variables
## Installation
```bash
pip install pydantic-settings-manager
```
## Quick Start
### Basic Usage
```python
from pydantic_settings import BaseSettings
from pydantic_settings_manager import SettingsManager
# 1. Define your settings
class AppSettings(BaseSettings):
app_name: str = "MyApp"
debug: bool = False
max_connections: int = 100
# 2. Create a settings manager
manager = SettingsManager(AppSettings)
# 3. Use your settings
settings = manager.settings
print(f"App: {settings.app_name}, Debug: {settings.debug}")
# Output: App: MyApp, Debug: False
```
### Loading from Configuration
```python
# Load from a dictionary (JSON, YAML, etc.)
manager.user_config = {
"app_name": "ProductionApp",
"debug": False,
"max_connections": 500
}
settings = manager.settings
print(f"App: {settings.app_name}") # Output: App: ProductionApp
```
### Runtime Overrides
```python
# Override settings at runtime (e.g., from command line)
manager.cli_args = {"debug": True, "max_connections": 50}
settings = manager.settings
print(f"Debug: {settings.debug}") # Output: Debug: True
print(f"Connections: {settings.max_connections}") # Output: Connections: 50
```
## Multiple Configurations
For applications that need different settings for different environments or contexts:
```python
# Enable multi-configuration mode
manager = SettingsManager(AppSettings, multi=True)
# Configure multiple environments
manager.user_config = {
"development": {
"app_name": "MyApp-Dev",
"debug": True,
"max_connections": 10
},
"production": {
"app_name": "MyApp-Prod",
"debug": False,
"max_connections": 1000
},
"testing": {
"app_name": "MyApp-Test",
"debug": True,
"max_connections": 5
}
}
# Switch between configurations
manager.active_key = "development"
dev_settings = manager.settings
print(f"Dev: {dev_settings.app_name}, Debug: {dev_settings.debug}")
manager.active_key = "production"
prod_settings = manager.settings
print(f"Prod: {prod_settings.app_name}, Debug: {prod_settings.debug}")
# Get all configurations
all_settings = manager.all_settings
for env, settings in all_settings.items():
print(f"{env}: {settings.app_name}")
```
## Advanced Usage
### Thread Safety
The `SettingsManager` is fully thread-safe and can be used in multi-threaded applications:
```python
import threading
from concurrent.futures import ThreadPoolExecutor
manager = SettingsManager(AppSettings, multi=True)
manager.user_config = {
"worker1": {"app_name": "Worker1", "max_connections": 10},
"worker2": {"app_name": "Worker2", "max_connections": 20}
}
def worker_function(worker_id: int):
# Each thread can safely switch configurations
manager.active_key = f"worker{worker_id}"
settings = manager.settings
print(f"Worker {worker_id}: {settings.app_name}")
# Run multiple workers concurrently
with ThreadPoolExecutor(max_workers=5) as executor:
futures = [executor.submit(worker_function, i) for i in range(1, 3)]
for future in futures:
future.result()
```
### Dynamic Configuration Updates
```python
# Update individual CLI arguments
manager.set_cli_args("debug", True)
manager.set_cli_args("nested.value", "test") # Supports nested keys
# Update entire CLI args
manager.cli_args = {"debug": False, "max_connections": 200}
# Get specific settings by key (multi mode)
dev_settings = manager.get_settings_by_key("development")
prod_settings = manager.get_settings_by_key("production")
```
### Complex Settings with Nested Configuration
```python
from typing import Dict, List
from pydantic import Field
class DatabaseSettings(BaseSettings):
host: str = "localhost"
port: int = 5432
username: str = "user"
password: str = "password"
class APISettings(BaseSettings):
base_url: str = "https://api.example.com"
timeout: int = 30
retries: int = 3
class AppSettings(BaseSettings):
app_name: str = "MyApp"
debug: bool = False
database: DatabaseSettings = Field(default_factory=DatabaseSettings)
api: APISettings = Field(default_factory=APISettings)
features: List[str] = Field(default_factory=list)
metadata: Dict[str, str] = Field(default_factory=dict)
manager = SettingsManager(AppSettings, multi=True)
manager.user_config = {
"development": {
"app_name": "MyApp-Dev",
"debug": True,
"database": {
"host": "dev-db.example.com",
"port": 5433
},
"api": {
"base_url": "https://dev-api.example.com",
"timeout": 10
},
"features": ["debug_toolbar", "hot_reload"],
"metadata": {"environment": "dev", "version": "1.0.0-dev"}
},
"production": {
"app_name": "MyApp-Prod",
"debug": False,
"database": {
"host": "prod-db.example.com",
"port": 5432,
"username": "prod_user"
},
"api": {
"base_url": "https://api.example.com",
"timeout": 30,
"retries": 5
},
"features": ["monitoring", "caching"],
"metadata": {"environment": "prod", "version": "1.0.0"}
}
}
# Use nested configuration
manager.active_key = "development"
settings = manager.settings
print(f"DB Host: {settings.database.host}")
print(f"API URL: {settings.api.base_url}")
print(f"Features: {settings.features}")
```
## Project Structure for Large Applications
For complex applications with multiple modules:
```
your_project/
├── settings/
│ ├── __init__.py
│ ├── app.py
│ ├── database.py
│ └── api.py
├── modules/
│ ├── auth/
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ └── service.py
│ └── billing/
│ ├── __init__.py
│ ├── settings.py
│ └── service.py
├── config/
│ ├── base.yaml
│ ├── development.yaml
│ └── production.yaml
├── bootstrap.py
└── main.py
```
### Module-based Settings
```python
# settings/app.py
from pydantic_settings import BaseSettings
from pydantic_settings_manager import SettingsManager
class AppSettings(BaseSettings):
name: str = "MyApp"
debug: bool = False
secret_key: str = "dev-secret"
app_settings_manager = SettingsManager(AppSettings, multi=True)
# modules/auth/settings.py
from pydantic_settings import BaseSettings
from pydantic_settings_manager import SettingsManager
class AuthSettings(BaseSettings):
jwt_secret: str = "jwt-secret"
token_expiry: int = 3600
max_login_attempts: int = 5
auth_settings_manager = SettingsManager(AuthSettings, multi=True)
```
### Bootstrap Configuration
```python
# bootstrap.py
import yaml
import importlib
from pathlib import Path
from pydantic_settings_manager import SettingsManager
def load_config(config_path: str) -> dict:
"""Load configuration from YAML file"""
with open(config_path) as f:
return yaml.safe_load(f)
def bootstrap_settings(environment: str = "development"):
"""Bootstrap all settings managers with configuration"""
# Load base configuration
base_config = load_config("config/base.yaml")
env_config = load_config(f"config/{environment}.yaml")
# Merge configurations (env overrides base)
config = {**base_config, **env_config}
# Configure each module's settings manager
settings_managers = [
("settings.app", "app_settings_manager"),
("modules.auth.settings", "auth_settings_manager"),
("modules.billing.settings", "billing_settings_manager"),
]
for module_name, manager_name in settings_managers:
try:
module = importlib.import_module(module_name)
manager = getattr(module, manager_name, None)
if isinstance(manager, SettingsManager):
# Set configuration for this module
if module_name.split('.')[-1] in config:
manager.user_config = config[module_name.split('.')[-1]]
manager.active_key = environment
except ImportError:
print(f"Warning: Could not import {module_name}")
# main.py
from bootstrap import bootstrap_settings
from settings.app import app_settings_manager
from modules.auth.settings import auth_settings_manager
def main():
# Bootstrap all settings
bootstrap_settings("production")
# Use settings throughout the application
app_settings = app_settings_manager.settings
auth_settings = auth_settings_manager.settings
print(f"App: {app_settings.name}")
print(f"JWT Expiry: {auth_settings.token_expiry}")
if __name__ == "__main__":
main()
```
### Configuration Files
```yaml
# config/base.yaml
app:
name: "MyApp"
debug: false
auth:
token_expiry: 3600
max_login_attempts: 5
billing:
currency: "USD"
tax_rate: 0.08
```
```yaml
# config/development.yaml
app:
debug: true
secret_key: "dev-secret-key"
auth:
jwt_secret: "dev-jwt-secret"
token_expiry: 7200 # Longer expiry for development
billing:
mock_payments: true
```
```yaml
# config/production.yaml
app:
secret_key: "${SECRET_KEY}" # From environment variable
auth:
jwt_secret: "${JWT_SECRET}"
max_login_attempts: 3 # Stricter in production
billing:
mock_payments: false
stripe_api_key: "${STRIPE_API_KEY}"
```
## CLI Integration
Integrate with command-line tools for runtime configuration:
```python
# cli.py
import click
from bootstrap import bootstrap_settings
from settings.app import app_settings_manager
@click.command()
@click.option("--environment", "-e", default="development",
help="Environment to run in")
@click.option("--debug/--no-debug", default=None,
help="Override debug setting")
@click.option("--max-connections", type=int,
help="Override max connections")
def main(environment: str, debug: bool, max_connections: int):
"""Run the application with specified settings"""
# Bootstrap with environment
bootstrap_settings(environment)
# Apply CLI overrides
cli_overrides = {}
if debug is not None:
cli_overrides["debug"] = debug
if max_connections is not None:
cli_overrides["max_connections"] = max_connections
if cli_overrides:
app_settings_manager.cli_args = cli_overrides
# Run application
settings = app_settings_manager.settings
print(f"Running {settings.name} in {environment} mode")
print(f"Debug: {settings.debug}")
if __name__ == "__main__":
main()
```
Usage:
```bash
# Run with defaults
python cli.py
# Run in production with debug enabled
python cli.py --environment production --debug
# Override specific settings
python cli.py --max-connections 500
```
## Migration Guide
### Migrating from v0.x to v1.x
Starting from v1.0.0, the library provides a unified `SettingsManager` class that replaces the previous three separate classes. The old classes are deprecated and will be removed in v2.0.0.
#### From SingleSettingsManager
```python
# OLD (deprecated)
from pydantic_settings_manager import SingleSettingsManager
manager = SingleSettingsManager(MySettings)
manager.user_config = {"name": "app", "value": 42}
manager.cli_args["value"] = 100
settings = manager.settings
# NEW (recommended)
from pydantic_settings_manager import SettingsManager
manager = SettingsManager(MySettings) # multi=False is default
manager.user_config = {"name": "app", "value": 42}
manager.cli_args = {"value": 100} # Note: now a dict assignment, not dict access
settings = manager.settings
```
#### From MappedSettingsManager
```python
# OLD (deprecated)
from pydantic_settings_manager import MappedSettingsManager
manager = MappedSettingsManager(MySettings)
manager.user_config = {
"map": {
"dev": {"name": "development", "value": 42},
"prod": {"name": "production", "value": 100}
}
}
manager.set_cli_args("dev")
settings = manager.settings
# NEW (recommended)
from pydantic_settings_manager import SettingsManager
manager = SettingsManager(MySettings, multi=True)
manager.user_config = {
"dev": {"name": "development", "value": 42},
"prod": {"name": "production", "value": 100}
}
manager.active_key = "dev" # More intuitive property-based API
settings = manager.settings
```
#### From BaseSettingsManager
```python
# OLD (deprecated)
from pydantic_settings_manager import BaseSettingsManager
class MyManager(BaseSettingsManager[MySettings]):
def __init__(self, settings_cls):
super().__init__(settings_cls)
# ... custom implementation
@property
def settings(self):
# ... custom logic
pass
def clear(self):
# ... custom logic
pass
# NEW (recommended)
from pydantic_settings_manager import SettingsManager
# Use the unified manager directly
manager = SettingsManager(MySettings) # or SettingsManager(MySettings, multi=True)
# If you need custom behavior, consider composition over inheritance:
class MyCustomManager:
def __init__(self, settings_cls):
self._manager = SettingsManager(settings_cls)
# ... additional setup
@property
def settings(self):
# ... custom logic using self._manager
return self._manager.settings
```
### Key Differences
1. **Unified API**: One class handles both single and multiple configurations
2. **Property-based**: `manager.active_key = "prod"` instead of `manager.set_cli_args("prod")`
3. **Dict assignment**: `manager.cli_args = {...}` instead of `manager.cli_args[key] = value`
4. **Simplified config**: No need for `"map"` wrapper in multi mode
5. **Thread-safe**: Built-in thread safety for concurrent access
### Deprecation Timeline
- **v1.0.0**: Old classes deprecated with warnings
- **v2.0.0**: Old classes will be removed
### Suppressing Deprecation Warnings
If you need to suppress deprecation warnings temporarily:
```python
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
# Your code using deprecated classes
manager = SingleSettingsManager(MySettings)
```
## Related Tools
### pydantic-config-builder
For complex projects with multiple configuration files, you might want to use [`pydantic-config-builder`](https://github.com/kiarina/pydantic-config-builder) to merge and build your YAML configuration files:
```bash
pip install pydantic-config-builder
```
This tool allows you to:
- Merge multiple YAML files into a single configuration
- Use base configurations with overlay files
- Build different configurations for different environments
- Support glob patterns and recursive merging
Example workflow:
```yaml
# pydantic_config_builder.yml
development:
input:
- base/*.yaml
- dev-overrides.yaml
output:
- config/dev.yaml
production:
input:
- base/*.yaml
- prod-overrides.yaml
output:
- config/prod.yaml
```
Then use the generated configurations with your settings manager:
```python
import yaml
from your_app import settings_manager
# Load the built configuration
with open("config/dev.yaml") as f:
config = yaml.safe_load(f)
settings_manager.user_config = config
```
## Development
This project uses modern Python development tools:
- **uv**: Fast Python package manager with PEP 735 dependency groups support
- **ruff**: Fast linter and formatter (replaces black, isort, and flake8)
- **mypy**: Static type checking
- **pytest**: Testing framework with coverage reporting
### Setup
```bash
# Install all development dependencies
uv sync --group dev
# Or install specific dependency groups
uv sync --group test # Testing tools only
uv sync --group lint # Linting tools only
# Format code
uv run ruff check --fix .
# Run linting
uv run ruff check .
uv run mypy .
# Run tests
uv run pytest --cov=pydantic_settings_manager tests/
# Build and test everything
make build
```
### Development Workflow
```bash
# Quick setup for testing
uv sync --group test
make test
# Quick setup for linting
uv sync --group lint
make lint
# Full development environment
uv sync --group dev
make build
```
## API Reference
### SettingsManager
The main class for managing Pydantic settings.
```python
class SettingsManager(Generic[T]):
def __init__(self, settings_cls: type[T], *, multi: bool = False)
```
#### Parameters
- `settings_cls`: The Pydantic settings class to manage
- `multi`: Whether to enable multi-configuration mode (default: False)
#### Properties
- `settings: T` - Get the current active settings
- `all_settings: dict[str, T]` - Get all settings (multi mode)
- `user_config: dict[str, Any]` - Get/set user configuration
- `cli_args: dict[str, Any]` - Get/set CLI arguments
- `active_key: str | None` - Get/set active key (multi mode only)
#### Methods
- `clear() -> None` - Clear cached settings
- `get_settings_by_key(key: str) -> T` - Get settings by specific key
- `set_cli_args(target: str, value: Any) -> None` - Set individual CLI argument
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## Documentation
For more detailed documentation and examples, please see the [GitHub repository](https://github.com/kiarina/pydantic-settings-manager).
Raw data
{
"_id": null,
"home_page": null,
"name": "pydantic-settings-manager",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "configuration, pydantic, settings",
"author": null,
"author_email": "kiarina <kiarinadawa@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/d0/e2/200a7c336cb691edd80eb00fc07e4cb6c7146e7d074f76f30c8794a04236/pydantic_settings_manager-1.0.3.tar.gz",
"platform": null,
"description": "# pydantic-settings-manager\n\nA modern, thread-safe library for managing Pydantic settings with support for multiple configurations and runtime overrides.\n\n## Features\n\n- **Unified API**: Single `SettingsManager` class handles both simple and complex configurations\n- **Thread-safe**: Built-in thread safety for concurrent applications\n- **Type-safe**: Full type hints and Pydantic validation\n- **Flexible**: Support for single settings or multiple named configurations\n- **Runtime overrides**: Command-line arguments and dynamic configuration changes\n- **Easy migration**: Simple upgrade path from configuration files and environment variables\n\n## Installation\n\n```bash\npip install pydantic-settings-manager\n```\n\n## Quick Start\n\n### Basic Usage\n\n```python\nfrom pydantic_settings import BaseSettings\nfrom pydantic_settings_manager import SettingsManager\n\n# 1. Define your settings\nclass AppSettings(BaseSettings):\n app_name: str = \"MyApp\"\n debug: bool = False\n max_connections: int = 100\n\n# 2. Create a settings manager\nmanager = SettingsManager(AppSettings)\n\n# 3. Use your settings\nsettings = manager.settings\nprint(f\"App: {settings.app_name}, Debug: {settings.debug}\")\n# Output: App: MyApp, Debug: False\n```\n\n### Loading from Configuration\n\n```python\n# Load from a dictionary (JSON, YAML, etc.)\nmanager.user_config = {\n \"app_name\": \"ProductionApp\",\n \"debug\": False,\n \"max_connections\": 500\n}\n\nsettings = manager.settings\nprint(f\"App: {settings.app_name}\") # Output: App: ProductionApp\n```\n\n### Runtime Overrides\n\n```python\n# Override settings at runtime (e.g., from command line)\nmanager.cli_args = {\"debug\": True, \"max_connections\": 50}\n\nsettings = manager.settings\nprint(f\"Debug: {settings.debug}\") # Output: Debug: True\nprint(f\"Connections: {settings.max_connections}\") # Output: Connections: 50\n```\n\n## Multiple Configurations\n\nFor applications that need different settings for different environments or contexts:\n\n```python\n# Enable multi-configuration mode\nmanager = SettingsManager(AppSettings, multi=True)\n\n# Configure multiple environments\nmanager.user_config = {\n \"development\": {\n \"app_name\": \"MyApp-Dev\",\n \"debug\": True,\n \"max_connections\": 10\n },\n \"production\": {\n \"app_name\": \"MyApp-Prod\", \n \"debug\": False,\n \"max_connections\": 1000\n },\n \"testing\": {\n \"app_name\": \"MyApp-Test\",\n \"debug\": True,\n \"max_connections\": 5\n }\n}\n\n# Switch between configurations\nmanager.active_key = \"development\"\ndev_settings = manager.settings\nprint(f\"Dev: {dev_settings.app_name}, Debug: {dev_settings.debug}\")\n\nmanager.active_key = \"production\"\nprod_settings = manager.settings\nprint(f\"Prod: {prod_settings.app_name}, Debug: {prod_settings.debug}\")\n\n# Get all configurations\nall_settings = manager.all_settings\nfor env, settings in all_settings.items():\n print(f\"{env}: {settings.app_name}\")\n```\n\n## Advanced Usage\n\n### Thread Safety\n\nThe `SettingsManager` is fully thread-safe and can be used in multi-threaded applications:\n\n```python\nimport threading\nfrom concurrent.futures import ThreadPoolExecutor\n\nmanager = SettingsManager(AppSettings, multi=True)\nmanager.user_config = {\n \"worker1\": {\"app_name\": \"Worker1\", \"max_connections\": 10},\n \"worker2\": {\"app_name\": \"Worker2\", \"max_connections\": 20}\n}\n\ndef worker_function(worker_id: int):\n # Each thread can safely switch configurations\n manager.active_key = f\"worker{worker_id}\"\n settings = manager.settings\n print(f\"Worker {worker_id}: {settings.app_name}\")\n\n# Run multiple workers concurrently\nwith ThreadPoolExecutor(max_workers=5) as executor:\n futures = [executor.submit(worker_function, i) for i in range(1, 3)]\n for future in futures:\n future.result()\n```\n\n### Dynamic Configuration Updates\n\n```python\n# Update individual CLI arguments\nmanager.set_cli_args(\"debug\", True)\nmanager.set_cli_args(\"nested.value\", \"test\") # Supports nested keys\n\n# Update entire CLI args\nmanager.cli_args = {\"debug\": False, \"max_connections\": 200}\n\n# Get specific settings by key (multi mode)\ndev_settings = manager.get_settings_by_key(\"development\")\nprod_settings = manager.get_settings_by_key(\"production\")\n```\n\n### Complex Settings with Nested Configuration\n\n```python\nfrom typing import Dict, List\nfrom pydantic import Field\n\nclass DatabaseSettings(BaseSettings):\n host: str = \"localhost\"\n port: int = 5432\n username: str = \"user\"\n password: str = \"password\"\n\nclass APISettings(BaseSettings):\n base_url: str = \"https://api.example.com\"\n timeout: int = 30\n retries: int = 3\n\nclass AppSettings(BaseSettings):\n app_name: str = \"MyApp\"\n debug: bool = False\n database: DatabaseSettings = Field(default_factory=DatabaseSettings)\n api: APISettings = Field(default_factory=APISettings)\n features: List[str] = Field(default_factory=list)\n metadata: Dict[str, str] = Field(default_factory=dict)\n\nmanager = SettingsManager(AppSettings, multi=True)\nmanager.user_config = {\n \"development\": {\n \"app_name\": \"MyApp-Dev\",\n \"debug\": True,\n \"database\": {\n \"host\": \"dev-db.example.com\",\n \"port\": 5433\n },\n \"api\": {\n \"base_url\": \"https://dev-api.example.com\",\n \"timeout\": 10\n },\n \"features\": [\"debug_toolbar\", \"hot_reload\"],\n \"metadata\": {\"environment\": \"dev\", \"version\": \"1.0.0-dev\"}\n },\n \"production\": {\n \"app_name\": \"MyApp-Prod\",\n \"debug\": False,\n \"database\": {\n \"host\": \"prod-db.example.com\",\n \"port\": 5432,\n \"username\": \"prod_user\"\n },\n \"api\": {\n \"base_url\": \"https://api.example.com\",\n \"timeout\": 30,\n \"retries\": 5\n },\n \"features\": [\"monitoring\", \"caching\"],\n \"metadata\": {\"environment\": \"prod\", \"version\": \"1.0.0\"}\n }\n}\n\n# Use nested configuration\nmanager.active_key = \"development\"\nsettings = manager.settings\nprint(f\"DB Host: {settings.database.host}\")\nprint(f\"API URL: {settings.api.base_url}\")\nprint(f\"Features: {settings.features}\")\n```\n\n## Project Structure for Large Applications\n\nFor complex applications with multiple modules:\n\n```\nyour_project/\n\u251c\u2500\u2500 settings/\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 app.py\n\u2502 \u251c\u2500\u2500 database.py\n\u2502 \u2514\u2500\u2500 api.py\n\u251c\u2500\u2500 modules/\n\u2502 \u251c\u2500\u2500 auth/\n\u2502 \u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u2502 \u251c\u2500\u2500 settings.py\n\u2502 \u2502 \u2514\u2500\u2500 service.py\n\u2502 \u2514\u2500\u2500 billing/\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 settings.py\n\u2502 \u2514\u2500\u2500 service.py\n\u251c\u2500\u2500 config/\n\u2502 \u251c\u2500\u2500 base.yaml\n\u2502 \u251c\u2500\u2500 development.yaml\n\u2502 \u2514\u2500\u2500 production.yaml\n\u251c\u2500\u2500 bootstrap.py\n\u2514\u2500\u2500 main.py\n```\n\n### Module-based Settings\n\n```python\n# settings/app.py\nfrom pydantic_settings import BaseSettings\nfrom pydantic_settings_manager import SettingsManager\n\nclass AppSettings(BaseSettings):\n name: str = \"MyApp\"\n debug: bool = False\n secret_key: str = \"dev-secret\"\n\napp_settings_manager = SettingsManager(AppSettings, multi=True)\n\n# modules/auth/settings.py\nfrom pydantic_settings import BaseSettings\nfrom pydantic_settings_manager import SettingsManager\n\nclass AuthSettings(BaseSettings):\n jwt_secret: str = \"jwt-secret\"\n token_expiry: int = 3600\n max_login_attempts: int = 5\n\nauth_settings_manager = SettingsManager(AuthSettings, multi=True)\n```\n\n### Bootstrap Configuration\n\n```python\n# bootstrap.py\nimport yaml\nimport importlib\nfrom pathlib import Path\nfrom pydantic_settings_manager import SettingsManager\n\ndef load_config(config_path: str) -> dict:\n \"\"\"Load configuration from YAML file\"\"\"\n with open(config_path) as f:\n return yaml.safe_load(f)\n\ndef bootstrap_settings(environment: str = \"development\"):\n \"\"\"Bootstrap all settings managers with configuration\"\"\"\n \n # Load base configuration\n base_config = load_config(\"config/base.yaml\")\n env_config = load_config(f\"config/{environment}.yaml\")\n \n # Merge configurations (env overrides base)\n config = {**base_config, **env_config}\n \n # Configure each module's settings manager\n settings_managers = [\n (\"settings.app\", \"app_settings_manager\"),\n (\"modules.auth.settings\", \"auth_settings_manager\"),\n (\"modules.billing.settings\", \"billing_settings_manager\"),\n ]\n \n for module_name, manager_name in settings_managers:\n try:\n module = importlib.import_module(module_name)\n manager = getattr(module, manager_name, None)\n \n if isinstance(manager, SettingsManager):\n # Set configuration for this module\n if module_name.split('.')[-1] in config:\n manager.user_config = config[module_name.split('.')[-1]]\n manager.active_key = environment\n \n except ImportError:\n print(f\"Warning: Could not import {module_name}\")\n\n# main.py\nfrom bootstrap import bootstrap_settings\nfrom settings.app import app_settings_manager\nfrom modules.auth.settings import auth_settings_manager\n\ndef main():\n # Bootstrap all settings\n bootstrap_settings(\"production\")\n \n # Use settings throughout the application\n app_settings = app_settings_manager.settings\n auth_settings = auth_settings_manager.settings\n \n print(f\"App: {app_settings.name}\")\n print(f\"JWT Expiry: {auth_settings.token_expiry}\")\n\nif __name__ == \"__main__\":\n main()\n```\n\n### Configuration Files\n\n```yaml\n# config/base.yaml\napp:\n name: \"MyApp\"\n debug: false\n\nauth:\n token_expiry: 3600\n max_login_attempts: 5\n\nbilling:\n currency: \"USD\"\n tax_rate: 0.08\n```\n\n```yaml\n# config/development.yaml\napp:\n debug: true\n secret_key: \"dev-secret-key\"\n\nauth:\n jwt_secret: \"dev-jwt-secret\"\n token_expiry: 7200 # Longer expiry for development\n\nbilling:\n mock_payments: true\n```\n\n```yaml\n# config/production.yaml\napp:\n secret_key: \"${SECRET_KEY}\" # From environment variable\n\nauth:\n jwt_secret: \"${JWT_SECRET}\"\n max_login_attempts: 3 # Stricter in production\n\nbilling:\n mock_payments: false\n stripe_api_key: \"${STRIPE_API_KEY}\"\n```\n\n## CLI Integration\n\nIntegrate with command-line tools for runtime configuration:\n\n```python\n# cli.py\nimport click\nfrom bootstrap import bootstrap_settings\nfrom settings.app import app_settings_manager\n\n@click.command()\n@click.option(\"--environment\", \"-e\", default=\"development\", \n help=\"Environment to run in\")\n@click.option(\"--debug/--no-debug\", default=None, \n help=\"Override debug setting\")\n@click.option(\"--max-connections\", type=int, \n help=\"Override max connections\")\ndef main(environment: str, debug: bool, max_connections: int):\n \"\"\"Run the application with specified settings\"\"\"\n \n # Bootstrap with environment\n bootstrap_settings(environment)\n \n # Apply CLI overrides\n cli_overrides = {}\n if debug is not None:\n cli_overrides[\"debug\"] = debug\n if max_connections is not None:\n cli_overrides[\"max_connections\"] = max_connections\n \n if cli_overrides:\n app_settings_manager.cli_args = cli_overrides\n \n # Run application\n settings = app_settings_manager.settings\n print(f\"Running {settings.name} in {environment} mode\")\n print(f\"Debug: {settings.debug}\")\n\nif __name__ == \"__main__\":\n main()\n```\n\nUsage:\n```bash\n# Run with defaults\npython cli.py\n\n# Run in production with debug enabled\npython cli.py --environment production --debug\n\n# Override specific settings\npython cli.py --max-connections 500\n```\n\n## Migration Guide\n\n### Migrating from v0.x to v1.x\n\nStarting from v1.0.0, the library provides a unified `SettingsManager` class that replaces the previous three separate classes. The old classes are deprecated and will be removed in v2.0.0.\n\n#### From SingleSettingsManager\n\n```python\n# OLD (deprecated)\nfrom pydantic_settings_manager import SingleSettingsManager\n\nmanager = SingleSettingsManager(MySettings)\nmanager.user_config = {\"name\": \"app\", \"value\": 42}\nmanager.cli_args[\"value\"] = 100\nsettings = manager.settings\n\n# NEW (recommended)\nfrom pydantic_settings_manager import SettingsManager\n\nmanager = SettingsManager(MySettings) # multi=False is default\nmanager.user_config = {\"name\": \"app\", \"value\": 42}\nmanager.cli_args = {\"value\": 100} # Note: now a dict assignment, not dict access\nsettings = manager.settings\n```\n\n#### From MappedSettingsManager\n\n```python\n# OLD (deprecated)\nfrom pydantic_settings_manager import MappedSettingsManager\n\nmanager = MappedSettingsManager(MySettings)\nmanager.user_config = {\n \"map\": {\n \"dev\": {\"name\": \"development\", \"value\": 42},\n \"prod\": {\"name\": \"production\", \"value\": 100}\n }\n}\nmanager.set_cli_args(\"dev\")\nsettings = manager.settings\n\n# NEW (recommended)\nfrom pydantic_settings_manager import SettingsManager\n\nmanager = SettingsManager(MySettings, multi=True)\nmanager.user_config = {\n \"dev\": {\"name\": \"development\", \"value\": 42},\n \"prod\": {\"name\": \"production\", \"value\": 100}\n}\nmanager.active_key = \"dev\" # More intuitive property-based API\nsettings = manager.settings\n```\n\n#### From BaseSettingsManager\n\n```python\n# OLD (deprecated)\nfrom pydantic_settings_manager import BaseSettingsManager\n\nclass MyManager(BaseSettingsManager[MySettings]):\n def __init__(self, settings_cls):\n super().__init__(settings_cls)\n # ... custom implementation\n \n @property\n def settings(self):\n # ... custom logic\n pass\n \n def clear(self):\n # ... custom logic\n pass\n\n# NEW (recommended)\nfrom pydantic_settings_manager import SettingsManager\n\n# Use the unified manager directly\nmanager = SettingsManager(MySettings) # or SettingsManager(MySettings, multi=True)\n\n# If you need custom behavior, consider composition over inheritance:\nclass MyCustomManager:\n def __init__(self, settings_cls):\n self._manager = SettingsManager(settings_cls)\n # ... additional setup\n \n @property\n def settings(self):\n # ... custom logic using self._manager\n return self._manager.settings\n```\n\n### Key Differences\n\n1. **Unified API**: One class handles both single and multiple configurations\n2. **Property-based**: `manager.active_key = \"prod\"` instead of `manager.set_cli_args(\"prod\")`\n3. **Dict assignment**: `manager.cli_args = {...}` instead of `manager.cli_args[key] = value`\n4. **Simplified config**: No need for `\"map\"` wrapper in multi mode\n5. **Thread-safe**: Built-in thread safety for concurrent access\n\n### Deprecation Timeline\n\n- **v1.0.0**: Old classes deprecated with warnings\n- **v2.0.0**: Old classes will be removed\n\n### Suppressing Deprecation Warnings\n\nIf you need to suppress deprecation warnings temporarily:\n\n```python\nimport warnings\n\nwith warnings.catch_warnings():\n warnings.simplefilter(\"ignore\", DeprecationWarning)\n # Your code using deprecated classes\n manager = SingleSettingsManager(MySettings)\n```\n\n## Related Tools\n\n### pydantic-config-builder\n\nFor complex projects with multiple configuration files, you might want to use [`pydantic-config-builder`](https://github.com/kiarina/pydantic-config-builder) to merge and build your YAML configuration files:\n\n```bash\npip install pydantic-config-builder\n```\n\nThis tool allows you to:\n- Merge multiple YAML files into a single configuration\n- Use base configurations with overlay files\n- Build different configurations for different environments\n- Support glob patterns and recursive merging\n\nExample workflow:\n```yaml\n# pydantic_config_builder.yml\ndevelopment:\n input:\n - base/*.yaml\n - dev-overrides.yaml\n output:\n - config/dev.yaml\n\nproduction:\n input:\n - base/*.yaml\n - prod-overrides.yaml\n output:\n - config/prod.yaml\n```\n\nThen use the generated configurations with your settings manager:\n```python\nimport yaml\nfrom your_app import settings_manager\n\n# Load the built configuration\nwith open(\"config/dev.yaml\") as f:\n config = yaml.safe_load(f)\n\nsettings_manager.user_config = config\n```\n\n## Development\n\nThis project uses modern Python development tools:\n\n- **uv**: Fast Python package manager with PEP 735 dependency groups support\n- **ruff**: Fast linter and formatter (replaces black, isort, and flake8)\n- **mypy**: Static type checking\n- **pytest**: Testing framework with coverage reporting\n\n### Setup\n\n```bash\n# Install all development dependencies\nuv sync --group dev\n\n# Or install specific dependency groups\nuv sync --group test # Testing tools only\nuv sync --group lint # Linting tools only\n\n# Format code\nuv run ruff check --fix .\n\n# Run linting\nuv run ruff check .\nuv run mypy .\n\n# Run tests\nuv run pytest --cov=pydantic_settings_manager tests/\n\n# Build and test everything\nmake build\n```\n\n### Development Workflow\n\n```bash\n# Quick setup for testing\nuv sync --group test\nmake test\n\n# Quick setup for linting\nuv sync --group lint\nmake lint\n\n# Full development environment\nuv sync --group dev\nmake build\n```\n\n## API Reference\n\n### SettingsManager\n\nThe main class for managing Pydantic settings.\n\n```python\nclass SettingsManager(Generic[T]):\n def __init__(self, settings_cls: type[T], *, multi: bool = False)\n```\n\n#### Parameters\n- `settings_cls`: The Pydantic settings class to manage\n- `multi`: Whether to enable multi-configuration mode (default: False)\n\n#### Properties\n- `settings: T` - Get the current active settings\n- `all_settings: dict[str, T]` - Get all settings (multi mode)\n- `user_config: dict[str, Any]` - Get/set user configuration\n- `cli_args: dict[str, Any]` - Get/set CLI arguments\n- `active_key: str | None` - Get/set active key (multi mode only)\n\n#### Methods\n- `clear() -> None` - Clear cached settings\n- `get_settings_by_key(key: str) -> T` - Get settings by specific key\n- `set_cli_args(target: str, value: Any) -> None` - Set individual CLI argument\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## Documentation\n\nFor more detailed documentation and examples, please see the [GitHub repository](https://github.com/kiarina/pydantic-settings-manager).\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A library for managing Pydantic settings objects",
"version": "1.0.3",
"project_urls": {
"documentation": "https://github.com/kiarina/pydantic-settings-manager",
"homepage": "https://github.com/kiarina/pydantic-settings-manager",
"repository": "https://github.com/kiarina/pydantic-settings-manager"
},
"split_keywords": [
"configuration",
" pydantic",
" settings"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "4cb4fbd40cc069ff593a2ce9becae885b1e17c64a8fbb24a7edee3bdca45482f",
"md5": "da65300d46cf87f4e72c873df6e894ed",
"sha256": "71ea48b89f66ab94697234ed78d9fd8f90dabd2a19fdf0aea59087162dac0bea"
},
"downloads": -1,
"filename": "pydantic_settings_manager-1.0.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "da65300d46cf87f4e72c873df6e894ed",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 16642,
"upload_time": "2025-08-15T12:19:38",
"upload_time_iso_8601": "2025-08-15T12:19:38.174279Z",
"url": "https://files.pythonhosted.org/packages/4c/b4/fbd40cc069ff593a2ce9becae885b1e17c64a8fbb24a7edee3bdca45482f/pydantic_settings_manager-1.0.3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "d0e2200a7c336cb691edd80eb00fc07e4cb6c7146e7d074f76f30c8794a04236",
"md5": "3b7e4f09c747d9d023d97bbb1b077078",
"sha256": "5112d6d310ce277487a82294a51848401a4bbd3fc19e73e85ea4d4751e3634d5"
},
"downloads": -1,
"filename": "pydantic_settings_manager-1.0.3.tar.gz",
"has_sig": false,
"md5_digest": "3b7e4f09c747d9d023d97bbb1b077078",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 55488,
"upload_time": "2025-08-15T12:19:39",
"upload_time_iso_8601": "2025-08-15T12:19:39.816556Z",
"url": "https://files.pythonhosted.org/packages/d0/e2/200a7c336cb691edd80eb00fc07e4cb6c7146e7d074f76f30c8794a04236/pydantic_settings_manager-1.0.3.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-15 12:19:39",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "kiarina",
"github_project": "pydantic-settings-manager",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "pydantic-settings-manager"
}