Scoping a guard
A guard is a parry.Parry object. It owns one cache, one set of rate-limit
buckets, one spend ledger and one retry policy. You choose where it applies.
import parry
guard = parry.install() # default guardguard = parry.install(max_cost_usd=10) # guard with optionsguard = parry.install(parry.Parry(...)) # a guard you built yourselfEvery OpenAI and Anthropic client in the process is covered, including clients created by frameworks and clients created before this line.
from parry import Parry
with Parry(cache=False, max_cost_usd=1) as guard: run_eval_suite() # every call in here goes through `guard`print(guard.stats.summary())The scope is thread- and task-local (it uses contextvars), so concurrent
requests in a web server don’t leak into each other. async with works too.
import openai, parry
client = parry.wrap(openai.OpenAI()) # new default guardclient = parry.wrap(openai.OpenAI(), my_guard) # or bind a specific oneclient = my_guard.wrap(openai.OpenAI()) # same thing
parry.unwrap(client) # undoOnly this client object is guarded. Other clients are untouched.
Precedence
Section titled “Precedence”When more than one scope applies, the most specific wins:
with parry.bypass():turns parry off for everything inside it- a client bound with
parry.wrap(client, guard) - the innermost
with Parry():block - the guard from
parry.install() - no guard: the call goes straight to the SDK
parry.install() # global guard A
with Parry() as b: # B wins inside the block client.chat.completions.create(...)
c = Parry()special = c.wrap(openai.OpenAI()) # C always wins for `special`
with parry.bypass(): client.chat.completions.create(...) # no parry at allInstalling more than once
Section titled “Installing more than once”install() is idempotent. SDK methods are never wrapped twice.
| Call | Effect |
|---|---|
parry.install() again with no arguments | returns the guard that is already installed |
parry.install(cache=False) | creates a new guard with those options and installs it |
parry.install(guard, cache=False) | TypeError: pass a guard or options, not both |
parry.uninstall() | removes every hook and forgets the global guard |
Choosing what to hook
Section titled “Choosing what to hook”parry.install(anthropic=False) # OpenAI onlyparry.install(sync=False) # async clients onlyparry.install_async() # same as aboveFinding the active guard
Section titled “Finding the active guard”parry.get_guard() # with-block guard, else the installed guard, else Noneparry.is_installed() # hooks applied and a global guard setparry.stats() # stats of the active guardguard.is_intercepting # True if this guard receives SDK calls through the hooks