Python
Generate synchronous and asynchronous Python clients for your API with typed models and errors.
Typeship generates synchronous and asynchronous Python clients for your API as one focused package with no runtime dependencies. This page covers Python-specific behavior; shared client behavior is on the SDK overview.
CLI and MCP packages are generated and released separately.
Package
- The distribution and import name derive from your API's title. The Acme API produces
acme, installed withpip install .from the package directory or from PyPI once you publish it. Set a different name per project in the console under package names. pyproject.tomldeclaresdependencies = []andrequires-python >= 3.11. The runtime isurllibonly.- The package ships
py.typed, so type checkers see everyTypedDictandLiteral.
Idioms
- Raise, don't return. Every failure is an exception under
TypeshipError. See Results and errors. - Keyword arguments for bodies. When an operation's request body is an inline object (JSON or form-encoded), its fields become keyword arguments:
client.accounts.create(name="Ops", currency="usd"). When the body is a named schema, an array, or plain text, it stays onebody=argument typed accordingly (AccountWrite,List[AccountWrite],str). If a body field is spelled like a path or query parameter, the whole body steps aside intobody=rather than producing a duplicate argument. - Dicts, typed. Responses are the parsed JSON, typed as
TypedDicts. You readaccount["id"], notaccount.id. There is no conversion layer between you and the wire. - Directional models stay honest. A component whose request and response contracts differ becomes
AccountWriteandAccountRead; identical contracts keep the cleanAccountname. Response enums accept future strings and discriminator responses retain an unknownDict[str, Any]fallback, while request types remain closed. - Generators for pages.
for account in client.accounts.list():walks every page.client.accounts.list_page()returns one raw envelope. request_optionsper call. A dict withtimeout,max_retries, andheaders.
Environment variables
The client reads these when the matching option is not passed:
| Variable | Sets |
|---|---|
ACME_TOKEN | bearer_token |
ACME_BASE_URL | base_url |
ACME_WEBHOOK_KEY | webhook_key |
ACME_DEBUG=1 | debug |
A single API key is api_key= and ACME_API_KEY, the same spelling as the TypeScript SDK and the CLI; several keys get one keyword and variable each, named after the header or parameter. Basic auth reads ACME_USERNAME and ACME_PASSWORD. The prefix comes from your package name.
Transport
Pass transport= to replace urllib. A transport is a callable that takes (method, url, headers, body_bytes, timeout) and returns (status, headers, body_bytes). Use it to route through a proxy, a recording layer, or a test double. Streaming endpoints read the response incrementally and go straight to urllib, so they bypass a custom transport.
Webhooks
client.webhooks.unwrap(payload, headers) verifies and parses. unwrap_unsafe parses only. Module-level verify_webhook and sign_webhook are exported for handlers that do not hold a client. Events are TypedDicts discriminated by their type field. WEBHOOK_NAMES and WEBHOOK_SAMPLES list every declared event with a sample payload.
Async
AsyncAcmeClient takes the same options and has the same methods, awaitable: await client.accounts.get(...), and async for over paginated methods and streams. Requests run on the event loop's default executor, so nothing blocks the loop and there is still nothing to install. Both clients are context managers (with / async with) and have close() / aclose().
The zero-dependency tradeoff is explicit: cancelling a coroutine stops waiting for its result, but it cannot interrupt a synchronous socket call already running in the executor. timeout bounds each socket attempt; it is not one wall-clock deadline across retries. The generated async client is therefore not the right transport when immediate in-flight socket cancellation is a hard requirement.
Uploads
A multipart/form-data field is a keyword argument that accepts raw bytes, an open binary file (its name becomes the filename), or a (filename, data, content_type) tuple; the other fields of the body travel alongside as form parts. A raw binary body (application/octet-stream, image/png, ...) is body= with bytes or a file object, sent under the spec's content type.
GraphQL
A GraphQL schema generates one method per query and mutation field, keyed under client.query and client.mutation. Arguments are keyword arguments and become variables. Every scalar field to depth 2 is selected by default, with a fragment per concrete type for unions and interfaces, and select="{ id name }" overrides that with a raw selection set on methods that return an object. Connection fields paginate like everything else. Errors in a 200 raise GraphQLRequestError (an ApiError) with the raw errors list.
Connections
The default transport pools keep-alive connections per client on http.client, one per scheme/host/port, honoring HTTP_PROXY/HTTPS_PROXY/NO_PROXY. A request that fails on a reused connection before any response arrived is retried once on a fresh one (the server closed an idle keep-alive, the same case Go's net/http retries), so pooling never turns into spurious transport errors. Redirects follow the rules urllib applied.