Snippets
Minimal copy-paste blocks for flags, configs, kill switches and metric tracking.
Minimal copy-paste blocks, grouped by the registry taxonomy. These are the same leaves the docs get op returns.
release
release / flags
Read a feature flag on a user-bound client. Assumes configure() ran at
startup — see Installation.
Basic check
import shipeasy
# construct once per callsite (cheap; binds the user)
client = shipeasy.Client(current_user)
# name flag name (required)
# default=False returned ONLY when the flag can't be evaluated
# (client not ready / flag absent) — never when it
# simply resolves off
if client.get_flag("new_checkout", default=False):
...Why it resolved that way — get_flag_detail
import shipeasy
client = shipeasy.Client(current_user)
# returns FlagDetail(value, reason); reason ∈ RULE_MATCH / DEFAULT / OFF /
# OVERRIDE / FLAG_NOT_FOUND / CLIENT_NOT_READY
detail = client.get_flag_detail("new_checkout")
log.info("flag=%s value=%s reason=%s", "new_checkout", detail.value, detail.reason)React to flag changes (long-running server)
import shipeasy
# requires configure(poll=True); fires after a poll fetches NEW data (200, not 304)
unsubscribe = shipeasy.on_change(lambda: rebuild_local_cache())
# ... later: unsubscribe()release / configs
Read a dynamic config on a user-bound client. Assumes configure() ran at
startup — see Installation.
Raw value
import shipeasy
# construct once per callsite (cheap; binds the user)
client = shipeasy.Client(current_user)
# name config name (required)
# default={} returned when the key is absent (or decode raises)
config = client.get_config("billing_copy", default={})Typed decode
import shipeasy
client = shipeasy.Client(current_user)
# decode=lambda v: ... transform the raw JSON value into the shape you want;
# applied on top of overrides — if it raises, default is returned
max_items = client.get_config("billing_copy", decode=lambda v: v["max"], default=0)release / killswitches
Check a kill switch on a user-bound client. Assumes configure() ran at
startup — see Installation.
Top-level guard
import shipeasy
# construct once per callsite (cheap; binds the user)
client = shipeasy.Client(current_user)
# name kill switch name (required)
if client.get_killswitch("payments"): # True == engaged (feature killed)
return fallback()Named switch — check one configured per-key switch
import shipeasy
client = shipeasy.Client(current_user)
# switch_key the variable you check against the switches
# CONFIGURED on the kill switch (dashboard "switches");
# if that key isn't configured, falls back to the
# top-level value above
provider = "stripe"
if client.get_killswitch("payments", provider):
return use_backup_processor() # the "stripe" switch is engagedmetrics
metrics / track
Track a metric/conversion event from the bound client. Metrics in the dashboard
are computed from these events. Assumes configure() ran at startup — see
Installation.
Track an event
import shipeasy
# construct once per callsite (cheap; binds the user)
client = shipeasy.Client(current_user)
# event_name the event your metric is built on (required)
# properties={...} optional payload; numeric/string fields you can
# sum/filter on in a metric (private attributes are
# stripped before the event leaves the process)
client.track("checkout_started", {"amount": 49, "currency": "usd"})Fire-and-forget (never blocks your response) and a no-op under
configure_for_testing / configure_for_offline. The unit is the bound user
(user_id, else anonymous_id); with no unit the call is a no-op.
Track without properties
import shipeasy
client = shipeasy.Client(current_user)
client.track("checkout_started") # properties are optionalops
ops / see
Report a caught, handled error (or a non-exception "violation") to Shipeasy with
see() — fire-and-forget, never re-raises. Package-level, so it reports against
the engine from configure(). Assumes configure() ran at startup — see
Installation.
Report a handled exception
from shipeasy import see
try:
charge(order)
except PaymentError as e:
# .causes_the(subject) what the error affects (e.g. "checkout")
# .to(outcome) the terminal — what you do about it; builds + fires
see(e).causes_the("checkout").to("use the backup processor")
fallback_charge(order)Attach context with .extras(...)
from shipeasy import see
try:
charge(order)
except PaymentError as e:
# .to(outcome, mapping) PREFERRED: fold the extras into the terminal.
# The consequence sentence stays whole and
# there is no ordering to remember.
see(e).causes_the("checkout").to("use cached prices", {"order_id": oid})
# .to() fires synchronously here, so a trailing .extras() AFTER .to() is
# ignored with a warning (it never raises into the except block) — the
# extras are DROPPED. Use the inline form above, or add_extras() below.
# see(e).causes_the("checkout").to("use cached prices").extras({"order_id": oid})
# NEVER: extras wedged between the subject and the outcome — it splits the
# consequence sentence in half and is hard to read.
# see(e).causes_the("checkout").extras({"order_id": oid}).to("use cached prices")Attach context from anywhere with add_extras(...)
Prefer this over the inline form whenever the context already exists above the except block — it keeps the catch site a clean one-liner.
import shipeasy
# Buffer extras earlier in the request — from any layer, not just the except.
# Every see() report that fires LATER in the same request carries them, so you
# don't have to thread context down into the catch site. Backed by a ContextVar
# (concurrent requests / async tasks never mix); the WSGI/ASGI/Django middleware
# clears it per request (outside a request, call shipeasy.clear_extras yourself).
# Accepts a mapping and/or keyword args.
shipeasy.add_extras(order_id=order.id, tenant=tenant.slug)
# ...deep in a service, later in the same request...
try:
charge(order)
except PaymentError as e:
# report carries order_id + tenant automatically; a chained .extras / .to
# extra of the same key wins over the ambient one.
shipeasy.see(e).causes_the("checkout").to("use cached prices")Report a non-exception violation
from shipeasy import see_violation
# a bad state that isn't an exception — same chain, .to() is the terminal
see_violation("missing_invoice").causes_the("billing").to("skip the dunning email")Mark an expected exception — report NOTHING
from shipeasy import control_flow_exception
try:
parse(token)
except StopIteration as e:
# transmits nothing; .because(...) / .extras() are local-debug only
control_flow_exception(e).because("end of stream is expected")Admin API client (optional) — `shipeasy.admin`
The base SDK evaluates flags, configs, and experiments (configure() + shipeasy.Client(user)). The Admin API client is a separate, optional surface for…
Agent skill
Installable LLM skill for the Python SDK (configure() + Client(user), evaluate, experiment + track, testing).