A library for managing Pydantic settings objects
Project description
pydantic-settings-manager
A modern, thread-safe library for managing Pydantic settings with support for multiple configurations and runtime overrides.
Features
- Unified API: Single
SettingsManagerclass 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
pip install pydantic-settings-manager
Quick Start
Basic Usage
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
# 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
# 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:
# 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:
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
# 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
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
# 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
# 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
# config/base.yaml
app:
name: "MyApp"
debug: false
auth:
token_expiry: 3600
max_login_attempts: 5
billing:
currency: "USD"
tax_rate: 0.08
# 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
# 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:
# 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:
# 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
# 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
# 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
# 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
- Unified API: One class handles both single and multiple configurations
- Property-based:
manager.active_key = "prod"instead ofmanager.set_cli_args("prod") - Dict assignment:
manager.cli_args = {...}instead ofmanager.cli_args[key] = value - Simplified config: No need for
"map"wrapper in multi mode - 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:
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 to merge and build your YAML configuration files:
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:
# 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:
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
# 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
# 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.
class SettingsManager(Generic[T]):
def __init__(self, settings_cls: type[T], *, multi: bool = False)
Parameters
settings_cls: The Pydantic settings class to managemulti: Whether to enable multi-configuration mode (default: False)
Properties
settings: T- Get the current active settingsall_settings: dict[str, T]- Get all settings (multi mode)user_config: dict[str, Any]- Get/set user configurationcli_args: dict[str, Any]- Get/set CLI argumentsactive_key: str | None- Get/set active key (multi mode only)
Methods
clear() -> None- Clear cached settingsget_settings_by_key(key: str) -> T- Get settings by specific keyset_cli_args(target: str, value: Any) -> None- Set individual CLI argument
License
This project is licensed under the MIT License - see the 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pydantic_settings_manager-1.0.0.tar.gz.
File metadata
- Download URL: pydantic_settings_manager-1.0.0.tar.gz
- Upload date:
- Size: 55.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
438f313abf4cb78397fed2aaaa63f97d82b2c0b864afdd4a3efd7ec1594789f7
|
|
| MD5 |
a0ccbbdc8897f68a4e4ac3542248b13a
|
|
| BLAKE2b-256 |
e3754d2ac51a104c823860740c7aa694a8c8650512010d6629b54500d2cdfbdb
|
File details
Details for the file pydantic_settings_manager-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pydantic_settings_manager-1.0.0-py3-none-any.whl
- Upload date:
- Size: 16.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bba1b0cdf2ed6abd7b9ad60641979e1732e6873902ee8e68c67173f7cd766d6d
|
|
| MD5 |
8efd1032cf50b549a1fec99b07634634
|
|
| BLAKE2b-256 |
b43b16cd27c14b2595d651fd25a2ca9d08a03435f307de45d066f15cdb18fd88
|