Python
The Shipeasy Python server SDK — local evaluation, configs, experiments, WSGI/ASGI anon middleware, and metric tracking.
Server-side flags, configs, experiments, and tracking for Python. Server-key only — never embed in browsers. Evaluates locally against a background-polled blob — see the shared evaluation model.
Install
pip install shipeasyConfigure
Initialise once with the server key, plus an optional attributes transform that maps your user object onto the Shipeasy attribute map. configure() starts a background poll.
import shipeasy
shipeasy.configure(
api_key=os.environ["SHIPEASY_SERVER_KEY"],
attributes=lambda u: {"user_id": u.id, "plan": u.plan, "country": u.country},
)Bind a client to the user
Construct a lightweight Client from your user object — the attributes transform turns it into the evaluation context, so the getters take no user argument:
flags = shipeasy.Client(current_user)Evaluate a feature flag
if flags.get_flag("new_checkout"):
...A default is returned only when the value can't be evaluated (client not ready or gate absent) — a gate that evaluates False returns False:
flags.get_flag("new_checkout", default=True)For the value plus the reason, use get_flag_detail — see Evaluation reasons:
d = flags.get_flag_detail("new_checkout")
print(d.value, d.reason) # e.g. True RULE_MATCHRead a dynamic config
config = flags.get_config("billing_copy", default={"title": "Welcome"})Resolve an experiment
result = flags.get_experiment("checkout_button", default_params={"color": "blue"})
if result.in_experiment:
print(result.group, result.params)Track an event
flags.track("purchase", {"amount": 49})The low-level Engine
The per-call style still works for back-compat. Construct shipeasy.Engine directly and pass the user on every call — use init_once() for serverless (one synchronous fetch, no thread):
engine = shipeasy.Engine(api_key=os.environ["SHIPEASY_SERVER_KEY"])
engine.init_once() # one fetch, no background thread
engine.get_flag("new_checkout", {"user_id": "u_123", "country": "US"})Anonymous visitors
Mount the middleware once; logged-out requests then bucket on the shared __se_anon_id cookie automatically — no per-call wiring:
# WSGI (Flask, Django)
from shipeasy.middleware import AnonIdMiddleware
app.wsgi_app = AnonIdMiddleware(app.wsgi_app)
# ASGI (FastAPI, Starlette)
from shipeasy.middleware import AnonIdASGIMiddleware
app.add_middleware(AnonIdASGIMiddleware)
shipeasy.Client(anon_user).get_flag("new_checkout") # buckets on the cookieTesting
Engine.for_testing() does zero network — no api*key, init/track are no-ops. Seed with override*\*and reset withclear_overrides. See Testing.
engine = shipeasy.Engine.for_testing()
engine.override_flag("new_checkout", True)
assert engine.get_flag("new_checkout", {"user_id": "u_123"}) is True
engine.clear_overrides()Errors & feedback
Report a handled exception with see() so it folds into the errors primitive with a one-sentence consequence — what feature broke and how it degraded. see() rides the same configure() boot (server key); there is no separate error SDK or second key.
try:
charge_card(order, prices)
except Exception as e:
shipeasy.see(e).causes_the("checkout").to("use cached prices").extras({"order_id": order.id})Use shipeasy.see_violation("large query") for a non-exception problem, and shipeasy.control_flow_exception(e).because("…") for expected control flow that should report nothing. Full grammar — consequence phrasing, control-flow exceptions, anti-patterns — is in Error reporting with see().
The in-app bug & feature report overlay is a standalone <script> tag you drop into your frontend — platform-agnostic, no server SDK required. See The devtools overlay.