# pydantic-settings-manager
A modern, thread-safe library for managing Pydantic settings with support for multiple configurations and runtime overrides.
## Features
- **Bootstrap Pattern**: Centralized configuration loading for multi-module applications
- **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
## Table of Contents
- [pydantic-settings-manager](#pydantic-settings-manager)
- [Features](#features)
- [Table of Contents](#table-of-contents)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Single Module (Simple Projects)](#single-module-simple-projects)
- [Runtime Overrides](#runtime-overrides)
- [Bootstrap Pattern (Recommended for Production)](#bootstrap-pattern-recommended-for-production)
- [Why Bootstrap Pattern?](#why-bootstrap-pattern)
- [Project Structure](#project-structure)
- [Quick Example](#quick-example)
- [Configuration File Structure](#configuration-file-structure)
- [Custom Manager Names](#custom-manager-names)
- [Frequently Asked Questions](#frequently-asked-questions)
- [Multiple Configurations](#multiple-configurations)
- [Advanced Usage](#advanced-usage)
- [Thread Safety](#thread-safety)
- [Dynamic Configuration Updates](#dynamic-configuration-updates)
- [CLI Integration](#cli-integration)
- [Related Tools](#related-tools)
- [pydantic-config-builder](#pydantic-config-builder)
- [Development](#development)
- [Prerequisites](#prerequisites)
- [Quick Start](#quick-start-1)
- [Available Tasks](#available-tasks)
- [Common Workflows](#common-workflows)
- [Daily Development](#daily-development)
- [Before Committing](#before-committing)
- [Testing](#testing)
- [Code Quality](#code-quality)
- [Dependency Management](#dependency-management)
- [Release Process](#release-process)
- [Project Structure](#project-structure-1)
- [Technology Stack](#technology-stack)
- [Why mise?](#why-mise)
- [Troubleshooting](#troubleshooting)
- [mise not found](#mise-not-found)
- [Python version issues](#python-version-issues)
- [Dependency issues](#dependency-issues)
- [CI failures](#ci-failures)
- [API Reference](#api-reference)
- [SettingsManager](#settingsmanager)
- [Parameters](#parameters)
- [Properties](#properties)
- [Methods](#methods)
- [License](#license)
- [Contributing](#contributing)
- [Documentation](#documentation)
## Installation
```bash
pip install pydantic-settings-manager
```
## Quick Start
### Single Module (Simple Projects)
```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. Load configuration
manager.user_config = {
"app_name": "ProductionApp",
"debug": False,
"max_connections": 500
}
# 4. Use your settings
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
```
## Bootstrap Pattern (Recommended for Production)
**For multi-module applications, use the bootstrap pattern with `load_user_configs()`.** This is the recommended approach for production applications.
### Why Bootstrap Pattern?
- **Centralized Configuration**: Load all module settings from a single configuration file
- **Automatic Discovery**: No need to manually import and configure each module
- **Environment Management**: Easy switching between development, staging, and production
- **Clean Separation**: Configuration files separate from application code
### Project Structure
```
your_project/
├── settings/
│ ├── __init__.py
│ └── app.py # app_settings_manager
├── modules/
│ ├── auth/
│ │ ├── __init__.py
│ │ ├── settings.py # auth_settings_manager
│ │ └── service.py
│ └── billing/
│ ├── __init__.py
│ ├── settings.py # billing_settings_manager
│ └── service.py
├── config/
│ ├── base.yaml # Shared configuration
│ ├── development.yaml # Dev overrides
│ └── production.yaml # Prod overrides
├── bootstrap.py # Bootstrap logic
└── main.py # Application entry point
```
### Quick Example
```python
# 1. Define settings in each module
# 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"
settings_manager = SettingsManager(AppSettings)
# modules/auth/settings.py
class AuthSettings(BaseSettings):
jwt_secret: str = "jwt-secret"
token_expiry: int = 3600
settings_manager = SettingsManager(AuthSettings)
# modules/billing/settings.py
class BillingSettings(BaseSettings):
currency: str = "USD"
stripe_api_key: str = ""
settings_manager = SettingsManager(BillingSettings)
```
```yaml
# config/base.yaml (shared across all environments)
settings.app:
name: "MyApp"
modules.auth.settings:
token_expiry: 3600
modules.billing.settings:
currency: "USD"
# config/production.yaml (prod-specific overrides)
settings.app:
debug: false
secret_key: "${SECRET_KEY}"
modules.auth.settings:
jwt_secret: "${JWT_SECRET}"
modules.billing.settings:
stripe_api_key: "${STRIPE_API_KEY}"
```
```python
# bootstrap.py - RECOMMENDED IMPLEMENTATION
import os
import yaml
from pathlib import Path
from pydantic_settings_manager import load_user_configs, update_dict
def bootstrap(environment: str | None = None) -> None:
"""
Bootstrap all settings managers with environment-specific configuration.
Args:
environment: Environment name (e.g., "development", "production").
If None, uses ENVIRONMENT env var or defaults to "development".
"""
if environment is None:
environment = os.getenv("ENVIRONMENT", "development")
config_dir = Path("config")
# Load base configuration (optional)
base_file = config_dir / "base.yaml"
if base_file.exists():
with open(base_file) as f:
config = yaml.safe_load(f) or {}
else:
config = {}
# Load environment-specific configuration
env_file = config_dir / f"{environment}.yaml"
if env_file.exists():
with open(env_file) as f:
env_config = yaml.safe_load(f) or {}
# Deep merge configurations (environment overrides base)
config = update_dict(config, env_config)
# This single line configures ALL your settings managers!
load_user_configs(config)
print(f"✓ Loaded configuration for '{environment}' environment")
# main.py
from bootstrap import bootstrap
from settings.app import settings_manager as app_settings_manager
from modules.auth.settings import settings_manager as auth_settings_manager
from modules.billing.settings import settings_manager as billing_settings_manager
def main():
# Bootstrap all settings with one line
bootstrap("production")
# All settings are now configured and ready to use!
app = app_settings_manager.settings
auth = auth_settings_manager.settings
billing = billing_settings_manager.settings
print(f"App: {app.name}, Debug: {app.debug}")
print(f"JWT Expiry: {auth.token_expiry}")
print(f"Currency: {billing.currency}")
if __name__ == "__main__":
main()
```
### Configuration File Structure
The configuration file structure maps directly to your module structure:
```yaml
# Key = module path (e.g., "settings.app" → settings/app.py)
# Value = configuration for that module's settings manager
settings.app:
name: "MyApp-Production"
debug: false
secret_key: "${SECRET_KEY}" # Pydantic will read from environment
modules.auth.settings:
jwt_secret: "${JWT_SECRET}"
token_expiry: 3600
modules.billing.settings:
currency: "USD"
stripe_api_key: "${STRIPE_API_KEY}"
```
### Custom Manager Names
By default, `load_user_configs()` looks for `settings_manager` in each module. You can customize this:
```python
# settings/app.py
app_manager = SettingsManager(AppSettings) # Custom name
# bootstrap.py
load_user_configs(config, manager_name="app_manager")
```
### Frequently Asked Questions
**Q: Do I need `multi=True` for bootstrap pattern?**
A: No! Bootstrap pattern works with both single and multi mode:
- **Single mode** (recommended): One configuration per module
- **Multi mode**: Multiple configurations per module (e.g., dev/staging/prod in same manager)
```python
# Single mode (simpler, recommended for most cases)
settings_manager = SettingsManager(AppSettings)
# Multi mode (when you need multiple configs per module)
settings_manager = SettingsManager(AppSettings, multi=True)
```
**Q: How are environment variables like `${SECRET_KEY}` handled?**
A: Pydantic Settings automatically reads from environment variables. The `${VAR}` syntax in YAML is just documentation - you can use any value:
```yaml
# config/production.yaml
settings.app:
secret_key: "placeholder" # Will be overridden by SECRET_KEY env var
```
Pydantic will automatically use `os.getenv("SECRET_KEY")` if the environment variable is set.
**Q: When should I use manual configuration instead of `load_user_configs`?**
A: Only when you need module-specific logic:
- Custom validation per module
- Conditional configuration based on module state
- Dynamic module discovery
For 99% of cases, use `load_user_configs()`.
**Q: Can I use bootstrap pattern with a single module?**
A: Yes, but it's overkill. For single-module projects, just use:
```python
manager = SettingsManager(AppSettings)
manager.user_config = yaml.safe_load(open("config.yaml"))
```
## 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 (direct format)
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
}
}
# Alternative: structured format (useful when you want to set active_key in config)
# manager.user_config = {
# "key": "production", # Set active configuration
# "map": {
# "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("development")
prod_settings = manager.get_settings("production")
```
## 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
```
## 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 [mise](https://mise.jdx.dev/) for development environment management.
### Quick Start
```bash
# Install mise (macOS)
brew install mise
# Clone and setup
git clone https://github.com/kiarina/pydantic-settings-manager.git
cd pydantic-settings-manager
mise run setup
# Verify everything works
mise run ci
```
### Common Tasks
```bash
# Daily development (auto-fix + test)
mise run
# Before committing (full CI checks)
mise run ci
# Run tests
mise run test
mise run test -v # verbose
mise run test -c # with coverage
# Code quality
mise run format # format code
mise run lint # check issues
mise run lint-fix # auto-fix issues
mise run typecheck # type check
# Dependencies
mise run upgrade # upgrade dependencies
mise run upgrade --sync # upgrade and sync
# Release (see docs/runbooks/how_to_release.md for details)
mise run version 2.3.0
mise run update-changelog 2.3.0
mise run ci
git add . && git commit -m "chore: release v2.3.0"
git tag v2.3.0 && git push origin main --tags
```
### Technology Stack
- **[mise](https://mise.jdx.dev/)**: Development environment and task runner
- **[uv](https://github.com/astral-sh/uv)**: Fast Python package manager
- **[ruff](https://github.com/astral-sh/ruff)**: Fast linter and formatter
- **[mypy](https://mypy-lang.org/)**: Static type checking
- **[pytest](https://pytest.org/)**: Testing framework
For detailed documentation, see:
- Available tasks: `mise tasks`
- Release process: `docs/runbooks/how_to_release.md`
- Project info: `docs/knowledges/about_this_project.md`
## 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
- `get_settings(key: str | None = None) -> T` - Get settings by key or current active settings
- `clear() -> None` - Clear cached settings
- `set_cli_args(target: str, value: Any) -> None` - Set individual CLI argument
- `get_settings_by_key(key: str | None) -> T` - **[Deprecated]** Use `get_settings()` instead (will be removed in v3.0.0)
## 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/e4/70/ddc65a5c5fb07968039fe52860a8c8f4b05402a9c0d666b6fea9d662c905/pydantic_settings_manager-2.3.1.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- **Bootstrap Pattern**: Centralized configuration loading for multi-module applications\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## Table of Contents\n\n- [pydantic-settings-manager](#pydantic-settings-manager)\n - [Features](#features)\n - [Table of Contents](#table-of-contents)\n - [Installation](#installation)\n - [Quick Start](#quick-start)\n - [Single Module (Simple Projects)](#single-module-simple-projects)\n - [Runtime Overrides](#runtime-overrides)\n - [Bootstrap Pattern (Recommended for Production)](#bootstrap-pattern-recommended-for-production)\n - [Why Bootstrap Pattern?](#why-bootstrap-pattern)\n - [Project Structure](#project-structure)\n - [Quick Example](#quick-example)\n - [Configuration File Structure](#configuration-file-structure)\n - [Custom Manager Names](#custom-manager-names)\n - [Frequently Asked Questions](#frequently-asked-questions)\n - [Multiple Configurations](#multiple-configurations)\n - [Advanced Usage](#advanced-usage)\n - [Thread Safety](#thread-safety)\n - [Dynamic Configuration Updates](#dynamic-configuration-updates)\n - [CLI Integration](#cli-integration)\n - [Related Tools](#related-tools)\n - [pydantic-config-builder](#pydantic-config-builder)\n - [Development](#development)\n - [Prerequisites](#prerequisites)\n - [Quick Start](#quick-start-1)\n - [Available Tasks](#available-tasks)\n - [Common Workflows](#common-workflows)\n - [Daily Development](#daily-development)\n - [Before Committing](#before-committing)\n - [Testing](#testing)\n - [Code Quality](#code-quality)\n - [Dependency Management](#dependency-management)\n - [Release Process](#release-process)\n - [Project Structure](#project-structure-1)\n - [Technology Stack](#technology-stack)\n - [Why mise?](#why-mise)\n - [Troubleshooting](#troubleshooting)\n - [mise not found](#mise-not-found)\n - [Python version issues](#python-version-issues)\n - [Dependency issues](#dependency-issues)\n - [CI failures](#ci-failures)\n - [API Reference](#api-reference)\n - [SettingsManager](#settingsmanager)\n - [Parameters](#parameters)\n - [Properties](#properties)\n - [Methods](#methods)\n - [License](#license)\n - [Contributing](#contributing)\n - [Documentation](#documentation)\n\n## Installation\n\n```bash\npip install pydantic-settings-manager\n```\n\n## Quick Start\n\n### Single Module (Simple Projects)\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. Load configuration\nmanager.user_config = {\n \"app_name\": \"ProductionApp\",\n \"debug\": False,\n \"max_connections\": 500\n}\n\n# 4. Use your settings\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## Bootstrap Pattern (Recommended for Production)\n\n**For multi-module applications, use the bootstrap pattern with `load_user_configs()`.** This is the recommended approach for production applications.\n\n### Why Bootstrap Pattern?\n\n- **Centralized Configuration**: Load all module settings from a single configuration file\n- **Automatic Discovery**: No need to manually import and configure each module\n- **Environment Management**: Easy switching between development, staging, and production\n- **Clean Separation**: Configuration files separate from application code\n\n### Project Structure\n\n```\nyour_project/\n\u251c\u2500\u2500 settings/\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u2514\u2500\u2500 app.py # app_settings_manager\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 # auth_settings_manager\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 # billing_settings_manager\n\u2502 \u2514\u2500\u2500 service.py\n\u251c\u2500\u2500 config/\n\u2502 \u251c\u2500\u2500 base.yaml # Shared configuration\n\u2502 \u251c\u2500\u2500 development.yaml # Dev overrides\n\u2502 \u2514\u2500\u2500 production.yaml # Prod overrides\n\u251c\u2500\u2500 bootstrap.py # Bootstrap logic\n\u2514\u2500\u2500 main.py # Application entry point\n```\n\n### Quick Example\n\n```python\n# 1. Define settings in each module\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\nsettings_manager = SettingsManager(AppSettings)\n\n# modules/auth/settings.py\nclass AuthSettings(BaseSettings):\n jwt_secret: str = \"jwt-secret\"\n token_expiry: int = 3600\n\nsettings_manager = SettingsManager(AuthSettings)\n\n# modules/billing/settings.py\nclass BillingSettings(BaseSettings):\n currency: str = \"USD\"\n stripe_api_key: str = \"\"\n\nsettings_manager = SettingsManager(BillingSettings)\n```\n\n```yaml\n# config/base.yaml (shared across all environments)\nsettings.app:\n name: \"MyApp\"\n\nmodules.auth.settings:\n token_expiry: 3600\n\nmodules.billing.settings:\n currency: \"USD\"\n\n# config/production.yaml (prod-specific overrides)\nsettings.app:\n debug: false\n secret_key: \"${SECRET_KEY}\"\n\nmodules.auth.settings:\n jwt_secret: \"${JWT_SECRET}\"\n\nmodules.billing.settings:\n stripe_api_key: \"${STRIPE_API_KEY}\"\n```\n\n```python\n# bootstrap.py - RECOMMENDED IMPLEMENTATION\nimport os\nimport yaml\nfrom pathlib import Path\nfrom pydantic_settings_manager import load_user_configs, update_dict\n\ndef bootstrap(environment: str | None = None) -> None:\n \"\"\"\n Bootstrap all settings managers with environment-specific configuration.\n\n Args:\n environment: Environment name (e.g., \"development\", \"production\").\n If None, uses ENVIRONMENT env var or defaults to \"development\".\n \"\"\"\n if environment is None:\n environment = os.getenv(\"ENVIRONMENT\", \"development\")\n\n config_dir = Path(\"config\")\n\n # Load base configuration (optional)\n base_file = config_dir / \"base.yaml\"\n if base_file.exists():\n with open(base_file) as f:\n config = yaml.safe_load(f) or {}\n else:\n config = {}\n\n # Load environment-specific configuration\n env_file = config_dir / f\"{environment}.yaml\"\n if env_file.exists():\n with open(env_file) as f:\n env_config = yaml.safe_load(f) or {}\n # Deep merge configurations (environment overrides base)\n config = update_dict(config, env_config)\n\n # This single line configures ALL your settings managers!\n load_user_configs(config)\n\n print(f\"\u2713 Loaded configuration for '{environment}' environment\")\n\n# main.py\nfrom bootstrap import bootstrap\nfrom settings.app import settings_manager as app_settings_manager\nfrom modules.auth.settings import settings_manager as auth_settings_manager\nfrom modules.billing.settings import settings_manager as billing_settings_manager\n\ndef main():\n # Bootstrap all settings with one line\n bootstrap(\"production\")\n\n # All settings are now configured and ready to use!\n app = app_settings_manager.settings\n auth = auth_settings_manager.settings\n billing = billing_settings_manager.settings\n\n print(f\"App: {app.name}, Debug: {app.debug}\")\n print(f\"JWT Expiry: {auth.token_expiry}\")\n print(f\"Currency: {billing.currency}\")\n\nif __name__ == \"__main__\":\n main()\n```\n\n### Configuration File Structure\n\nThe configuration file structure maps directly to your module structure:\n\n```yaml\n# Key = module path (e.g., \"settings.app\" \u2192 settings/app.py)\n# Value = configuration for that module's settings manager\n\nsettings.app:\n name: \"MyApp-Production\"\n debug: false\n secret_key: \"${SECRET_KEY}\" # Pydantic will read from environment\n\nmodules.auth.settings:\n jwt_secret: \"${JWT_SECRET}\"\n token_expiry: 3600\n\nmodules.billing.settings:\n currency: \"USD\"\n stripe_api_key: \"${STRIPE_API_KEY}\"\n```\n\n### Custom Manager Names\n\nBy default, `load_user_configs()` looks for `settings_manager` in each module. You can customize this:\n\n```python\n# settings/app.py\napp_manager = SettingsManager(AppSettings) # Custom name\n\n# bootstrap.py\nload_user_configs(config, manager_name=\"app_manager\")\n```\n\n### Frequently Asked Questions\n\n**Q: Do I need `multi=True` for bootstrap pattern?**\n\nA: No! Bootstrap pattern works with both single and multi mode:\n- **Single mode** (recommended): One configuration per module\n- **Multi mode**: Multiple configurations per module (e.g., dev/staging/prod in same manager)\n\n```python\n# Single mode (simpler, recommended for most cases)\nsettings_manager = SettingsManager(AppSettings)\n\n# Multi mode (when you need multiple configs per module)\nsettings_manager = SettingsManager(AppSettings, multi=True)\n```\n\n**Q: How are environment variables like `${SECRET_KEY}` handled?**\n\nA: Pydantic Settings automatically reads from environment variables. The `${VAR}` syntax in YAML is just documentation - you can use any value:\n\n```yaml\n# config/production.yaml\nsettings.app:\n secret_key: \"placeholder\" # Will be overridden by SECRET_KEY env var\n```\n\nPydantic will automatically use `os.getenv(\"SECRET_KEY\")` if the environment variable is set.\n\n**Q: When should I use manual configuration instead of `load_user_configs`?**\n\nA: Only when you need module-specific logic:\n- Custom validation per module\n- Conditional configuration based on module state\n- Dynamic module discovery\n\nFor 99% of cases, use `load_user_configs()`.\n\n**Q: Can I use bootstrap pattern with a single module?**\n\nA: Yes, but it's overkill. For single-module projects, just use:\n\n```python\nmanager = SettingsManager(AppSettings)\nmanager.user_config = yaml.safe_load(open(\"config.yaml\"))\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 (direct format)\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# Alternative: structured format (useful when you want to set active_key in config)\n# manager.user_config = {\n# \"key\": \"production\", # Set active configuration\n# \"map\": {\n# \"development\": {\"app_name\": \"MyApp-Dev\", \"debug\": True, \"max_connections\": 10},\n# \"production\": {\"app_name\": \"MyApp-Prod\", \"debug\": False, \"max_connections\": 1000},\n# \"testing\": {\"app_name\": \"MyApp-Test\", \"debug\": True, \"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(\"development\")\nprod_settings = manager.get_settings(\"production\")\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## 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 [mise](https://mise.jdx.dev/) for development environment management.\n\n### Quick Start\n\n```bash\n# Install mise (macOS)\nbrew install mise\n\n# Clone and setup\ngit clone https://github.com/kiarina/pydantic-settings-manager.git\ncd pydantic-settings-manager\nmise run setup\n\n# Verify everything works\nmise run ci\n```\n\n### Common Tasks\n\n```bash\n# Daily development (auto-fix + test)\nmise run\n\n# Before committing (full CI checks)\nmise run ci\n\n# Run tests\nmise run test\nmise run test -v # verbose\nmise run test -c # with coverage\n\n# Code quality\nmise run format # format code\nmise run lint # check issues\nmise run lint-fix # auto-fix issues\nmise run typecheck # type check\n\n# Dependencies\nmise run upgrade # upgrade dependencies\nmise run upgrade --sync # upgrade and sync\n\n# Release (see docs/runbooks/how_to_release.md for details)\nmise run version 2.3.0\nmise run update-changelog 2.3.0\nmise run ci\ngit add . && git commit -m \"chore: release v2.3.0\"\ngit tag v2.3.0 && git push origin main --tags\n```\n\n### Technology Stack\n\n- **[mise](https://mise.jdx.dev/)**: Development environment and task runner\n- **[uv](https://github.com/astral-sh/uv)**: Fast Python package manager\n- **[ruff](https://github.com/astral-sh/ruff)**: Fast linter and formatter\n- **[mypy](https://mypy-lang.org/)**: Static type checking\n- **[pytest](https://pytest.org/)**: Testing framework\n\nFor detailed documentation, see:\n- Available tasks: `mise tasks`\n- Release process: `docs/runbooks/how_to_release.md`\n- Project info: `docs/knowledges/about_this_project.md`\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- `get_settings(key: str | None = None) -> T` - Get settings by key or current active settings\n- `clear() -> None` - Clear cached settings\n- `set_cli_args(target: str, value: Any) -> None` - Set individual CLI argument\n- `get_settings_by_key(key: str | None) -> T` - **[Deprecated]** Use `get_settings()` instead (will be removed in v3.0.0)\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": "2.3.1",
"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": "078e4f57da6567cf5827b181fa318b1be16d0806b09fc238234c3f7d7aa6af15",
"md5": "4f744e5460838a61623e5a3bc4622979",
"sha256": "6b744ccc01e3e3c97adf46dad3d3a4b3f3df17b309d2f5fd3bbf529c50fa6fe0"
},
"downloads": -1,
"filename": "pydantic_settings_manager-2.3.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "4f744e5460838a61623e5a3bc4622979",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 13386,
"upload_time": "2025-10-12T18:33:42",
"upload_time_iso_8601": "2025-10-12T18:33:42.555454Z",
"url": "https://files.pythonhosted.org/packages/07/8e/4f57da6567cf5827b181fa318b1be16d0806b09fc238234c3f7d7aa6af15/pydantic_settings_manager-2.3.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "e470ddc65a5c5fb07968039fe52860a8c8f4b05402a9c0d666b6fea9d662c905",
"md5": "280c7962a1c2857c20b411f03bb2f7e3",
"sha256": "2e2e587dd493ba2466e8c633b6bd83d9db39f8f8d94b3f7736b30431bd515dd5"
},
"downloads": -1,
"filename": "pydantic_settings_manager-2.3.1.tar.gz",
"has_sig": false,
"md5_digest": "280c7962a1c2857c20b411f03bb2f7e3",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 64257,
"upload_time": "2025-10-12T18:33:43",
"upload_time_iso_8601": "2025-10-12T18:33:43.768990Z",
"url": "https://files.pythonhosted.org/packages/e4/70/ddc65a5c5fb07968039fe52860a8c8f4b05402a9c0d666b6fea9d662c905/pydantic_settings_manager-2.3.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-12 18:33:43",
"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"
}