Skip to main content

Create a series of dependent actions and roll everything back when one of them fails.

Install

$ pip install saga_py

Usage

Simple example

from saga import SagaBuilder

counter1 = 0
counter2 = 0

def incr_counter1(amount):
    global counter1
    counter1 += amount

def incr_counter2(amount):
    global counter2
    counter2 += amount

def decr_counter1(amount):
    global counter1
    counter1 -= amount

def decr_counter2(amount):
    global counter2
    counter2 -= amount

SagaBuilder \
    .create() \
    .action(lambda: incr_counter1(15), lambda: decr_counter1(15)) \
    .action(lambda: incr_counter2(1), lambda: decr_counter2(1)) \
    .action() \
    .build() \
    .execute()

# if every action succeeds, the effects of all actions are applied
print(counter1)  # 15
print(counter2)  # 1

An action fails example

If one action fails, the compensations for all already executed actions are run and a SagaError is raised that wraps all Exceptions encountered during the run.

from saga import SagaBuilder, SagaError

counter1 = 0
counter2 = 0

def incr_counter1(amount):
    global counter1
    counter1 += amount

def incr_counter2(amount):
    global counter2
    counter2 += amount
    raise BaseException('some error happened')

def decr_counter1(amount):
    global counter1
    counter1 -= amount

def decr_counter2(amount):
    global counter2
    counter2 -= amount

try:
    SagaBuilder \
        .create() \
        .action(lambda: incr_counter1(15), lambda: decr_counter1(15)) \
        .action(lambda: incr_counter2(1), lambda: decr_counter2(1)) \
        .action() \
        .build() \
        .execute()
except SagaError as e:
    print(e)  # wraps the BaseException('some error happened')

print(counter1)  # 0
print(counter2)  # 0

An action and a compensation fail example

Since the compensation for action2 fails, the compensation effect is undefined from the framework’s perspective, all other compensations are run regardless.

from saga import SagaBuilder, SagaError

counter1 = 0
counter2 = 0

def incr_counter1(amount):
    global counter1
    counter1 += amount

def incr_counter2(amount):
    global counter2
    counter2 += amount
    raise BaseException('some error happened')

def decr_counter1(amount):
    global counter1
    counter1 -= amount

def decr_counter2(amount):
    global counter2
    raise BaseException('compensation also fails')

try:
    SagaBuilder \
        .create() \
        .action(lambda: incr_counter1(15), lambda: decr_counter1(15)) \
        .action(lambda: incr_counter2(1), lambda: decr_counter2(1)) \
        .action() \
        .build() \
        .execute()
except SagaError as e:
    print(e)  #

print(counter1)  # 0
print(counter2)  # 1

Passing values from one action to the next

An action can return a dict of return values. The dict is then passed as keyword arguments to the next action and it’s corresponding compensation. No values can be passed between compensations.

from saga import SagaBuilder, SagaError

counter1 = 0
counter2 = 0

def incr_counter1(amount):
    global counter1
    counter1 += amount
    return {'counter1_value': counter1}

def incr_counter2(counter1_value):
    global counter2
    counter2 += amount

def decr_counter1(amount):
    global counter1
    counter1 -= amount

def decr_counter2(counter1_value):
    global counter2
    counter2 -= amount

SagaBuilder \
    .create() \
    .action(lambda: incr_counter1(15), lambda: decr_counter1(15)) \
    .action(incr_counter2, decr_counter2) \
    .action() \
    .build() \
    .execute()

print(counter1)  # 15
print(counter2)  # 15

Metadata

Release files for saga_py 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for saga_py 1.1.0
File Size Uploaded Provenance Actions
saga_py-1.1.0.tar.gz 5.8 kB Without provenance Details for saga_py-1.1.0.tar.gz

Release files / saga_py-1.1.0.tar.gz

Download URL saga_py-1.1.0.tar.gz
Size 5.8 kB
Tags Source
SHA-256 checksum
How to use checksums
b3c679310c95bd1288cec9b2d07c0c4f28ac2b748d4c2dc7da38f437f5a0850e
BLAKE2b-256 checksum
How to use checksums
32cfe9e4003be1bb7386882e1427b7c79c599ffc80ca84ecfbfd76d8efc1e067
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No

Security for saga_py 1.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page