Skip to main content

RelPrim

CI PyPI version Python License

Reliability primitives for operations that cross process, network or provider boundaries.

RelPrim helps you wrap external calls with retries, timeouts, fallbacks, validation, circuit breakers, idempotency, provider-aware rate-limit recovery, execution reports and structured events.

Install

pip install relprim

Wrap an external call in seconds

from relprim import resilient


async def call_gemini(prompt: str) -> str:
    return await gemini_client.generate(prompt)


@resilient(retries=3, timeout=10, fallback=call_gemini)
async def call_openai(prompt: str) -> str:
    return await openai_client.generate(prompt)


result = await call_openai("Write a short product summary")

print(result.value)
print(result.report.to_dict())

The decorated function returns an OperationResult[T], not a raw value. This keeps the business result and the execution report explicit.

Why RelPrim?

Most external calls start simple:

response = await openai.chat.completions.create(...)

But production systems need to answer harder questions:

  • What if the provider times out?
  • What if the response is temporarily unavailable?
  • What if the provider returns an invalid response?
  • What if the primary provider is down?
  • What if you need a fallback provider?
  • What if you need to debug what happened after the fact?

RelPrim gives you two levels of adoption.

Beginner-friendly decorator API:

@resilient(retries=3, timeout=10)
async def call_provider(prompt: str) -> str:
    return await provider.generate(prompt)

Advanced composition API:

result = await (
    async_operation("generate_response", call_provider)
    .with_retry(RetryPolicy(max_attempts=3))
    .with_timeout(TimeoutPolicy(seconds=10))
    .with_validation(validation_policy(...))
    .with_fallbacks(fallback_chain(("backup_provider", call_backup)))
    .run(prompt)
)

What RelPrim provides

Current primitives:

  • Resilient decorator API
  • Retry policies
  • Exponential backoff with jitter
  • Provider-aware rate-limit recovery
  • Provider retry-after delay handling
  • Async timeout enforcement
  • Async fallback chains
  • Async circuit breakers
  • Validation policies
  • Callable validators
  • Structured events
  • Event emitters
  • No-op event sink
  • In-memory event sink
  • Async operation builder API
  • Structured execution reports
  • Operation results
  • Typed execution errors
  • Idempotency policies
  • Concurrent execution joining
  • Successful result replay
  • In-memory idempotency store

Planned primitives:

  • SQLite event store
  • OpenTelemetry exporter
  • JSON Schema validator adapter
  • Pydantic validator adapter

Prevent duplicate executions

RelPrim can deduplicate repeated or concurrent calls using an idempotency key.

from relprim import resilient


@resilient(
    retries=2,
    timeout=10,
    idempotency_key=lambda request_id, amount: f"create-payment:{request_id}",
    idempotency_ttl=3600,
)
async def create_payment(
    request_id: str,
    amount: int,
) -> str:
    return await payment_gateway.create(request_id, amount)

The first call executes the operation. Concurrent callers join the same execution, and later calls replay the successful result.

The default store is in-memory and single-process. See the idempotency guide for concurrency semantics, key design and store limitations.

Respect provider rate limits

RelPrim can use retry delays supplied by an external provider and avoid waiting longer than the current operation allows.

Provider SDKs expose retry information differently, so a small extractor translates the provider exception into a delay expressed in seconds:

def provider_retry_after(
    exception: Exception,
) -> float | None:
    if isinstance(exception, ProviderRateLimitError):
        return exception.retry_after_seconds

    return None

Pass the extractor to @resilient(...):

@resilient(
    retries=3,
    timeout=10,
    rate_limit_on=(ProviderRateLimitError,),
    retry_after=provider_retry_after,
    max_rate_limit_wait=30,
    fallback=call_backup_provider,
)
async def call_provider(prompt: str) -> str:
    return await provider.generate(prompt)

RelPrim uses the provider delay when available and falls back to the normal retry backoff otherwise. When the selected delay exceeds max_rate_limit_wait, the operation continues into fallback or final failure.

See the rate-limit handling guide for delay selection, report metadata, structured events and limitations.

Examples

Practical examples are available in the examples directory:

If you run examples from a cloned repository, install RelPrim in editable mode first:

python -m pip install -e ".[dev]"
python examples/decorator_usage.py

Or run a single example without installing the package:

PYTHONPATH=src python examples/decorator_usage.py

Documentation

Design principles

RelPrim is intentionally small and explicit.

Core principles:

  • Reliability behavior should be visible in code.
  • Failure modes should be explicit.
  • Defaults should be safe for production use.
  • Primitives should be composable, not magical.
  • Observability should be built into the execution model.
  • Async execution should respect cancellation and timeout semantics.
  • The library should not hide side effects behind fake safety guarantees.
  • External integrations should be wrapped, not replaced.

RelPrim does not try to become a workflow engine. It provides the reliability layer that can be used inside your application, worker, service or orchestration system.

What RelPrim is not

RelPrim is not:

  • an AI provider SDK
  • an HTTP client
  • a workflow engine
  • a task queue
  • an observability backend
  • a replacement for provider-native SDKs
  • a replacement for Temporal, Celery or OpenTelemetry

It is a reliability layer for external operations.

Maintainer

Created and maintained by Bart Rozycki.

License

Apache License 2.0

Release files for relprim 0.9.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 relprim 0.9.0
File Size Uploaded
relprim-0.9.0.tar.gz 45.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for relprim 0.9.0
File Interpreter ABI Platform
relprim-0.9.0-py3-none-any.whl Python 3 none any Details

Total release size: 79.7 kB

Release files / relprim-0.9.0.tar.gz

Download URL relprim-0.9.0.tar.gz
Size 45.8 kB
Tags Source
SHA-256 checksum
How to use checksums
49bf24fd62eb21816fd834f7fc21f6df4759302bffc3afc7409a8a7063b5fd59
BLAKE2b-256 checksum
How to use checksums
392efd00a6d771f2f6d36670aaacdd47c3d40c2c05d3efbbeaac4abd86381ea6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release files / relprim-0.9.0-py3-none-any.whl

Download URL relprim-0.9.0-py3-none-any.whl
Size 33.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a09d79f935b69261931c8a0bee799d1cb1eee886dabc038df5fec482f065ab71
BLAKE2b-256 checksum
How to use checksums
07ceb552e32cdcc77cc0234f28cb6219f4093f116984183968090e33a22b29bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release history Release notifications | RSS feed

0.10.0

2 release files

This release

0.9.0 This release

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

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