Shipeasy
SDKsReferencePython

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 engaged

metrics

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 optional

ops

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")
Was this page helpful?
Updated July 25, 2026

On this page