Best Practices
Guidelines for writing effective Dyngle operations. These practices help create operations that are reliable, maintainable, and work correctly across different execution contexts.
Operation Design
Use hyphen-separated keys: Always use hyphen-separated strings (like my-variable) for YAML keys in expressions, constants, and operation names. Never use underscore-separated strings (like my_variable). This maintains consistency across your configuration.
Return structured results: Operations should return results as dicts with named fields. Add nested dicts and lists as appropriate to represent the data structure clearly.
Parse string outputs: If a command step produces a string containing structured data, parse it in expressions to return a structured result rather than a raw string.
Accept inputs via schema: Define operation inputs using the accepts: attribute to validate and default input data.
Document field purposes: Add description: to each field in your accepts: schema. Field descriptions are passed through to MCP tools and help users understand parameters in dynamic contexts like APIs or MCP servers.
Output troubleshooting info: Operations may optionally write useful troubleshooting information to stdout during execution.
Expression Usage
Use expressions for logic: Use Dyngle expressions for logic and string or data manipulation. Avoid bash -c and python -c command steps.
Break up long expressions: Avoid long multi-line expressions. Break them into multiple expressions that reference each other using get(), without losing logic or functionality.
Handle missing values: The get() function returns a blank string if the value isn't found. Use the or operator to specify defaults: get('value') or 'default'.
Reference expressions with get(): When one expression references another expression's value, always use get('expression-name'). Direct variable references only work for input values and context data, not for other expressions.
Use YAML objects over Python dicts: When defining structured data in expressions, use YAML object syntax instead of Python dict strings. YAML objects are easier to read, parse, and maintain. For example, instead of result: "{'path': get('abs-path'), 'items': get('items')}", use:
result:
path: get('abs-path')
items: get('items')
Path Management
Prefer absolute paths: Operations may run in daemons, cron jobs, or other non-interactive contexts. Prefer absolute paths over relative paths for file operations.
Convert relative to absolute: Many command steps interpret path arguments relative to the current working directory. Operations should contain logic to convert relative paths to absolute paths.
Use path functions: Use getcwd() and PurePath() for path manipulation in expressions.
Handle environment variables carefully: Environment variables like HOME can be accessed via getenv() when needed, but operations should not assume they exist in all execution contexts.
Example
This example demonstrates several best practices: accepting inputs with defaults, converting relative paths to absolute, parsing command output into structured data, and returning a well-structured result.
list-directory:
description: List contents of a directory with name, type, and size for each item (includes hidden files)
accepts:
path:
type: string
default: "."
description: Directory path to list (relative or absolute)
expressions:
# Convert relative path to absolute path
is-absolute: "get('path').startswith('/')"
relative-to-abs: "str(PurePath(getcwd()) / get('path'))"
abs-path: "get('path') if get('is-absolute') else get('relative-to-abs')"
# Parse ls output into structured data
# Each line format: "permissions links owner group size month day time name"
lines: "get('raw-listing').strip().split('\\n')"
filtered-lines: "[line for line in get('lines') if line.strip() and not line.startswith('total')]"
# Extract names from each line (last field)
names: "[line.split()[-1] for line in get('filtered-lines')]"
# Determine types (directory or file based on first character)
types: "['directory' if line.startswith('d') else 'file' for line in get('filtered-lines')]"
# Extract sizes (5th field, 0-indexed position 4)
sizes: "[int(line.split()[4]) if line.split()[4].isdigit() else 0 for line in get('filtered-lines')]"
# Combine into list of dicts
items: "[{'name': n, 'type': t, 'size': s} for n, t, s in zip(get('names'), get('types'), get('sizes'))]"
result:
path: get('abs-path')
items: get('items')
steps:
# Use ls -la to get detailed listing including hidden files
# -l: long format with details
# -a: include hidden files (starting with .)
- ls -la {{abs-path}} => raw-listing
returns: result
Note how the example:
- Uses
accepts:with a default value for the path input - Converts relative paths to absolute using expressions
- Uses hyphen-separated keys consistently throughout
- Breaks complex parsing into multiple small expressions
- Returns a structured dict with named fields using YAML object syntax instead of Python dict strings