Skip to main content
Operations are the core building blocks of pyinfra. They define the commands that will be executed on remote hosts to achieve desired system states. This guide will teach you how to write your own custom operations.

Understanding Operations

Operations in pyinfra:
  • Are Python generator functions decorated with @operation
  • Yield shell commands to be executed on remote hosts
  • Can check current state using facts before generating commands
  • Support idempotency by only yielding commands when changes are needed
  • Return an OperationMeta object that tracks execution status

Basic Operation Structure

Here’s the anatomy of a simple operation:

The @operation Decorator

The @operation decorator is defined in src/pyinfra/api/operation.py:240 and accepts these parameters:

Parameters

  • is_idempotent: Set to True (default) if the operation can be safely run multiple times without side effects
  • idempotent_notice: Custom message explaining idempotency behavior
  • is_deprecated: Mark the operation as deprecated
  • deprecated_for: Suggest an alternative operation

Example: Non-Idempotent Operation

Yielding Commands

Operations yield commands that will be executed on the remote host. There are several command types:

StringCommand

The most common command type for shell commands:

FileUploadCommand

Upload files from the local machine:

FileDownloadCommand

Download files from the remote host:

FunctionCommand

Execute Python functions instead of shell commands:

Using Facts

Facts allow operations to query the current state of the remote host:

Complete Example: Custom Package Operation

Here’s a full example based on the pattern in src/pyinfra/operations/files.py:75:

Advanced Patterns

Conditional Commands

Yield commands only when conditions are met:

Nested Operations

Call other operations from within your operation using the _inner function:

Error Handling

Raise errors when prerequisites aren’t met:

Operation Context

Operations have access to the current execution context:

Testing Operations

When developing operations, test them thoroughly:

Best Practices

  1. Check state before acting: Always use facts to check current state before yielding commands
  2. Use host.noop(): Call host.noop() when no changes are needed to provide user feedback
  3. Handle errors gracefully: Raise OperationError with helpful messages when operations fail
  4. Quote arguments: Use QuoteString for user-provided values to prevent injection
  5. Document parameters: Use docstring parameter format (+ param: description)
  6. Type hints: Add type hints for better IDE support and documentation
  7. Test idempotency: Ensure operations can be run multiple times safely
  8. Use string commands: Prefer StringCommand over raw strings for better error handling

Next Steps