Skip to content

Interpolation Syntax

Dracon provides powerful expression interpolation using ${...} syntax for dynamic values.

Basic Interpolation

Simple Expressions

# Environment variables
log_level: ${getenv('LOG_LEVEL', 'INFO')}

# Mathematical expressions
max_workers: ${int(getenv('NUM_CPUS', '4')) * 2}

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

Evaluation Timing

All interpolation expressions — whether using ${...} or $(...) — are evaluated lazily at construction time. Both syntaxes behave identically.

# evaluated at construction time
computed: ${now()}
also_computed: $(now())  # same behavior as ${}

Shorthand Variables

When enable_shorthand_vars=True (default):

# These are equivalent:
user_home: $HOME
user_home: ${HOME}

# Complex expressions still need full syntax
computed: ${$HOME + '/data'}

Reference System

Construction-time References (@)

Reference other keys in the same configuration:

environment: prod
database:
  host: "db.${@/environment}.local" # References /environment
  backup_host: "backup.${@host}" # References database/host

Reference Syntax

Note

KeyPaths use a dot-separated syntax to navigate the YAML structure. The slash (/) is a special character that means "root".

  • @/key: Absolute reference from root
  • @key.subkey: Relative reference from current level
  • @..key: Parent level reference
  • @/nested.deep.key: Deep nested reference (starting at root)

Node (composition-time) Copies (&)

Will duplicate the referenced node:

defaults:
  timeout: 30
  retries: 3

service_config:
  name: my-service
  timeout: ${&/defaults.timeout * 2} # References anchor content

Built-in Functions

OS Functions

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

# File system
data_dir: ${expanduser('~/data')}
current_dir: ${getcwd()}
config_files: ${listdir('/etc/myapp')}

# Path operations
log_file: ${join(expanduser('~/logs'), 'app.log')}
script_name: ${basename(FILE)}   # FILE is set by the file loader
script_dir: ${dirname(FILE)}

Date/Time Functions

# Current datetime (default format: YYYY-MM-DD HH:MM:SS)
timestamp: ${now()}

# Custom format using strftime codes
date_only: ${now('%Y-%m-%d')}
time_only: ${now('%H:%M:%S')}
iso_format: ${now('%Y-%m-%dT%H:%M:%S')}
filename_safe: ${now('%Y%m%d_%H%M%S')}

NumPy (when installed)

When numpy is installed, it's available as np:

weights: ${np.array([0.1, 0.5, 0.4])}
normalized: ${np.linspace(0, 1, 10).tolist()}

Dracon Functions

# Construct deferred nodes
output_path: ${construct(deferred_node, runtime_var='value')}

Context Variables

Automatic Context (in file loading)

# File information
config_backup: ${DIR}/backup/${FILE_STEM}.backup
load_timestamp: ${FILE_LOAD_TIME}
config_size: ${FILE_SIZE}

Custom Context

# Provided at loader construction time
loader = DraconLoader(context={
    'version': '1.2.3',
    'deployment': 'production',
    'custom_func': lambda x: x.upper()
})
# Used in YAML
app_version: ${version}
deployment_type: ${deployment}
service_name: ${custom_func('myservice')}

Advanced Patterns

Conditional Expressions

# Simple conditionals
debug_mode: ${getenv('DEBUG', 'false').lower() == 'true'}

# Complex conditionals
log_level: ${
  'DEBUG' if getenv('ENVIRONMENT') == 'dev'
  else 'WARNING' if getenv('ENVIRONMENT') == 'prod'
  else 'INFO'
}

List Comprehensions

# Generate lists
service_ports: ${[8000 + i for i in range(int(getenv('REPLICAS', '3')))]}

# Filter lists
enabled_services: ${[s for s in services if s.get('enabled', True)]}

Dictionary Operations

# Merge dictionaries
merged_config: ${dict(base_config, **override_config)}

# Filter dictionaries
prod_settings: ${
  {k: v for k, v in all_settings.items()
   if not k.startswith('dev_')}
}

Key Interpolation

Generate dynamic keys:

!define environments: ["dev", "staging", "prod"]

!each(env) ${environments}:
  ${env}_database_url: postgres://db.${env}.local/myapp
  ${env}_redis_url: redis://cache.${env}.local

Nested Interpolation

# Environment-based configuration selection
!define config_key: ${getenv('ENVIRONMENT', 'dev')}_settings

# Use the computed key
app_config: ${globals()[config_key]}

# Nested expressions
complex_value: ${getenv('PREFIX', 'app') + '_' + str(getenv('VERSION', '1'))}

Escaping

Escape interpolation when you need literal ${}:

# Escaped - will be literal "${version}"
escaped_template: \${version}

# Not escaped - will interpolate
interpolated: ${version}

# Mixed
docker_command: echo \${VERSION} > /tmp/version && echo ${actual_version}

Error Handling

Safe Navigation

# Handle missing keys gracefully
optional_value: ${config.get('optional_key', 'default')}

# Chain operations safely
nested_value: ${config.get('section', {}).get('key', 'fallback')}

Try-Catch Patterns

# Using Python's exception handling
database_url: ${
  getenv('DATABASE_URL') if getenv('DATABASE_URL')
  else f"postgresql://{getenv('DB_USER')}:{getenv('DB_PASS')}@{getenv('DB_HOST')}/myapp"
}

Permissive Mode

When permissive=True, undefined variables don't raise errors — they survive as literal ${...} strings. Known variables are resolved and constant sub-expressions are folded.

from dracon.interpolation import evaluate_expression

# partial resolution
evaluate_expression("${a} and ${b}", context={'a': 1}, permissive=True)
# → "1 and ${b}"

# single expression with partial folding
evaluate_expression("${x + 1 + y}", context={'x': 10}, permissive=True)
# → "${11 + y}"

# fully unknown — returned unchanged
evaluate_expression("${unknown}", context={}, permissive=True)
# → "${unknown}"

Available on: evaluate_expression, do_safe_eval, InterpolableNode.evaluate, LazyInterpolable, resolve_all_lazy, and Dracontainer.resolve_all_lazy.

See Permissive Evaluation for details.

Performance Notes

  • Expressions are cached when possible
  • References (@ and &) are resolved efficiently
  • Complex expressions may impact loading time
  • $() and ${} behave identically — both are lazy

Common Use Cases

Environment-based Configuration

database:
  host: db.${getenv('ENVIRONMENT', 'dev')}.local
  pool_size: ${int(getenv('DB_POOL_SIZE', '10'))}
  ssl_mode: ${
    'require' if getenv('ENVIRONMENT') == 'prod'
    else 'prefer'
  }

Dynamic Service Discovery

services:
  auth_service: http://${getenv('AUTH_HOST', 'localhost')}:${getenv('AUTH_PORT', '8001')}
  data_service: http://${getenv('DATA_HOST', 'localhost')}:${getenv('DATA_PORT', '8002')}

service_mesh: ${
  [f"http://{service}:{port}"
   for service, port in zip(service_hosts, service_ports)]
}

Configuration Validation

# Validate required environment variables
database_url: ${
  getenv('DATABASE_URL') or
  (_ for _ in ()).throw(ValueError('DATABASE_URL is required'))
}