Migrating from 0.1 to 0.2¶
Version 0.2 is a rewrite of the internals with a handful of deliberate breaking changes. Most v0.1 code keeps working: from python_wrap.decorators import x still resolves (a compatibility module re-exports everything), and the exceptions with a natural built-in counterpart subclass it (TimeoutExpired is a TimeoutError, ValidationError a ValueError).
Checklist
- [ ] Python 3.10 or newer (Python 3.8 and 3.9 dropped).
- [ ]
run_in_threadcallers:.join()becomes.result(). - [ ]
benchmarkis now a function, not a decorator. - [ ] Replace
retry_on_exceptionwithretry(on=...). - [ ] Anything catching a bare
Exceptionfromrate_limit: catchRateLimitExceeded. - [ ] Logging: configure it yourself, and expect
DEBUGas the default level forlog_args,log_returnandtrace. - [ ] Remove
no_debug. - [ ]
validate_args/check_typewith a misspelled parameter name now fail at import time.
Run your test suite with -W error::DeprecationWarning to find deprecated calls.
Breaking changes¶
run_in_thread returns a Future¶
It used to return a threading.Thread, with no way to get the function's result. It now returns a concurrent.futures.Future.
future.result() waits like join(), returns the value, and re-raises the function's exception. Use future.done() where you used thread.is_alive() (inverted).
benchmark is a function¶
The v0.1 decorator silently ran your function three times on every call. In 0.2 you call benchmark explicitly and get a BenchmarkResult back. Use benchmark_async for async def functions.
retry_on_exception is deprecated and bounded¶
retry_on_exception still works but emits a DeprecationWarning, and its default changed: it used to retry forever with no delay; it now stops after 3 attempts. Switch to retry(on=...).
If you relied on unlimited retries, say so explicitly with max_attempts=None, and add a delay so you do not spin:
from python_wrap import retry
@retry(on=ConnectionError, max_attempts=None, delay=1.0, max_delay=30, backoff=2)
def wait_for_service() -> None:
...
Note that retry defaults to delay=1.0 while the deprecated retry_on_exception defaults to delay=0.0.
rate_limit raises RateLimitExceeded¶
It used to raise a bare Exception. RateLimitExceeded is a PythonWrapError (so it is still an Exception) and carries retry_after.
New in 0.2: rate_limit(block=True) waits instead of raising, and key= gives each user their own limit.
Logging defaults changed¶
Two changes you may notice as "my logs disappeared":
- Importing python-wrap no longer calls
logging.basicConfig. v0.1 configured the root logger as a side effect. 0.2 never configures logging; thepython_wraplogger has aNullHandler. Configure logging in your application. log_args,log_returnandtracelog atDEBUG(log_argsused to useINFO). Set the level, or passlevel=logging.INFOto restore the old output.
Every logging decorator also accepts logger= (a logger or a name) and level=. See Observability.
no_debug is deprecated¶
no_debug is now a no-op that warns and will be removed in 0.3. Delete it, and use logging levels to hide debug output.
Python 3.8 and 3.9 are dropped¶
python-wrap 0.2 requires Python 3.10 or newer. Stay on 0.1 if you cannot upgrade. The two runtime dependencies v0.1 declared but did not use (wrapt, typing-extensions) are gone: 0.2 has no dependencies.
Unknown parameter names fail at decoration time¶
validate_args and check_type now raise TypeError when you decorate if a keyword is not a parameter of the function, so a typo is found at import rather than silently never validating.
Behaviour details to know¶
These are not breaking for most code, but they can surprise you if you read the early 0.2 notes or wrote your own wrappers around python-wrap.
timeout(...)returns aTimeoutobject. It is still a decorator (@timeout(5)and@timeout(5)onasync defare unchanged), and is now also a context manager:with timeout(5):andasync with timeout(5):.retrynever retries cancellation-type exceptions.asyncio.CancelledError,KeyboardInterrupt,SystemExitandGeneratorExitpropagate immediately, even if you list them (orBaseException) inon=. Shutdown and task cancellation are no longer swallowed by a retry loop.redact=onlog_argsandauditdefaults toNone.Nonemeans "use the value fromconfigure(), orDEFAULT_REDACT". Passingredact=()still turns redaction off, andredact_extra=adds fragments to whichever list is in force.auditmay skip building a record. With nosink=and the log level disabled, nobody could see theAuditRecord, so none is built (no work, noinclude_resultevaluation). Pass asink=if you always need the record.
Behaviour that was broken and is now fixed¶
These were bugs in v0.1. Code that worked around them may need to change.
| Area | v0.1 | 0.2 |
|---|---|---|
revert_on_failure |
Claimed to revert, but its snapshot was always empty. | Actually restores vars(obj), or attrs=; deep= and target= available. |
transactional |
A no-op. | A real Transaction with on_commit / on_rollback, tx= injection and current_transaction(). |
memoize / cache keys |
Built from str(args), so f(1) and f(x=1) were different entries. |
Signature-bound, defaults applied, unhashable lists/dicts/sets handled. cache() no longer shares one store across functions. |
timing on async def |
Measured about 0 seconds. | Times the whole awaited coroutine; uses perf_counter. |
measure_memory |
ru_maxrss: Unix-only, wrong units. |
tracemalloc; works everywhere. |
timeout |
Thread fallback undocumented. | SIGALRM interruption on Unix main thread, thread fallback documented, asyncio.wait_for for async; raises TimeoutExpired (a TimeoutError). |
deprecate |
The warning pointed into the library. | Points at the caller; sets __deprecated__ (PEP 702); works on classes. |
audit |
Reported the wrapper's frame as the caller. | Reports the real caller; returns an AuditRecord. |
once |
Not thread-safe. | Thread-safe; retries after an exception; .reset() and .called. |
validate_args |
Rebuilt the signature on every call. | Binds once at decoration. |
| Windows | Import failed (imported Unix-only resource). |
Works. |
If you depended on revert_on_failure or transactional doing nothing, check those call sites: they now do what they say.
New in 0.2¶
You do not need these to migrate, but they replace common hand-written code:
retry:backoff,max_delay,jitter,on=,retry_if=,on_retry=, unlimited attempts.fallback,suppress,CircuitBreaker/circuit_breaker.memoize(maxsize=, ttl=, typed=, key=)withcache_info,cache_clear,cache_delete.throttle,debounce,singleflight.enforce_typesfor runtime checks from annotations.compose,mock_data(enabled=...),deprecate(since=, removed_in=, alternative=).retry(delay_from=retry_after_header, max_time=),retrying(...)blocks,deadline()andwith timeout(...).Bulkhead,RateLimiter,gather_limit/map_async/race,batch_callsandhedge.cached_property(ttl=),count_calls, thepython_wrap.eventshook andlog_context.policy(...)andconfigure(...)for reusable stacks and project-wide defaults.python_wrap.testing:fast_mode,FakeClock,spyand pytest fixtures.
See the changelog for the full list.