A Python library for schema-based YAML configuration management with built-in validation, constraints, and type safety
Project description
ConfFlow
An extremely fast Python library for schema-based YAML configuration management with built-in validation, constraints, and type safety.
Features
-
Type-Safe Configuration: Support for all common data types
- Scalars:
str,int,float,bool,datetime,bytes - Lists:
list[str],list[int],list[float],list[bool],list[datetime],list[bytes]
- Scalars:
-
Comprehensive Validation: Built-in constraint system
- String constraints: min/max length, regex patterns, enum values
- Numeric constraints: greater/less than (or equal), ranges
- List constraints: min/max length, item-level validation
-
Flexible Schema Organization:
- Nested Schemas: Create hierarchical configuration structures with unlimited nesting depth
- Multiple Independent Schemas: Compose configurations from separate schema definitions
- Split Configuration Files: Load and merge multiple YAML files into a single validated config
- Directory Loading: Automatically load all
.ymlfiles from a directory
-
Group Constraints: Advanced validation logic
OneOf: Exactly one schema from a group must be presentAnyOf: At least one schema from a group must be present
-
Template Generation: Automatic YAML template creation with inline documentation
-
Default Values: Support for defaults at all levels
Installation
pip install confflow
Quick Start
1. Define Your Schema
from confflow import Manager
from confflow.schema import Schema
from confflow.schema.fields import StringField, IntegerField, BooleanField
from confflow.schema.groups import OneOf
# Create a schema
database_schema = Schema(
"database",
description="Database configuration"
).add(
StringField(
"host",
description="Database host",
default="localhost",
min_length=1,
max_length=255
)
).add(
IntegerField(
"port",
description="Database port",
default=5432,
gt=0,
le=65535
)
).add(
BooleanField(
"ssl_enabled",
description="Enable SSL",
default=True
)
)
# Create configuration manager
manager = Manager(database_schema)
2. Generate Configuration Templates
# Generate individual template files for each schema
manager.create_templates("./templates")
This creates separate YAML template files with inline documentation:
templates/database_template.yml:
# Database configuration
database:
# Database host
# type: str
# constraints:
# - Minimum length = 1
# - Maximum length = 255
host: localhost
# Database port
# type: int
# constraints:
# - Greater than: 0
# - Less than or equal: 65535
port: 5432
# Enable SSL
# type: bool
ssl_enabled: True
3. Load and Validate Configuration
# Load configuration from a single file
config = manager.load("config.yml")
# Or load from multiple files (later files override earlier ones)
config = manager.load("base.yml", "environment.yml", "overrides.yml")
# Or load all .yml files from a directory
config = manager.load("./config")
# Access configuration with dot notation or subscription
print(config.database.host) # Dot notation
print(config["database"]["host"]) # Subscription
print(config.database["port"]) # Mixed!
print(config["database"].ssl_enabled) # Also mixed!
Configuration Organization Patterns
ConfFlow supports two powerful patterns for organizing your configuration:
Pattern 1: Nested Schemas (Hierarchical Structure)
Use nested schemas when you want a single configuration file with hierarchical organization:
# Create a single schema with nested structure
app_schema = Schema(
"app",
description="Application configuration"
).add(
StringField("name", description="App name", default="MyApp")
).add(
StringField("version", description="App version", default="1.0.0")
).add(
# Nest database config inside app schema
Schema("database", description="Database settings")
.add(StringField("host", description="DB host", default="localhost"))
.add(IntegerField("port", description="DB port", default=5432))
.add(BooleanField("ssl", description="Use SSL", default=True))
).add(
# Nest cache config inside app schema
Schema("cache", description="Cache settings")
.add(StringField("backend", description="Cache backend", default="redis"))
.add(IntegerField("ttl", description="TTL in seconds", default=3600))
)
manager = Manager(app_schema)
manager.create_templates("./templates")
# Creates: ./templates/app_template.yml (single file with nested structure)
Generated app_template.yml:
# Application configuration
app:
# App name
# type: str
name: MyApp
# App version
# type: str
version: 1.0.0
# Database settings
database:
# DB host
# type: str
host: localhost
# DB port
# type: int
port: 5432
# Use SSL
# type: bool
ssl: True
# Cache settings
cache:
# Cache backend
# type: str
backend: redis
# TTL in seconds
# type: int
ttl: 3600
Usage:
config = manager.load("app.yml")
print(config.app.name) # "MyApp"
print(config.app.database.host) # "localhost"
print(config.app.cache.backend) # "redis"
Pattern 2: Multiple Independent Schemas (Modular Structure)
Use multiple schemas when you want separate, independent configuration files that can be managed and loaded separately:
# Create separate top-level schemas
database_schema = Schema(
"database",
description="Database configuration"
).add(
StringField("host", description="DB host", default="localhost")
).add(
IntegerField("port", description="DB port", default=5432)
)
api_schema = Schema(
"api",
description="API configuration"
).add(
StringField("base_url", description="API base URL", default="http://localhost:8000")
).add(
IntegerField("timeout", description="Request timeout (seconds)", default=30)
)
logging_schema = Schema(
"logging",
description="Logging configuration"
).add(
StringField("level", description="Log level", default="INFO", enum=["DEBUG", "INFO", "WARNING", "ERROR"])
).add(
StringField("format", description="Log format", default="json")
)
# Manager accepts multiple schemas
manager = Manager(database_schema, api_schema, logging_schema)
# Generate separate template files
manager.create_templates("./config/templates")
# Creates:
# - ./config/templates/database_template.yml
# - ./config/templates/api_template.yml
# - ./config/templates/logging_template.yml
Usage - Load All or Some:
# Load all configurations
config = manager.load(
"./config/database.yml",
"./config/api.yml",
"./config/logging.yml"
)
# Access each configuration section
print(config.database.host) # "localhost"
print(config.api.base_url) # "http://localhost:8000"
print(config.logging.level) # "INFO"
# Or load only what you need (partial configuration)
config = manager.load("./config/database.yml", "./config/api.yml")
print(config.database.port) # 5432
print(config.api.timeout) # 30
# config.logging would not exist
Pattern 3: Combining Both Approaches
You can mix nested and modular schemas for maximum flexibility:
# Main app config with nested auth
app_schema = Schema("app", description="Application").add(
StringField("name", default="MyApp")
).add(
Schema("auth", description="Authentication").add(
StringField("provider", default="oauth2")
).add(
IntegerField("session_timeout", default=3600)
)
)
# Separate database config
database_schema = Schema("database", description="Database").add(
StringField("host", default="localhost")
)
# Separate feature flags config
features_schema = Schema("features", description="Feature flags").add(
BooleanField("new_ui", default=False)
).add(
BooleanField("beta_api", default=False)
)
manager = Manager(app_schema, database_schema, features_schema)
# Load configurations - mix templates and custom files
config = manager.load(
"./templates/app_template.yml", # Base app config with nested auth
"./config/database.yml", # Database config
"./config/features_dev.yml", # Dev-specific feature flags
"./config/overrides.yml" # Local overrides
)
print(config.app.name) # "MyApp"
print(config.app.auth.provider) # "oauth2" (nested)
print(config.database.host) # From database.yml (modular)
print(config.features.new_ui) # From features_dev.yml (modular)
Multi-File Loading and Merging
Load and merge multiple YAML files - later files override earlier ones:
# Base configuration with defaults
# base.yml:
# database:
# host: localhost
# port: 5432
# Environment-specific overrides
# production.yml:
# database:
# host: prod-db.example.com
# ssl_enabled: True
# Local development overrides
# local.yml:
# database:
# port: 5433
# Load with override priority: base < production < local
config = manager.load("base.yml", "production.yml", "local.yml")
print(config.database.host) # "prod-db.example.com" (from production.yml)
print(config.database.port) # 5433 (from local.yml - overrides base)
print(config.database.ssl_enabled) # True (from production.yml)
Directory Loading
Load all .yml files from a directory automatically:
# Directory structure:
# ./config/
# ├── database.yml
# ├── api.yml
# └── logging.yml
# Load all .yml files from directory (sorted alphabetically)
config = manager.load("./config")
# Equivalent to:
# config = manager.load("./config/api.yml", "./config/database.yml", "./config/logging.yml")
# Access configurations
print(config.database.host)
print(config.api.base_url)
print(config.logging.level)
Note: Directory loading is non-recursive and only loads files directly in the specified directory. Files are loaded in alphabetical order.
Advanced Features
Group Constraints
from confflow.schema.groups import OneOf, AnyOf
# OneOf: Exactly one cloud provider must be configured
cloud_schema = Schema("config", description="Cloud configuration").add(
OneOf(
Schema("aws", description="AWS config")
.add(StringField("region", description="AWS region", default="us-east-1")),
Schema("gcp", description="GCP config")
.add(StringField("project", description="GCP project", default="my-project")),
Schema("azure", description="Azure config")
.add(StringField("subscription", description="Subscription ID"))
)
)
# AnyOf: At least one observability tool must be enabled
observability_schema = Schema("config", description="Config").add(
AnyOf(
Schema("logging", description="Logging config")
.add(StringField("level", description="Log level", default="INFO")),
Schema("monitoring", description="Monitoring config")
.add(StringField("provider", description="Provider", default="prometheus")),
Schema("tracing", description="Tracing config")
.add(StringField("backend", description="Backend", default="jaeger"))
)
)
Field Constraints
from confflow.schema.fields import (
StringField, IntegerField, FloatField,
Stringlist, Integerlist, Floatlist
)
schema = Schema("app", description="Application").add(
# String with regex and enum
StringField(
"environment",
description="Deployment environment",
default="development",
enum=["development", "staging", "production"]
)
).add(
# Integer with range
IntegerField(
"max_connections",
description="Max connections",
default=100,
ge=1,
le=1000
)
).add(
# Float with precision
FloatField(
"threshold",
description="Threshold percentage",
default=95.5,
ge=0.0,
le=100.0
)
).add(
# String list with item validation
Stringlist(
"allowed_origins",
description="CORS origins",
default=["http://localhost:3000"],
min_length=1,
max_length=10,
item_regex=r"^https?://"
)
).add(
# Integer list with item constraints
Integerlist(
"port_ranges",
description="Allowed ports",
default=[8000, 8080, 8443],
min_length=1,
max_length=20,
item_gt=0,
item_le=65535
)
)
Custom Constraints
from confflow.schema.constraint import Constraint, ValidationError
class CustomEmailConstraint(Constraint[str]):
def __call__(self, value: str) -> str:
if not value.endswith("@company.com"):
raise ValidationError(f"{value} must be a company email")
return value
def __repr__(self) -> str:
return "CustomEmailConstraint()"
def to_formatted_string(self, indent: int = 0) -> str:
return "Must end with @company.com"
# Use in field
StringField(
"email",
CustomEmailConstraint(),
description="Company email",
regex=r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
)
Validation from Dictionary
# Validate dictionary data directly
data = {
"database": {
"host": "db.example.com",
"port": 5432,
"ssl_enabled": True
}
}
config = manager.loads(data)
API Reference
Manager
The Manager class coordinates validation and template generation for your schemas.
Manager(*schemas: Schema)
- Initializes with one or more schemas
- Each schema becomes a top-level configuration section
- Raises
ValueErrorif no schemas provided or duplicates detected
manager.validate(data: dict)
- Validates configuration data against all schemas
- Raises
ValueErroron validation failure
manager.loads(data: dict) -> Config
- Loads and validates configuration from a dictionary
- Returns a frozen
Configdataclass
manager.load(*filepaths: str | Path) -> Config
- Loads and merges configuration from multiple files or a directory
- If a single directory path is provided, loads all
.ymlfiles from that directory - Later files override earlier ones for duplicate keys
- Returns a validated
Configobject - Raises
ValueErrorif no files provided or if a directory contains no.ymlfiles
manager.create_templates(directory: str | Path)
- Creates
{schema_name}_template.ymlfor each schema - Creates directory if it doesn't exist
Schema
Schema(name: str, description: str)
- Creates a configuration schema with a name and description
schema.add(item: Schema | Group | Field) -> Self
- Adds a field, nested schema, or group constraint
- Returns self for method chaining
schema.validate(data: dict)
- Validates data against the schema
- Raises
ValueErroron validation failure
Fields
Scalar Fields:
StringField(name, *, description, default, min_length, max_length, regex, enum)IntegerField(name, *, description, default, gt, ge, lt, le)FloatField(name, *, description, default, gt, ge, lt, le)BooleanField(name, *, description, default)DateField(name, *, description, default)BytesField(name, *, description, default)
List Fields:
Stringlist(name, *, description, default, min_length, max_length, item_min_length, item_max_length, item_regex, item_enum)Integerlist(name, *, description, default, min_length, max_length, item_gt, item_ge, item_lt, item_le)Floatlist(name, *, description, default, min_length, max_length, item_gt, item_ge, item_lt, item_le)Booleanlist(name, *, description, default, min_length, max_length)Datelist(name, *, description, default, min_length, max_length)Byteslist(name, *, description, default, min_length, max_length)
Groups
OneOf(*schemas): Exactly one schema must be presentAnyOf(*schemas): At least one schema must be present
Error Handling
ConfFlow raises ValueError exceptions with descriptive messages for validation failures:
try:
config = manager.load("config.yml")
except ValueError as e:
print(f"Configuration validation failed: {e}")
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
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 confflow-4.0.0.tar.gz.
File metadata
- Download URL: confflow-4.0.0.tar.gz
- Upload date:
- Size: 16.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.22
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5144ff320027a03f4ab282f550d5147529a3b388feccb607bbee4ffdae8b2a25
|
|
| MD5 |
0e7930a9444436860ce903bd813882b4
|
|
| BLAKE2b-256 |
00d8eb4062bd362533e995225119ae711a5115ce28e90495edfcbedf25e37659
|
File details
Details for the file confflow-4.0.0-py3-none-any.whl.
File metadata
- Download URL: confflow-4.0.0-py3-none-any.whl
- Upload date:
- Size: 20.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.22
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
01d14cf778acfaea420ca24d2a44f681e2f971687b6af97121f106517863387b
|
|
| MD5 |
ebb7cce03e76c363b0ed0bbf118457ae
|
|
| BLAKE2b-256 |
c03b5dfe03a419ecfb86f8ec68dfbf9e952a1873064d8fa3fc203f38a9ad6c33
|