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 names initially derive from your API’s title. When that name is the import name of an established package for the same API, such as
github(PyGithub) orstripe, the default becomesgithub_clientso installing both does not break either. Set the repository Delivery’s PyPI package name under Targets → Python SDK, usingpackage_namein the API. Install withpip install .from the package directory, or from PyPI after publishing. Check the generated README for the normalized import name. pyproject.tomldeclaresdependencies = []andrequires-python >= 3.11. The runtime uses only the standard library.- The package ships
py.typed, so type checkers see everyTypedDictandLiteral. tests/holds aunittestsuite that calls one operation per resource against a local stub server, checks that a 404 raisesNotFoundError, and walks two pages when the API paginates. Runpython -m unittestin the package directory. The tests use only the standard library and are not installed with the package.
Idioms
- Raise, don't return. Generated exceptions derive from
SdkErrorand exposecode,status,request_id,body, and a message with the next step. CatchApiErrorfor API responses orTransportErrorwhen no response arrives. See Results and errors. - Keyword arguments for bodies. When an operation's request body is an object (JSON, form-encoded, or multipart, inline or a named schema, including
allOfcompositions), its fields become keyword arguments:client.shipments.create(recipient="Ada Lovelace", service="ground"). Each argument is the field's snake_case name. A name that is a Python keyword, or that a path, query, or header parameter,request_options, orselectalready uses, takes a trailing underscore: afromfield isfrom_=, and anownerbody field beside anownerpath parameter isowner_=. A body that is an array, a union, binary, or plain text stays onebody=argument typed accordingly (List[ShipmentWrite],str). - Dicts, typed. Responses are the parsed JSON, typed as
TypedDicts. You readshipment["id"], notshipment.id. There is no conversion layer between you and the wire. - Directional models stay honest. A component whose request and response contracts differ becomes
ShipmentWriteandShipmentRead; identical contracts keep the cleanShipmentname. Response enums accept future strings and discriminator responses retain an unknownDict[str, Any]fallback, while request types remain closed. - Generators for pages.
for shipment in client.shipments.list():walks every page.client.shipments.list_page()returns one raw envelope. request_optionsper call. A dict withtimeout,max_retries, andheaders.
Choose credentials for each endpoint
Pass credentials keyed by the names in your Spec's components.securitySchemes. Bearer tokens and API keys accept a string or a token callback. Basic auth accepts a (username, password) tuple. The same configuration works with the synchronous and asynchronous clients, and the asynchronous client also accepts async def callbacks, awaited on the running event loop. Passing an async def callback to the synchronous client raises TypeError when a request needs it.
The client selects the first complete permitted combination for each endpoint. When an endpoint requires two schemes together, both must be configured. An incomplete combination sends neither credential; a public endpoint receives no generated credentials. Only selected callbacks run, before each attempt. Your API enforces token validity and permissions.
Named credentials take precedence over matching convenience options and environment variables. Use names to distinguish multiple credentials of the same type. Unknown names raise ValueError during client construction. Explicit default_headers, request headers and on_request hooks remain available for application-managed authentication.
When a request sent with a token callback or a client_credentials token receives 401, the client discards the cached client-credentials token, resolves the credential again, and resends the request once. The resend does not count against max_retries; a second 401 raises. A static string or tuple is never resent. An exception raised by a callback propagates unchanged and the request is not retried.
Use other credentials
client.with_credentials(...) returns a new client of the same class that sends different credentials, for example to act for one of your users with their token:
user_client = client.with_credentials(bearer_token=user_token)
shipment = user_client.shipments.get("shp_123")It takes the same credential arguments as the constructor, such as bearer_token, api_key, client_credentials, and credentials, and replaces every credential. Nothing is inherited from the original client, and credential environment variables such as PARCEL_TOKEN are not read. The base URL, transport and pooled connections, timeouts, retries, hooks, and default headers are shared. Closing the new client leaves the shared connections open; close the original client when you are done with both.
Environment variables
The client reads these when the matching option is not passed:
| Variable | Sets |
|---|---|
PARCEL_TOKEN | bearer_token |
PARCEL_BASE_URL | base_url |
PARCEL_WEBHOOK_KEY | webhook_key |
PARCEL_DEBUG=1 | debug |
A single API key is api_key= and PARCEL_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 PARCEL_USERNAME and PARCEL_PASSWORD. The prefix comes from your package name. Credential variables use the same names as the CLI, which can follow the API's convention (such as PARCEL_API_KEY for a bearer scheme named for an API key) or your auth.credential_variables setting; see Auth and configuration. The README lists the names your package reads.
Transport
Pass transport= to replace the default keep-alive connection pool. 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
AsyncParcelClient takes the same options and has the same methods, awaitable: await client.shipments.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, and a list of them sends one part per file. The other fields of the body travel alongside as form parts: a dict is one JSON part (application/json), and a type the spec's encoding names for a field is used for that part. 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. The default transport follows up to five redirects: GET and HEAD follow every redirect, 303 turns other methods into GET, 307 and 308 replay the request, and 301 or 302 responses to other methods are returned as they are. A redirect to a different scheme, host, or port drops credential headers, including Authorization, Cookie, and your spec's API-key headers.