Skip to content

Python API

Everything below can be imported from the top-level parry package.

parry.install(guard: Parry | None = None, *, sync=True, async_=True,
openai=True, anthropic=True, **options) -> Parry

Hooks the SDKs and makes guard (or a new Parry(**options)) the global guard. It is idempotent, and calling it again with no arguments returns the installed guard. Passing both a guard and options raises TypeError.

parry.install_async(guard: Parry | None = None, **options) -> Parry

The same as install(sync=False).

parry.uninstall() -> None

Restores the original SDK methods and forgets the global guard.

parry.is_installed() -> bool # hooks applied and a global guard set
parry.get_guard() -> Parry | None # with-block guard first, then the installed guard
parry.stats() -> ParryStats # stats of get_guard(), or an empty snapshot
parry.wrap(client, guard: Parry | None = None) -> client
parry.unwrap(client) -> client

Binds a guard to one SDK client object. It returns the same client, so you can wrap it inline: client = parry.wrap(openai.OpenAI()).

with parry.bypass():
...

A context manager that turns parry off for every call inside it.

parry.storage(path) -> Parry

Enables SQLite persistence on the global guard, creating the guard if needed.

parry.count_tokens(text: str, model: str = "gpt-4o") -> int
parry.estimate_tokens(payload_json: str, model: str = "gpt-4o") -> int

tiktoken counts. estimate_tokens counts the prompt fields of a JSON request plus a per-message overhead.

class Parry:
def __init__(self, *, cache=True, rate_limit=True, cost_tracking=True, retry=True,
max_cost_usd=None, cache_ttl=3600, cache_size=10_000,
on_rate_limit="wait", max_wait=60.0, max_attempts=3,
retry_base_delay=0.5, retry_max_delay=30.0, storage=None): ...

See Configuration for every option.

MethodReturnsDescription
set_rate_limit(model, requests_per_minute=None, tokens_per_minute=None, *, burst=None, provider=None)Parryadd or replace a rule. With no limits, the rule means “unlimited”
remove_rate_limit(model, *, provider=None)boolremove a rule
rate_limit_for(provider, model)dictthe rule that applies
set_pricing(model, input_per_million, output_per_million)Parryprice an unknown model, or override a built-in price
set_cache(backend="memory", *, path=None)Parry"memory" or "sqlite"
storage(path)Parryenable SQLite persistence
clear_cache()Noneempty the memory and SQLite caches
reset_stats()Nonezero the counters and session spend, which restarts the budget
MemberDescription
install(**kwargs)parry.install(self, **kwargs)
wrap(client)parry.wrap(client, self)
with guard: / async with guard:make this the guard for the enclosed block (thread- and task-local)
is_interceptingTrue if SDK calls currently reach this guard through the hooks
MemberTypeDescription
statsParryStatssnapshot of the counters
total_cost_usdfloatsession spend
MethodDescription
acquire(provider, model, tokens=0)block until the rate limiter admits a request, or raise RateLimited
await acquire_async(provider, model, tokens=0)async version
check_budget(model="", prompt_tokens=0)raise BudgetExceeded if this call would cross the budget
record_usage(provider, model, input_tokens, output_tokens, *, estimated=False)record a call made outside the hooks. Returns its cost

A frozen dataclass. See Stats & logging for every field. It also has cache_hit_rate (a property) and summary(), which returns a human-readable report.

CachedRawResponse · AsyncCachedRawResponse

Section titled “CachedRawResponse · AsyncCachedRawResponse”

Returned on a cache hit for calls made through .with_raw_response.

MemberDescription
parse()the cached SDK object. On AsyncCachedRawResponse it is awaitable
headers{"x-parry-cache": "hit"}
status_code200
json(), text, content, read()the serialised response
class ParryError(Exception): ...
class BudgetExceeded(ParryError):
spent_usd: float
limit_usd: float
estimate_usd: float
model: str
class RateLimited(ParryError):
provider: str
model: str
retry_after: float # seconds until a slot frees up

SDK exceptions, such as openai.RateLimitError after the last retry, are re-raised unchanged, never wrapped.

Cache, RateLimit, Cost and Retry are documented in Custom clients & primitives.