A Python SDK shipped from OpenAPI in 2026 should be a typed pip install away from working. That means typing-aware, async-aware, and consistent enough that customers can read the method list and know how it'll behave. Three paths get you there: write it by hand, generate locally, or use a hosted pipeline. Here's the practical version of each.
Spec hygiene first
Same rules as TypeScript: every operation has an operationId (it becomes the method name), schemas live in components/schemas and are referenced (not inlined), every operation has an example, security schemes are defined and applied. A generator can't recover from a bad spec.
The Python-specific note: operationId becomes snake_case in idiomatic Python output. createUser → create_user. Most generators handle this. Verify your generator does or your SDK feels foreign to Python developers.
See OpenAPI best practices for SDK-friendly specs for the full checklist.
Path 1: hand-written Python client
Best when you have ten endpoints, you control both API and SDK, and you want zero generator dependency.
from dataclasses import dataclass
from typing import Optional
import httpx
@dataclass
class ClientOptions:
api_key: str
base_url: str = "https://api.example.com"
class APIError(Exception):
def __init__(self, status: int, body: str):
self.status = status
self.body = body
super().__init__(f"API {status}: {body}")
class Client:
def __init__(self, opts: ClientOptions):
self._opts = opts
self._http = httpx.Client(
base_url=opts.base_url,
headers={"Authorization": f"Bearer {opts.api_key}"},
timeout=30,
)
def list_users(self) -> list[dict]:
r = self._http.get("/users")
if not r.is_success:
raise APIError(r.status_code, r.text)
return r.json()
Fine for a small API. Doesn't scale — adding 50 endpoints turns into a maintenance burden, and your spec/SDK drift the moment the spec changes.
Path 2: run a generator locally
Two real options:
openapi-python-client— modern, async-aware, produces a clean typed client with type-annotateddataclassmodels (older versions usedattrs; current releases moved to dataclasses). Most Python-native option in 2026.- OpenAPI Generator (
pythontemplate) — full codegen with sync + async variants. Heavier, more configurable, slower to adapt to OpenAPI 3.1 specifics.
# Approach A — modern python-native
pipx install openapi-python-client
openapi-python-client generate --path ./openapi.yaml
# Approach B — OpenAPI Generator
npx @openapitools/openapi-generator-cli generate \
-i ./openapi.yaml \
-g python \
-o ./generated
Both work. Approach A produces something a Python developer reads and recognises immediately. Approach B produces something a Java developer would write — works fine, feels less native.
Path 3: hosted pipeline
Same story as TypeScript: upload spec, get an SDK, preview, publish. Bloom, Stainless, Speakeasy all do this.
With Bloom:
- Upload OpenAPI at signup. Dashboard shows parsed structure and any spec issues.
- Preview the generated Python SDK — method names, type definitions, async support, error classes, examples, all visible before any PyPI push.
- Compatibility report vs previously published SDK — breaking change detection on the public surface.
- Publish to PyPI from the dashboard or hand off the package for you to publish yourself.
Pricing: free for 30 days, no credit card. Enough to generate, preview, publish, and see if the output meets your bar before any monthly cost.
What "good" Python SDK output looks like
- Method names match
operationIdinsnake_case. - Types are explicit: dataclasses or Pydantic v2 models, with
from __future__ import annotationsso they work in older runtimes. - Errors are typed:
APIErrorwithstatus,code,message,body. - A single
Client(options=...)constructor, plus per-resource accessors (client.users.list()). - Async support: every method has both
list_users()andawait list_users_async(), or the whole client is async-only withAsyncClientexposed separately. - Pagination has helpers, not raw cursor handling.
- The package ships type hints visible to mypy/pyright — include
py.typedin the package.
If the output is missing any of these, expect customer issues. Python developers in 2026 expect typing, and a typed SDK without py.typed looks unmaintained.
A note on async
OpenAPI doesn't model sync vs async — it's a transport concern. Most modern Python clients ship both: a Client that wraps httpx.Client (sync) and an AsyncClient that wraps httpx.AsyncClient (async). Some teams ship async-only to avoid maintaining two surfaces. Pick whichever matches your audience; web service backends are usually async, scripts and notebooks are usually sync.
openapi-python-client generates both by default. OpenAPI Generator's python template generates both. Hand-rolled SDKs usually ship one and add the other later.
Next: publishing and breaking-change detection
Two follow-ups worth reading:
- SDK compatibility reports, explained — how to detect breaking changes between SDK versions before publishing.
- Breaking changes in OpenAPI — what counts as breaking on the spec side vs the SDK side.
Want spec → preview → PyPI as one workflow?
Try Bloom free for 30 days — upload your OpenAPI spec, preview the generated Python SDK in the dashboard, see a compatibility report against your last published version, and publish from the same workflow. Completely free for 30 days. No credit card required.