You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: examples/saga.py
+20-3Lines changed: 20 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -275,7 +275,20 @@ async def create_shipment(
275
275
items: list[str],
276
276
address: str,
277
277
) ->tuple[str, str]:
278
-
"""Create a shipment for the order."""
278
+
"""
279
+
Create a shipment for an order and record its tracking number.
280
+
281
+
Parameters:
282
+
order_id (str): Identifier of the order to ship.
283
+
items (list[str]): List of item identifiers included in the shipment.
284
+
address (str): Shipping address; must not be empty.
285
+
286
+
Returns:
287
+
tuple[str, str]: A tuple containing the created `shipment_id` and its `tracking_number`.
288
+
289
+
Raises:
290
+
ValueError: If `address` is empty.
291
+
"""
279
292
ifnotaddress:
280
293
raiseValueError("Shipping address is required")
281
294
@@ -469,7 +482,11 @@ class OrderSaga(Saga[OrderContext]):
469
482
470
483
471
484
asyncdefrun_successful_saga() ->None:
472
-
"""Demonstrate a successful saga execution."""
485
+
"""
486
+
Run an example order-processing saga and print the per-step progress and final results.
487
+
488
+
Sets up mock services, dependency injection, and in-memory saga storage; executes the OrderSaga with a generated saga ID, prints each completed step, then prints the final saga status, context fields (inventory reservation, payment ID, shipment ID) and the persisted execution log. If saga execution fails, the failure is printed and the exception is re-raised.
489
+
"""
473
490
print("\n"+"="*70)
474
491
print("SCENARIO 1: Successful Order Processing Saga")
RuntimeError: Indicates the primary step failed (service unavailable).
157
+
"""
148
158
self._call_count+=1
149
159
logger.info(
150
160
f" [PrimaryStep] Executing act() for order {context.order_id} "f"(call #{self._call_count})...",
@@ -271,7 +281,11 @@ async def run_saga(
271
281
272
282
273
283
asyncdefmain() ->None:
274
-
"""Run saga fallback example."""
284
+
"""
285
+
Run an interactive demonstration of the saga fallback pattern with a circuit breaker.
286
+
287
+
Executes three scenarios that show a failing primary step with an automatic fallback, the circuit breaker opening after a configurable number of failures, and fail-fast behavior when the circuit is open. Also conditionally demonstrates configuring a Redis-backed circuit breaker storage, prints per-scenario results and a summary, and informs about missing optional dependencies.
288
+
"""
275
289
print("\n"+"="*80)
276
290
print("SAGA FALLBACK PATTERN WITH CIRCUIT BREAKER EXAMPLE")
277
291
print("="*80)
@@ -419,4 +433,4 @@ class OrderSagaWithRedisBreaker(Saga[OrderContext]):
This demonstrates how recovery ensures eventual consistency by:
628
-
1. Loading saga state from storage
629
-
2. Reconstructing context
630
-
3. Resuming execution from last completed step
631
-
4. Completing remaining steps
634
+
Recover and complete an interrupted saga using persisted state.
635
+
636
+
Loads the saga state from storage, reconstructs the saga context, resumes execution from the last completed step, and completes any remaining steps to restore eventual consistency.
637
+
638
+
Parameters:
639
+
saga_id (uuid.UUID): Identifier of the saga instance to recover.
640
+
storage (MemorySagaStorage): Durable storage containing the saga's persisted state and step history.
Simulate a saga that fails and gets interrupted during compensation.
692
-
693
-
This shows recovery of compensation logic, which is critical for
694
-
maintaining consistency when rollback is interrupted.
700
+
Simulate a saga that fails and is interrupted during compensation.
701
+
702
+
Sets up services, a saga, and a failing shipment step to trigger compensation that is then artificially interrupted; returns identifiers and storage state for performing recovery in a separate run.
703
+
704
+
Returns:
705
+
tuple[uuid.UUID, MemorySagaStorage]: The saga ID and the in-memory storage containing the persisted saga state and step history after the simulated interruption.
Recover and complete the interrupted compensation.
805
-
806
-
This ensures that even if compensation is interrupted, it will
807
-
eventually complete, releasing all resources.
815
+
Recover and complete an interrupted compensation for a saga.
816
+
817
+
Loads the saga state from the provided storage using the given saga identifier and drives any incomplete compensation steps to completion, ensuring resources (inventory, payments, shipments) are released and the system reaches a consistent state. Progress and final status are printed to stdout.
818
+
819
+
Parameters:
820
+
saga_id (uuid.UUID): Identifier of the saga to recover.
821
+
storage (MemorySagaStorage): Persistent storage containing the saga state and step history.
Run the saga recovery scheduler demo and display its outcome.
625
+
626
+
Sets up an in-memory saga storage, creates a simulated interrupted saga and marks it stale, runs the recovery loop for three iterations (using the module's recovery_loop and recovery configuration constants), then loads and prints the final saga state.
# 1. Create SQLAlchemy Engine with Connection Pool
144
144
# SQLAlchemy creates a pool by default (QueuePool for most dialects, SingletonThreadPool for SQLite)
145
+
"""
146
+
Run a demonstration that executes an OrderSaga using an async SQLAlchemy engine and persistent SqlAlchemySagaStorage.
147
+
148
+
Initializes a pooled async SQLAlchemy engine and schema, creates a session factory and SqlAlchemySagaStorage, bootstraps a mediator with a DI container and saga mapper, runs an OrderSaga while streaming step results to stdout, and then reloads and prints the persisted saga state and step history before disposing the engine.
Compensate all completed steps in reverse order with retry mechanism.
55
-
56
-
Args:
57
-
completed_steps: List of completed step handlers to compensate
53
+
Compensates completed saga steps in reverse order, applying retry logic and recording step statuses.
54
+
55
+
Compensates each handler from last to first, skipping steps already recorded as compensated in the saga history. Updates the saga status to COMPENSATING at the start and logs per-step statuses (STARTED, COMPLETED, FAILED) in storage. After a step completes, the optional on_after_compensate_step callback (if provided) is awaited. If any step fails after all retry attempts, the saga is marked as FAILED. If no completed steps are provided, no compensation is attempted and the saga is marked as FAILED.
56
+
57
+
Parameters:
58
+
completed_steps (list[SagaStepHandler[ContextT, typing.Any]]): Handlers corresponding to steps that completed during the saga; these will be compensated in reverse order.
container: DI container for resolving step handlers
93
-
saga_steps: List of saga steps
87
+
Construct a SagaRecoveryManager that holds the identifiers, storage, DI container, and configured saga steps required to reconstruct a saga's execution state.
88
+
89
+
Parameters:
90
+
saga_id: Identifier for the saga instance (e.g., UUID or other unique value).
91
+
storage: Persistence backend implementing saga history operations (ISagaStorage or SagaStorageRun).
92
+
container: Dependency injection container used to resolve step handler instances.
93
+
saga_steps: Ordered list of saga step types or Fallback wrappers that define the saga's execution sequence.
Reconstruct list of completed step handlers from history.
116
-
117
-
Args:
118
-
completed_step_names: Set of completed step names
119
-
115
+
Reconstructs and returns the resolved step handler instances corresponding to the completed steps, preserving saga execution order.
116
+
117
+
Parameters:
118
+
completed_step_names (set[str]): Names of steps that completed the "act" action.
119
+
120
120
Returns:
121
-
List of step handlers in execution order
121
+
list[SagaStepHandler[SagaContext, typing.Any]]: Resolved step handler instances in execution order. For Fallback wrappers, the primary handler is chosen if its name appears in completed_step_names; otherwise the fallback handler is chosen when present.
0 commit comments