Skip to main content

Overview

Operations are the core building blocks of pyinfra. They define what should be done on target hosts, comparing desired state against current state and generating the necessary commands to achieve that state.
Every operation in pyinfra is idempotent by design. Running the same operation multiple times will only make changes when needed.

The @operation Decorator

The @operation decorator (defined in src/pyinfra/api/operation.py) transforms a generator function into an operation that integrates with pyinfra’s two-phase execution model:

Decorator Parameters

  • is_idempotent (default: True): Whether the operation can be safely re-run
  • idempotent_notice: Custom message about idempotency behavior
  • is_deprecated: Mark operation as deprecated
  • deprecated_for: Suggest alternative operation
  • _set_in_op: Internal flag for operation context tracking

Creating an Operation

Basic Structure

An operation is a generator function that yields shell commands or command objects:

Using Facts for Idempotency

Operations should check current state using facts before yielding commands:

Real-World Example

Here’s an example from the built-in operations (src/pyinfra/operations/files.py):
This operation demonstrates idempotency by checking if the file exists, validating checksums, and only downloading when necessary.

Operation Lifecycle

1. Call Time (Prepare Phase)

When an operation is called during the Prepare phase:

2. Execution Time (Execute Phase)

During the Execute phase, the command generator is run again:

3. Completion

After execution, the operation is marked complete:

Command Types

Operations can yield different types of commands:

String Commands

The simplest form - shell commands as strings:

StringCommand Objects

For more control over command construction:

Function Commands

Execute Python code on the control machine:

File Transfer Commands

Upload or download files:

Global Arguments

All operations accept global arguments that control execution behavior:

Available Global Arguments

From src/pyinfra/api/arguments.py:

Operation Return Value

Operations return an OperationMeta object that tracks status:

OperationMeta Properties

From src/pyinfra/api/operation.py:43-204:

Calling Operations

In Deploy Scripts

The normal way to call operations:

In the API

Using add_op for programmatic operation calls:

Nested Operations

Operations cannot call other operations directly. Use _inner to access the generator:
Nested operations are discouraged. Instead, create a deploy function that calls multiple operations.

Idempotency Patterns

Check-Then-Act

The most common pattern:

State Comparison

Compare current state to desired state:

Conditional Creation

Use shell conditionals for atomic operations:

Error Handling

Ignore Errors

Continue on Error

Conditional Retries

Testing Operations

Unit Testing

Test operation logic without execution:

Integration Testing

Test operations against real hosts:

Best Practices

Use Facts

Always check current state with facts before yielding commands. This ensures idempotency.

Descriptive Names

Use the name argument to provide clear operation descriptions for logging.

Handle Errors

Use _ignore_errors, _continue_on_error, or _retries for robust operations.

Quote Arguments

Use QuoteString for arguments that might contain spaces or special characters.

Type Hints

Add type hints to operation parameters for better IDE support and documentation.

Generator Pattern

Remember operations are generators - use yield not return for commands.

Common Pitfalls

Calling Operations Within Operations

Forgetting to Check State

Returning Instead of Yielding

Facts

Learn how to collect state information for idempotent operations

Architecture

Understand the two-phase model that powers operations

State

Deep dive into state management and operation tracking

Operations API

Browse all built-in operations