Back to Articles

Inside oxp-python: A Stainless-Generated SDK That Proves Auto-Generation Has Grown Up

[ View on GitHub ]

Inside oxp-python: A Stainless-Generated SDK That Proves Auto-Generation Has Grown Up

Hook

Most developers approach auto-generated client libraries with skepticism, but the oxp-python SDK demonstrates that modern code generation can produce idiomatic Python that feels hand-crafted.

Context

The Open eXecution Protocol (OXP) provides a standardized REST API for tool execution workflows, but consuming any REST API from Python typically means choosing between hand-rolling HTTP calls with requests or httpx, or relying on auto-generated clients. The oxp-python library takes a third path: it’s built using Stainless, a code generation platform that aims to produce clients that feel like hand-written libraries.

This approach addresses a real tension in API client development. Hand-written clients require ongoing maintenance as APIs evolve, with developers manually updating type definitions and method signatures. The oxp-python approach automates the tedious parts while maintaining Python conventions, complete with TypedDicts for request parameters, Pydantic models for responses, and first-class async support.

Technical Insight

The library exposes two client classes—Oxp for synchronous operations and AsyncOxp for async workflows. This dual interface design means you can write an async web service handler or a simple script with the same mental model. Installation is straightforward via PyPI, and authentication uses bearer tokens sourced from environment variables by default:

import os
from oxp import Oxp

client = Oxp(
    bearer_token=os.environ.get("OXP_API_KEY"),
)

tool = client.tools.list()
print(tool.items)

What makes this interesting architecturally is the type system. Request parameters use TypedDicts, Python’s native structural typing mechanism that provides IDE autocomplete without requiring class instantiation. When calling a tool with nested context parameters, you pass plain dictionaries that are validated against typed specifications:

response = client.tools.call(
    request={
        "tool_id": "sqFnKL1N_jr_.U0_2_jv__a",
        "call_id": "call_id",
        "context": {
            "authorization": [
                {
                    "id": "id",
                    "token": "token",
                }
            ],
            "secrets": [
                {
                    "id": "id",
                    "value": "value",
                }
            ],
            "user_id": "user_id",
        },
        "input": {"foo": "bar"},
        "trace_id": "trace_id",
    },
)

Responses come back as Pydantic models, which means you get both runtime validation and convenient serialization methods like to_json() and to_dict(). This hybrid approach—TypedDicts for inputs, Pydantic for outputs—strikes a balance between flexibility and safety.

Under the hood, oxp-python wraps httpx, inheriting its robust connection pooling. The library adds production-oriented features that you’d otherwise implement yourself: automatic retries with exponential backoff (certain errors are retried 2 times by default, including connection errors, 408 Request Timeout, 409 Conflict, 429 Rate Limit, and >=500 Internal errors), configurable timeouts (1 minute default), and a comprehensive exception hierarchy. Error handling maps HTTP status codes to specific exception types—RateLimitError for 429s, AuthenticationError for 401s, and so on—making it straightforward to implement appropriate retry logic:

import oxp

try:
    client.tools.list()
except oxp.APIConnectionError as e:
    print("The server could not be reached")
    print(e.__cause__)  # underlying httpx exception
except oxp.RateLimitError as e:
    print("A 429 status code was received; we should back off a bit.")
except oxp.APIStatusError as e:
    print(e.status_code)
    print(e.response)

The retry and timeout systems are configurable both globally and per-request using with_options(), allowing you to tighten timeouts for health checks while relaxing them for long-running operations. The library also supports standard library logging via the OXP_LOG environment variable, integrating cleanly into existing observability stacks.

What Stainless generation provides here is consistency: every method follows identical patterns for error handling, retries, and typing. The README notes that functionality between synchronous and asynchronous clients is identical, ensuring a uniform experience regardless of which you choose.

Gotcha

The auto-generation approach that makes oxp-python maintainable also constrains customization. If you need to add middleware, implement custom caching logic, or modify request/response processing beyond what the generated client exposes, you’re working against the grain. The library provides raw response access for custom header inspection, but extending behavior generally means wrapping the client rather than modifying it.

The project’s 17 GitHub stars reflect its niche scope—this is a client for a specific protocol, not a general-purpose tool. That limited adoption means you’ll be more reliant on the official documentation than community resources. The documentation references external resources at openexecprotocol.org and includes a generated api.md file. If you’re evaluating OXP itself, understand that this client’s maturity and the protocol’s maturity are separate concerns.

Verdict

Use oxp-python if you’re building Python applications that interact with OXP services and value type safety, async support, and production-ready retry/timeout logic out of the box. The Stainless-generated code follows idiomatic Python patterns, making this feel like a well-crafted library despite its automated origins. It’s particularly well-suited for async web frameworks like FastAPI or async task processors where the AsyncOxp client integrates naturally. Skip it if you need extensive customization beyond standard HTTP client behavior, prefer full control over your HTTP layer, or are just exploring OXP and might pivot away—in those cases, using httpx directly gives you flexibility at the cost of implementing retries and type definitions yourself. Also skip if the OXP protocol itself doesn’t match your execution model; this client can’t compensate for architectural misalignment with the underlying service.

// QUOTABLE

Most developers approach auto-generated client libraries with skepticism, but the oxp-python SDK demonstrates that modern code generation can produce idiomatic Python that feels hand-crafted.

[ Tweet This ]
// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/openexecprotocol-oxp-python.svg)](https://starlog.is/api/badge-click/developer-tools/openexecprotocol-oxp-python)