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-runidempotent_notice: Custom message about idempotency behavioris_deprecated: Mark operation as deprecateddeprecated_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):
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
Fromsrc/pyinfra/api/arguments.py:
Operation Return Value
Operations return anOperationMeta object that tracks status:
OperationMeta Properties
Fromsrc/pyinfra/api/operation.py:43-204:
Calling Operations
In Deploy Scripts
The normal way to call operations:In the API
Usingadd_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
Related Concepts
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
