Constants and Expressions

Constants and expressions allow you to define reusable values in your configuration. Constants are static values, while expressions are dynamically evaluated using Python.

Constants

Define constants using the constants: key. Constants are static values that don't change during execution.

Global Constants

Defined under dyngle: and available to all operations:

dyngle:
  constants:
    environment: production
    region: us-west-2
    api-url: https://api.example.com
  operations:
    deploy:
      steps:
        - echo "Deploying to {{environment}} in {{region}}"

Local Constants

Defined within a specific operation:

dyngle:
  operations:
    greet:
      constants:
        greeting: Hello
        name: World
      steps:
        - echo "{{greeting}}, {{name}}!"

Local constants override global constants with the same name.

Expressions

Expressions are Python code snippets that compute dynamic values. They're evaluated in real-time using the Python interpreter with a controlled set of available functions and variables specific to Dyngle.

Basic Usage

Define expressions that evaluate to values:

dyngle:
  operations:
    greet:
      expressions:
        greeting: "'Hello ' + name + '!'"
      steps:
        - echo "{{greeting}}"

Run it:

echo "name: Alice" | dyngle run greet

Output:

Hello Alice!

Expressions vs Constants

An expression is like a constant that is evaluated dynamically:

dyngle:
  constants:
    static-time: "2024-01-01"  # Always the same
  expressions:
    current-time: "datetime.now()"  # Evaluated each time

Global Expressions

Defined under dyngle: and available to all operations:

dyngle:
  expressions:
    timestamp: "datetime.now()"
    author: "'Francis Potter'"
  operations:
    log:
      steps:
        - echo "[{{timestamp}}] Log by {{author}}"

Local Expressions

Defined within a specific operation:

dyngle:
  operations:
    say-hello:
      expressions:
        count: "len(name)"
      steps:
        - echo "Hello {{name}}! Your name has {{count}} characters."

Local expressions override global expressions with the same name.

Available Functions and Names

Expressions evaluate in a context that includes a subset of Python's standard features plus some Dyngle-specific functions. This controlled environment ensures expressions are powerful yet predictable.

Referencing Context Values

Values from the operation context can be referenced directly as Python variables:

dyngle:
  operations:
    greet:
      expressions:
        message: "'Hello ' + name"
      steps:
        - echo "{{message}}"

Hyphenated Names

YAML keys can contain hyphens. To reference them in expressions:

Option 1: Replace hyphens with underscores:

dyngle:
  operations:
    greet:
      expressions:
        message: "'Hello ' + first_name"  # References 'first-name'
      steps:
        - echo "{{message}}"

Option 2: Use the get() function:

dyngle:
  operations:
    greet:
      expressions:
        message: "'Hello ' + get('first-name')"
      steps:
        - echo "{{message}}"

Dyngle-Specific Functions

get()

Retrieve values from the operation context by name:

dyngle:
  expressions:
    full-greeting: "'Hello ' + get('first-name') + ' ' + get('last-name')"

The get() function can also reference other expressions:

dyngle:
  expressions:
    greeting: "'Hello'"
    full-greeting: "get('greeting') + ' ' + name"

format()

Render a template string using the current operation context:

dyngle:
  values:
    first-name: Alice
    last-name: Smith
  operations:
    greet:
      expressions:
        full-greeting: "format('Hello, {{first-name}} {{last-name}}!')"
      steps:
        - echo "{{full-greeting}}"

The format() function supports all template syntax, including nested properties:

dyngle:
  operations:
    weather-report:
      expressions:
        report: "format('Temperature in {{location.city}} is {{weather.temperature}} degrees')"
      steps:
        - echo "{{report}}"

dtformat()

Format datetime objects as strings:

dyngle:
  expressions:
    now: "datetime.now()"
    timestamp: "dtformat(get('now'), '%Y-%m-%d %H:%M:%S')"
  operations:
    log:
      steps:
        - echo "[{{timestamp}}] Event occurred"

PurePath()

Work with operating system paths:

dyngle:
  operations:
    git-dir:
      expressions:
        result: PurePath(cwd) / '.git'
      steps:
        - pwd => cwd
      returns: result   

Note that command steps can be used for I/O operations while using Python to manipulate paths and strings.

dyngle:
  operations:
    show-tests:
      expressions:
        test-dir: PurePath(cwd) / 'test'
        test-files: '[f.strip() for f in test_files_text.split("\n")]'
        result:
          test-dir: get('test-dir')
          test-files: get('test-files')
      steps:
        - pwd => cwd
        - ls -1 {{test-dir}} => test-files-text
      returns: result   

Runtime Declarations

Runtime declarations are automatically provided values that reflect runtime execution context. Unlike constants and expressions defined in your configuration, these are set by the Dyngle runtime based on how the operation was invoked.

runtime.args

Command-line arguments passed to dyngle run are available under runtime.args:

dyngle:
  operations:
    greet:
      expressions:
        name: "get('runtime.args.0') or 'World'"
      steps:
        - echo "Hello {{name}}!"

Run with:

dyngle run greet Alice

Output:

Hello Alice!

Important notes:

  • runtime.args is only available in the run command, not in MCP operations
  • Args are not automatically passed to sub-operations; use send: to pass them explicitly
  • Access via get('runtime.args.N') for safe access with defaults
  • Access via runtime.args.N in templates when you know the arg exists

The runtime namespace is reserved for future runtime-provided values (environment info, execution metadata, etc.).

Environment and System Functions

getenv()

Access environment variables:

dyngle:
  operations:
    show-env:
      expressions:
        home-dir: "getenv('HOME', '/default/path')"
        api-key: "getenv('API_KEY') or 'not-set'"
      steps:
        - 'echo "Home directory: {{home-dir}}"'
        - 'echo "API Key: {{api-key}}"'

getcwd()

Get the current working directory:

dyngle:
  operations:
    show-cwd:
      expressions:
        current-dir: "getcwd()"
        parent-dir: "str(PurePath(getcwd()).parent)"
      steps:
        - 'echo "Working in: {{current-dir}}"'
        - 'echo "Parent: {{parent-dir}}"'

Data Serialization Functions

to_json()

Convert Python data structures to JSON strings:

dyngle:
  operations:
    create-config:
      expressions:
        config-data:
          server: "format('{{host}}')"
          port: "int(get('port'))"
          enabled: "True"
        json-output: "to_json(get('config-data'))"
      steps:
        - echo "{{json-output}}"

from_json()

Parse JSON strings into Python data structures:

dyngle:
  operations:
    parse-json:
      expressions:
        parsed: "from_json(json_string)"
        server-host: "get('parsed')['server']"
      steps:
        - 'echo "Server: {{server-host}}"'

to_yaml()

Convert Python data structures to YAML strings:

dyngle:
  operations:
    create-yaml:
      expressions:
        config-data:
          database: "format('{{db-name}}')"
          timeout: "30"
        yaml-output: "to_yaml(get('config-data'))"
      steps:
        - echo "{{yaml-output}}"

from_yaml()

Parse YAML strings into Python data structures:

dyngle:
  operations:
    parse-yaml:
      expressions:
        parsed: "from_yaml(yaml_string)"
        db-name: "get('parsed')['database']"
      steps:
        - 'echo "Database: {{db-name}}"'

Python Features

Expressions support a subset of Python's features:

Built-in Types and Functions

  • str(), int(), float(), bool(), len(), etc.

Standard Library Modules

  • datetime - Date and time operations (datetime.now(), datetime.date(), etc.)
  • math - Mathematical functions (math.pi, math.sqrt(), etc.)
  • re - Regular expression operations (re.match(), re.search(), re.sub(), etc.)
  • PurePath() - Path manipulation operations (no file I/O)
  • json - JSON serialization via to_json() and from_json() functions
  • yaml - YAML serialization via to_yaml() and from_yaml() functions
  • os - Environment and system operations via getenv() and getcwd() functions

Data Structures

  • Lists, dictionaries, tuples
  • List comprehensions
  • Dictionary comprehensions

Operators

  • Arithmetic: +, -, *, /, //, %, **
  • Comparison: ==, !=, <, >, <=, >=
  • Logical: and, or, not
  • String: concatenation, formatting

Expression Examples

String Manipulation

dyngle:
  expressions:
    uppercase-name: "name.upper()"
    initials: "'.'.join([word[0] for word in name.split()])"

Mathematical Operations

dyngle:
  expressions:
    circle-area: "math.pi * radius ** 2"
    rounded: "round(get('circle-area'), 2)"

Date and Time

dyngle:
  expressions:
    now: "datetime.now()"
    today: "get('now').date()"
    formatted-date: "dtformat(get('now'), '%B %d, %Y')"

List Operations

dyngle:
  values:
    numbers: [1, 2, 3, 4, 5]
  expressions:
    doubled: "[n * 2 for n in get('numbers')]"
    sum-numbers: "sum(get('numbers'))"
    max-number: "max(get('numbers'))"

Conditional Logic

dyngle:
  expressions:
    environment: "get('env') if get('env') else 'development'"
    log-level: "'DEBUG' if get('environment') == 'development' else 'INFO'"

Nested Structure Syntax

Constants and expressions can contain YAML structures. This allows defining hierarchical data that can be referenced using context paths.

For Constants

Use nested YAML structures directly:

dyngle:
  constants:
    server:
      host: api.example.com
      port: 443
      ssl: true
    database:
      name: mydb
      connection:
        - host: db.example.com
        - port: 5432
  operations:
    connect:
      steps:
        - echo "Connecting to {{server.host}}:{{server.port}}"
        - 'echo "Database: {{database.name}}"'

For Expressions

Strings within YAML structures can be referenced and evaluated as separate expression:

dyngle:
  expressions:
    configure:
      server:
        host: "format('{{server-host}}')"
        port: "int(get('server-port'))"
      database:
        name: "format('{{db-name}}')"
        connection:
          - "format('{{db-host}}')"
          - "int(get('db-port'))"

Important Notes

  • Each string in an expression structure is evaluated as Python code
  • Numbers, booleans, and None pass through unchanged in both constants and expressions
  • For string literals in expressions, use Python string syntax: "'literal string'"
  • Access nested properties using context paths: {{config.server.host}}
  • Access array elements in expressions using Python brackets: get('coordinates')[get('location-index')]

Mixed Example

Combining constants and expressions with nested structures:

dyngle:
  constants:
    defaults:
      timeout: 30
      retries: 3
  expressions:
    runtime:
      timestamp: "datetime.now()"
      timeout: "get('defaults'.timeout') * 2"
      config:
        retry-count: "get('defaults.retries')"
        enabled: "True"
  operations:
    process:
      steps:
        - 'echo "Timeout: {{runtime.timeout}}"'
        - 'echo "Retries: {{runtime.config.retry-count}}"'

Context paths

Both constants and expressions with nested structures can be accessed using context paths in:

  • Templates: {{config.server.host}}
  • Expressions: get('config.server.host') or via variables if hyphen-free
  • returns:: config.server.host
dyngle:
  constants:
    api:
      endpoint: https://api.example.com
      version: v1
  operations:
    call-api:
      returns: api.endpoint
      expressions:
        full-url: "get('api.endpoint') + '/' + get('api.version')"
      steps:
        - echo "Calling {{full-url}}"

Next Steps