Shipeasy
SDKsReferencePython

Error reporting — `see()`

The Python SDK ships the see() structured-error surface. It reports a caught, handled error (or a non-exception "violation") to Shipeasy as a…

Generated from the SDK's own /docs/ — also served raw at https://shipeasy-ai.github.io/sdk-python/pages/error-reporting.md.

The Python SDK ships the see() structured-error surface. It reports a caught, handled error (or a non-exception "violation") to Shipeasy as a fire-and-forget event — without re-raising. Reporting must never raise into your code.

Reporting a handled exception

see(problem) returns a chainable builder. The terminal is .to(outcome) — it builds the event and fires it:

from shipeasy import see

try:
    charge(order)
except PaymentError as e:
    see(e).causes_the("checkout").to("use the backup processor")
    fallback_charge(order)

Where extras go in the chain

.causes_the(subject) and .to(outcome) are two halves of one sentence and must stay adjacent, so fold the extras into the terminal:

# PREFERRED — the consequence reads as one sentence:
see(e).causes_the("checkout").to("use cached prices", {"order_id": oid})

.to fires the report synchronously, so a stray .extras chained after .to is ignored with a warning — it never raises into your except block (.to returns the chain), but the extras are dropped:

# WRONG — extras silently lost:
see(e).causes_the("checkout").to("use cached prices").extras({"order_id": oid})

# WRONG — extras wedged between the subject and the outcome. You read
# "checkout … order_id … use cached prices" and lose the consequence.
see(e).causes_the("checkout").extras({"order_id": oid}).to("use cached prices")

When the context already exists above the except block, prefer add_extras over the inline form — it keeps the catch site a clean one-liner.

Use the package-level see() — it reports against the engine you set up with configure(). No object to construct or pass around.

Attach context from anywhere: add_extras

To attach context without threading it into the except block, buffer it earlier in the request with shipeasy.add_extras. Every see() report that fires later in the same request merges it in:

import shipeasy

# from any layer, early in the request (mapping and/or keyword args)
shipeasy.add_extras(order_id=order.id, tenant=tenant.slug)

# ...later, deep in a service...
try:
    charge(order)
except PaymentError as e:
    shipeasy.see(e).causes_the("checkout").to("use cached prices")
    # report carries order_id + tenant automatically

The buffer is backed by a ContextVar, so concurrent requests and async tasks never bleed into each other, and it merges into every report in the request (not just the first). A chained .extras / .to extra of the same key overrides an ambient one. The WSGI / ASGI / Django middleware clears the buffer at the end of each request; in a background job or script call shipeasy.clear_extras() when a unit of work ends.

Non-exception violations

from shipeasy import see_violation, Violation

see_violation("missing_invoice").causes_the("billing").to("skip the dunning email")

Control-flow exceptions (report NOTHING)

control_flow_exception(e).because("...") marks an exception expected and transmits nothing — .extras() is stored for local debugging only:

from shipeasy import control_flow_exception

try:
    parse(token)
except StopIteration as e:
    control_flow_exception(e).because("end of stream is expected")

Spam guard

The SDK carries a per-process limiter, so repeated reports of the same issue collapse to a single send.

Was this page helpful?
Updated July 25, 2026

On this page