> ## Documentation Index
> Fetch the complete documentation index at: https://docs.varianz.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> Instrument Python services with @vz.vpoint decorators: runtime setup, registration, schema resolution, sessions, and gRPC support.

The Python application SDK is the `varianz` package ([install](/installation#python)). Its test-side counterpart, `varianz-pytest`, is covered in [Testing → pytest](/testing/pytest).

## Runtime

```python theme={null}
from varianz import Varianz

vz = Varianz("http://localhost:50051")   # connect to a registry
vz = Varianz()                           # local-only mode (no registry)
```

The SDK activates only when [enabled](/reference/configuration) (`VARIANZ_ENABLED=true`); otherwise every API is a no-op and VPoints pass through.

## Defining VPoints

```python theme={null}
@vz.vpoint(name="pricing/calculate")
def calculate_price(amount: float, discount: float) -> float:
    return amount * (1 - discount)

vz.register(calculate_price)
```

* The `name=` keyword argument is required in practice — always set it explicitly.
* Decorating is not enough: the function, class, or instance must be **registered**. A decorated-but-unregistered method raises a clear error when called, to catch the common mistake.

### Registration methods

| Method              | Use when                                                                                                                             |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `vz.register(item)` | Full control — one function, class, or instance per call                                                                             |
| `@vz.component`     | Per-class — register at definition time                                                                                              |
| `vz.discover()`     | Bulk — scan the calling module and register every `@vpoint` function, `@function` context function, and class with `@vpoint` methods |

For classes, register the **instance** to preserve constructor state (`vz.register(PricingService(config))`); registering the class auto-instantiates via `cls()`. Don't pass unbound methods.

`vz.register(...)` blocks until the registry confirms the schema (up to 5 seconds), then proceeds regardless — Varianz fails open if the registry is unreachable.

## Schema resolution: annotate your types

**With type annotations (recommended):** the schema is built at decoration time, the VPD registers eagerly, and stages work from the first request.

Statically mapped types: `int`, `float`, `bool`, `str`, `None`, `list[T]`, `dict[K, V]`, `Optional[T]`, and `@dataclass` types. Proto messages resolve eagerly from their `DESCRIPTOR`.

**Without annotations (lazy fallback):** the schema is built from the first call's actual values, and registration happens then instead of at startup — tests trigger the VPoint once before inserting stages ([details](/testing/overview#lazy-vpoints)). Runtime-introspectable types include Pydantic models, attrs classes, and anything with `__annotations__`, `__slots__`, or `vars()`.

Prefer annotations; where you can't, wire [`varianz-scan`](/guides/build-integration#python) into the build.

## gRPC servicer methods

Python can instrument servicer methods directly — annotate the proto types and mark the gRPC context as opaque:

```python theme={null}
class PaymentServiceServicer(demo_pb2_grpc.PaymentServiceServicer):

    @vz.vpoint(name="payment/charge", opaque=["context"])
    def Charge(self, request: demo_pb2.ChargeRequest, context) -> demo_pb2.ChargeResponse:
        return demo_pb2.ChargeResponse(transaction_id=str(uuid.uuid4()))

vz.register(PaymentServiceServicer())
```

`opaque=["context"]` passes the `ServicerContext` through without exposing it to CEL. Proto field names, types, and nested messages come from the descriptor — no warmup needed. CEL overrides that construct proto structs (`ChargeResponse { transaction_id: "test" }`) are converted back to real proto messages automatically. Use the message's simple name in CEL, not the package-qualified one.

Set up the [server interceptor](/guides/propagate-sessions#python) once so sessions reach these handlers.

## Session API

Propagation is interceptor-driven; these are the primitives interceptors use:

```python theme={null}
Varianz.set_session(session_id)    # bind session to the current context (returns a contextvars token)
Varianz.get_current_session()      # read it (for outbound header injection)
Varianz.reset_session(token)       # restore the previous value
```

The session lives in a `contextvar`, so it follows `asyncio` tasks automatically.

## Context functions

```python theme={null}
from varianz import vpoint, function

class PricingService:
    @vpoint(name="pricing/calculate")
    def calculate(self, amount: float) -> float: ...

    @function(name="applyTax")           # ctx.applyTax(...) in CEL
    def apply_tax(self, price: float, rate: float) -> float:
        return price * (1.0 + rate)
```

Context functions require type annotations on every parameter and the return — they are never lazily resolved. Functions with no profile are available to all VPoints; class-scoped functions are always available to that class's VPoints.

## Platform notes

Wheels only (no sdist): CPython 3.10–3.15 and PyPy 3.11 on macOS and Linux (glibc and musl). In Docker, install inside the Linux image — or run tests from the host against the containerized service, since only the registry connection matters.
