Resilience and Control-Flow Wrappers¶
Page Maps¶
graph LR
family["Python Programming"]
program["Python Meta-Programming"]
section["Decorator Design Policies Typing"]
page["Resilience and Control-Flow Wrappers"]
capstone["Capstone evidence"]
family --> program --> section --> page
page -.applies in.-> capstone
flowchart LR
orient["Orient on the page map"] --> read["Read the main claim and examples"]
read --> inspect["Inspect the related code, proof, or capstone surface"]
inspect --> verify["Run or review the verification path"]
verify --> apply["Apply the idea back to the module and capstone"]
The moment a wrapper starts retrying, timing out, or rate-limiting, it is no longer only changing what happens around a call. It is changing whether, when, and how often the underlying function gets to run.
That makes these decorators especially important to review honestly.
The sentence to keep¶
When reviewing a resilience wrapper, ask:
how does this decorator change control flow, failure behavior, or timing at the call boundary?
That is the right question because these wrappers do not merely observe calls. They govern them.
Retry changes failure semantics¶
A retry decorator can be useful, but it is already a policy engine:
- it decides which exceptions count as retryable
- it decides how many attempts are allowed
- it decides how long to sleep between attempts
- it decides when the failure becomes final
That is far more than "just wrapping a function."
import functools
import time
def retry(exceptions=(Exception,), max_attempts=3, initial_delay=1):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
delay = initial_delay
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except exceptions:
if attempt == max_attempts - 1:
raise
time.sleep(delay)
delay *= 2
return wrapper
return decorator
This wrapper now owns a clear call-policy contract. That is why it belongs in Module 05, not the thin-wrapper module.
Timeout changes waiting semantics, not only behavior¶
A timeout wrapper changes the relationship between caller and work:
- the caller stops waiting after a threshold
- the wrapped work may still continue depending on implementation strategy
- failure now includes timeout-specific behavior, not only the original function's exceptions
That is a major semantic change, even if the wrapper code still looks compact.
Rate limiting changes scheduling semantics¶
Rate limiting governs when calls are allowed to happen at all:
- calls may block and wait
- calls may be rejected
- state about prior calls now shapes later calls
At that point the decorator is governing traffic, not just transforming one callable in place.
That is exactly why policy-heavy decorators need slower review than thin wrappers.
One picture of the control-flow change¶
Thin wrapper:
caller -> wrapper -> original function -> result
Resilience wrapper:
caller -> policy gate -> maybe wait / retry / abort -> original function -> maybe repeat
That diagram is the difference between observation and governance.
Single-threaded boundaries matter here¶
The examples in this module stay synchronous and single-threaded on purpose.
That means:
- no async cancellation model
- no cross-thread state coordination
- no distributed rate-limit storage
Those limits are important because they keep the design cost visible. A wrapper that is already subtle in single-threaded sync code becomes even more expensive under concurrency.
Backoff, jitter, and quotas are policy knobs¶
Small configuration details matter a lot:
- exponential backoff changes retry pacing
- jitter changes herd behavior under failure
- quota windows change fairness and burst behavior
These are policy decisions, not harmless implementation details. If the wrapper owns them, the review has to own them too.
These wrappers should preserve non-policy surfaces¶
Even when semantics change, the wrapper should still preserve what it can:
- callable metadata
- names and docs
- signature transparency when the wrapper claims to preserve the original call contract
This is one of the recurring lessons of the course: stronger policy does not excuse weaker observability.
Review rules for resilience wrappers¶
When reviewing retry, timeout, or rate-limit decorators, keep these questions close:
- what control-flow rule does the wrapper now own?
- which exceptions or timing outcomes are now different from the original callable?
- what state or timing knobs shape later calls?
- are concurrency or async limitations being documented honestly?
- has this policy become large enough that an explicit object or service would be easier to review?
What to practice from this page¶
Try these before moving on:
- Implement a bounded retry decorator and explain exactly when it stops retrying.
- Write down one timeout implementation caveat for sync code.
- Compare one rate-limit decorator to an explicit limiter object and explain which design would be easier to review under growth.
If those feel ordinary, the next step is annotation-aware runtime behavior, where the wrapper begins to claim knowledge about types and contracts.
Continue through Module 05¶
- Previous: Decorator Factories and Parameter Capture
- Next: Annotation-Aware Runtime Contracts
- Practice: Exercises
- Terms: Glossary