Skip to content

Basic Features

This guide covers the fundamental features that make Dracon a powerful configuration management system.

Configuration Loading

Simple Loading

from dracon import load, loads

# Load from file
config = load('config.yaml')

# Load from string
config = loads("""
database:
  host: localhost
  port: 5432
""")

# Load multiple files (later overrides earlier)
config = load(['base.yaml', 'prod.yaml'])

Pydantic Integration

from pydantic import BaseModel
from dracon import load

class DatabaseConfig(BaseModel):
    host: str = 'localhost'
    port: int = 5432

class AppConfig(BaseModel):
    database: DatabaseConfig

# Automatic validation and type conversion
config = load('config.yaml', context={'AppConfig': AppConfig})
# Returns validated AppConfig instance

YAML Extensions

Basic Interpolation

# Environment variables
log_level: ${getenv('LOG_LEVEL', 'INFO')}
api_url: ${getenv('API_URL', 'http://localhost:8080')}

# Mathematical expressions
max_workers: ${os.cpu_count() * 2}
timeout: ${30 + 10}

# String operations
app_name: ${getenv('APP_NAME', 'myapp').lower()}

File Includes

# Include entire files
database: !include file:config/database.yaml
secrets: !include file:secrets/api-keys.yaml

# Include with path context
config: !include file:$DIR/local.yaml # $DIR = current file's directory

# Include specific keys
db_host: !include file:config/database.yaml@host
redis_config: !include file:config/cache.yaml@redis.settings

Environment and Variable Loading

# Load from environment variables
api_key: !include env:API_SECRET
database_url: !include env:DATABASE_URL

# Define and use variables
!define app_name: myapp
!define version: 1.2.3

service_name: ${app_name}-service
image_tag: ${app_name}:${version}

Configuration Merging

Basic Merge Operations

# Simple merge (base values win conflicts)
app_config:
  environment: prod
  workers: 4
  <<: !include file:base.yaml

# Recursive merge (override specific nested values)
database:
  host: prod-db.example.com
  pool_size: 20
  <<{+}: !include file:base-db.yaml # Merges recursively

Merge Strategies

# Different merge behaviors
<<{+}: !include file:base.yaml # Recursive merge, new wins
<<{>+}: !include file:base.yaml # Recursive merge, existing wins
<<{~}: !include file:base.yaml # Replace completely
<<[+]: !include file:base.yaml # Append lists
<<[~]: !include file:base.yaml # Replace lists

CLI Generation

Automatic CLI from Models

from typing import Annotated, Literal
from pydantic import BaseModel
from dracon import Arg, make_program

class AppConfig(BaseModel):
    # Required argument (no default)
    environment: Annotated[
        Literal['dev', 'prod', 'test'],
        Arg(short='e', help="Deployment environment")
    ]

    # Optional arguments (have defaults)
    port: Annotated[int, Arg(help="Server port")] = 8080
    debug: Annotated[bool, Arg(help="Enable debug mode")] = False
    workers: Annotated[int, Arg(help="Worker processes")] = 1

# Create CLI program
program = make_program(AppConfig, name="myapp", description="My application")

# Parse arguments and get validated config
config, raw_args = program.parse_args(['--environment', 'prod', '--port', '9090'])

CLI Usage Patterns

# Basic arguments
myapp --environment prod --port 9090 --debug

# Short flags
myapp -e prod --workers 4

# Load configuration files
myapp +config/base.yaml +config/prod.yaml

# Override nested values
myapp --database.host db.example.com --database.port 5433

# Load values from files
myapp --api-key +secrets/api.key

# Define runtime variables
myapp --define.region us-west-2 --define.version 1.2.3

Basic Instructions

Variable Definition

# Define variables for reuse
!define database_host: db.example.com
!define api_version: v2
!define retry_count: 3

# Use variables
database:
  host: ${database_host}
  connection_string: postgresql://${database_host}/myapp

api:
  endpoint: https://api.example.com/${api_version}
  retries: ${retry_count}

Simple Loops

# Generate configuration for multiple environments
!define environments: [dev, staging, prod]

!each(env) ${environments}:
  ${env}_database:
    host: db.${env}.local
    name: myapp_${env}

# Generate service endpoints
!define services: [auth, api, worker]

!each(service) ${services}:
  ${service}_url: http://${service}.example.com

Pydantic Model Tags

Model Construction in YAML

class DatabaseConfig(BaseModel):
    host: str
    port: int = 5432
    ssl: bool = False

class AppConfig(BaseModel):
    name: str
    database: DatabaseConfig
# Direct model construction
app: !AppConfig
  name: myapp
  database: !DatabaseConfig
    host: db.example.com
    port: 5433
    ssl: true

# Alternative syntax
app: !AppConfig
  name: myapp
  database:
    host: db.example.com
    port: 5433
    ssl: true

Keypath References

Referencing Other Configuration Values

environment: prod
region: us-west-2

# Reference other keys in the same config
database:
  host: "db.${@/environment}.${@/region}.local" # db.prod.us-west-2.local
  backup_host: "backup.${@host}" # backup.db.prod.us-west-2.local

# Relative references
api:
  version: v2
  auth_endpoint: "https://auth.api.com/${@version}"
  data_endpoint: "https://data.api.com/${@version}"

Basic Deferred Execution

Runtime Context Resolution

from dracon import DeferredNode, construct

class Config(BaseModel):
    # Value computed at runtime
    output_path: DeferredNode[str]

# In YAML
output_path: "/data/${runtime_id}/logs"

# Later, provide runtime context
final_config = construct(config.output_path, context={'runtime_id': 'job_123'})

Error Handling

Validation Errors

from pydantic import BaseModel, validator

class Config(BaseModel):
    port: int

    @validator('port')
    def validate_port(cls, v):
        if not (1024 <= v <= 65535):
            raise ValueError(f"Port must be between 1024-65535, got {v}")
        return v

# Clear error messages with context
try:
    config = load('config.yaml', context={'Config': Config})
except Exception as e:
    print(e)  # Shows file location, validation errors, etc.

File Organization Patterns

Layered Configuration

config/
├── defaults.yaml      # Base defaults
├── environments/
│   ├── dev.yaml      # Development overrides
│   ├── staging.yaml  # Staging overrides
│   └── prod.yaml     # Production overrides
├── secrets/
│   ├── api-keys.yaml
│   └── database.yaml
└── local.yaml        # Local developer overrides (gitignored)
# Main configuration
app_config:
  # Load in order of precedence (later wins)
  <<: !include file:defaults.yaml
  <<{>+}: !include file:environments/${getenv('ENVIRONMENT', 'dev')}.yaml
  <<{>+}: !include file:local.yaml

Secret Management

# Separate secrets from configuration
database:
  host: db.prod.example.com
  port: 5432
  # Load sensitive data separately
  username: !include file:secrets/db-user.txt
  password: !include env:DB_PASSWORD

api:
  base_url: https://api.example.com
  key: !include env:API_SECRET

Built-in Context Functions

OS and Path Operations

# Environment variables
debug_mode: ${getenv('DEBUG', 'false')}
home_dir: ${expanduser('~')}

# File system operations
config_dir: ${getcwd()}/config
log_files: ${listdir('/var/log/myapp')}

# Path operations
data_path: ${join(expanduser('~'), 'data', 'myapp')}
script_name: ${basename(__file__)}

File Context Variables

# Automatic context when loading files
backup_config: ${DIR}/backup/${FILE_STEM}.backup.yaml
load_timestamp: "Loaded at ${FILE_LOAD_TIME}"
config_size: "Config file is ${FILE_SIZE} bytes"

Common Patterns

Environment-Specific Configuration

!define env: ${getenv('ENVIRONMENT', 'dev')}

# Environment-specific database
database: !include file:config/database-${env}.yaml


### Service Configuration

```yaml
!define service_name: ${getenv('SERVICE_NAME', 'myapp')}
!define service_port: ${int(getenv('PORT', '8080'))}

service:
  name: ${service_name}
  port: ${service_port}
  health_check: http://localhost:${service_port}/health

# Load service-specific config
service_config: !include file:services/${service_name}.yaml