mrkeyoor.com_
Tue 22 Sept 01:46 UTC
PyPIUtilsupdated 21 Sept 2026

retrying review

retrying 1.4.2 is a synchronous Python decorator and callable wrapper for repeating work after selected exceptions or rejected return values. Its keyword arguments cap attempts or elapsed milliseconds and calculate fixed, random, exponential, or jittered sleep. A bare retry decorator has the dangerous default of retrying every exception forever with no delay. Version 1.4.2 updates packaging, notices, and project documentation rather than adding a new execution model. It has no coroutine-aware runner or persistent retry state.

Verdict

retrying 1.4.2 installed in 0.2 seconds and used 1 MB in our sandbox with no dependencies or audit findings. Keep it for compatible synchronous call paths; new async or observable retry work should start with Tenacity or Stamina.

We installed it

Lab card: what happened when we installed retryingScreenshot of retrying documentation
Install✓ · 0.2s1 package on disk · 1 MB
Importimport retrying in 0.11s · pure Python · requires Python >=3.6
Known vulns0(pip-audit)

Answers from our run

Does retrying install cleanly?

Yes. In a fresh container with an empty cache, pip install retrying finished in 0.2s, leaving 1 package and 1 MB on disk. pip-audit reported no known vulnerabilities.

What does retrying need to run?

Python >=3.6, and nothing compiled: it is pure Python. In our run import retrying succeeded in 0.11s.

retrying or tenacity: which should you use?

tenacity: Use it for new sync or async code needing composable policies, callbacks, and statistics. retrying 1.4.2 installed in 0.2 seconds and used 1 MB in our sandbox with no dependencies or audit findings.

When should you not use retrying?

You are writing new application code. Tenacity or Stamina supplies async support, callbacks, statistics, and safer composition.

API stability5/5The retry decorator, Retrying object, RetryError, and flat stop, wait, exception, and result callbacks retain the contract used by the older project line. Version 1.4.2 changes packaging rather than runtime policy. This preserves compatibility and also preserves unbounded immediate retries, millisecond units, and positional behavior that cannot be made safer without changing old applications.
Docs3/5The README demonstrates attempt and elapsed caps, fixed and random delay, exponential backoff, exception predicates, result predicates, wrapping, and the unlimited default. Examples are short enough to verify. It does not explain coroutine misuse, combined wait calculations, HTTP-specific rules, idempotency, operational callbacks, or the final result behavior in enough depth for a production policy.
Maintenance3/5PyPI lists 1.4.2 as current, and GitHub shows a 2026-07-26 push, 96 stars, two open issues or pull requests, and no archive flag. The revived 1.4 line removed an old dependency and modernized publishing after a long quiet period. Recent work provides packaging compatibility, but the runtime still lacks async execution, cancellation integration, and richer retry instrumentation.
Ecosystem3/5The supplied registry count is 7,763,823 weekly downloads, much of it from old applications and transitive dependency graphs. Our Python 3.12 import worked with no installed dependency. The package has no typing marker, async adapter, plugin system, transport policy, or operational reporting layer. Current retry design attention is concentrated around Tenacity and wrappers such as Stamina.

Use it if

  • Legacy synchronous code already depends on retrying's exact exception and timing behavior.
  • A small script needs bounded retries without another installed dependency.
  • Polling should repeat on a sentinel result such as None as well as on exceptions.
  • A third-party callable needs runtime-selected policy through Retrying.call.
Skip it if

Setup reality

We installed retrying 1.4.2 in a clean unprivileged Python 3.12 Bookworm container in 0.2 seconds. One package occupied 1 MB. It has zero direct dependencies, contains pure Python, and declares Python 3.6 or newer. import retrying succeeded in 0.11 seconds. pip-audit found zero known vulnerabilities. Our inspection found no py.typed marker and no license value in the installed metadata.

There are no credentials, native tools, services, or files to configure. Every duration is expressed in milliseconds: wait_fixed=2 sleeps for 2 ms, while stop_max_delay=10000 represents 10 seconds. Always set an attempts, elapsed-time, or custom stop rule because the defaults have no end.

By default every exception repeats, including TypeError and other programming mistakes. Use retry_on_exception to select transient failures. retry_on_result has reversed-looking semantics: true means reject this value and try again. Exhausted result retries raise RetryError; exhausted exception retries normally raise the original exception unless wrap_exception is true.

Several base wait policies resolve to the longest computed delay, after which jitter is added. Exponential wait starts above the multiplier on the first retry, so test the actual schedule against a latency budget. Version 1.4.2 does not understand async cancellation, HTTP Retry-After, or transport idempotency. Those policies belong in a newer retry layer or surrounding application code.

Patterns

Retry at most three calls cap-attempts

@retry(stop_max_attempt_number=3)
def fetch():
    response = session.get(url, timeout=5)
    response.raise_for_status()
    return response.text

A stopping option makes the loop finite; bare retry has unlimited immediate attempts.

Use a 10-second elapsed budget cap-elapsed-time

@retry(stop_max_delay=10_000)
def connect():
    return open_connection()

The setting is milliseconds, and the last slow call may finish after the configured elapsed threshold.

Pause 2 seconds between calls fixed-wait

@retry(stop_max_attempt_number=5, wait_fixed=2_000)
def poll():
    return request_status()

Add jitter when many clients may reach the same recovering service together.

Back off with a 10-second ceiling exponential-wait

@retry(stop_max_attempt_number=6, wait_exponential_multiplier=1_000, wait_exponential_max=10_000)
def call_remote():
    return remote_call()

The first computed exponential delay exceeds the multiplier; assert the schedule in a test.

Repeat only connection failures filter-exceptions

def transient(error):
    return isinstance(error, (TimeoutError, ConnectionError))

@retry(retry_on_exception=transient, stop_max_attempt_number=4, wait_fixed=500)
def read_remote(): return client.read()

When the predicate returns false, that exception is raised immediately.

Poll until a value appears retry-result

@retry(retry_on_result=lambda value: value is None, stop_max_attempt_number=5, wait_fixed=1_000)
def take_job(): return queue.take_nowait()

True rejects the returned value; exhaustion raises RetryError instead of returning the last None.

Apply policy without a decorator wrap-callable

policy = Retrying(stop_max_attempt_number=3, wait_fixed=200)
result = policy.call(session.get, url, timeout=5)

This works for a function you do not own or a policy chosen at runtime.

Leave coroutines to an async retry runner avoid-async

# Do not decorate async def with retrying.retry.
# Use tenacity.AsyncRetrying or stamina instead.

Calling async def returns a coroutine before its body runs, so retrying sees success and cannot catch awaited exceptions.

Alternatives

PackageRegistryPick it when
tenacityPyPIUse it for new sync or async code needing composable policies, callbacks, and statistics.
backoffPyPIUse it when maintained decorator-based exception and predicate retries fit the project.
staminaPyPIUse it for opinionated sync and async retries on Tenacity with testing helpers.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.