recursion-sdk (Python)
The Recursion Python SDK, generated from the same backend OpenAPI snapshot, and
the same @SdkRoute opt-in set, as @labelbox/recursion-sdk
with openapi-python-client
— free, open source, and pinned by version. The only hand-written code is a thin
auth constructor (src/recursion/__init__.py); everything under
src/recursion_sdk/ is generated and gitignored.
The two SDKs share a surface, not just a set of operations. @SdkRoute('synthesizers', 'create')
on a backend handler produces rl.synthesizers.create(...) in both clients,
from that one declaration.
import asyncio
import os
from recursion import create_recursion_client
async def main() -> None:
rl = create_recursion_client(api_key=os.environ["LABELBOX_API_KEY"])
environment = await rl.environments.get(environment_id)
print(environment.name)
asyncio.run(main())
Surface
Namespaced exactly like the TypeScript SDK, because both are driven by the same
x-sdk-path:
// @labelbox/recursion-sdk
const job = await rl.synthesizers.create({
environmentId,
body: { name: 'Variant generator', systemPrompt: '…', runConfigVersionId },
});
# recursion-sdk
body = CreateSynthesizerJobBodyDto(name="Variant generator", system_prompt="…", ...)
job = await rl.synthesizers.create(environment_id, body=body)
Path parameters are positional, the body and query parameters are keywords. Every
call is async. Non-2xx responses raise RecursionApiError, carrying
.status_code, .parsed (the typed error body, when the operation documents
one) and .content — matching the TypeScript client's throwOnError: true.
The namespace facade (recursion_sdk/facade.py) is generated from
x-sdk-path, exactly as hey-api's nesting() callback drives the TypeScript
side, so the two clients cannot drift into different namespacing. The generated
per-endpoint modules underneath it (recursion_sdk.api.<namespace>.<method>)
remain available directly, and expose sync/asyncio variants plus
*_detailed forms that return the full response instead of raising.
Opt-in, declared at the source. An operation is in this SDK iff its backend
handler carries @SdkRoute(...)
(apps/recursion/api/src/common/sdk-route.decorator.ts), read through the same
packages/sdk-ts/src/nesting.ts the TypeScript SDK uses, so the two clients
cannot cover different operations.
Notes from real use
- ID fields deserialize to
uuid.UUID, notstr. - Path parameters are annotated
UUIDtoo. Passing astrworks at runtime — and the composed docs examples do — but a caller running mypy against the published wheel will wantUUID(...). - Required fields are enforced at model construction, so a partial body fails fast rather than at the server.
- Some operations document an error status with a description and no schema; for
those
RecursionApiError.parsedisNoneand.contentcarries the bytes.
Install
Published to PyPI as labelbox-recursion-sdk (the import name stays
recursion), so a consumer needs nothing from this repo:
pip install labelbox-recursion-sdk
release-python-sdk.yml publishes it through PyPI trusted publishing —
automatically on a push to main that changes the wheel's inputs, and manually
via workflow_dispatch for an explicit version or a dry run. The version is
resolved from the highest python-sdk-v* git tag and recorded only as a new
tag; the release never commits to the branch, which is why pyproject.toml
carries a 0.0.0 placeholder. The python-sdk-v* tags are the record of what
has shipped — git tag --list 'python-sdk-v*' | sort -V | tail -1 — so no
version is pinned here to go stale.
To install an unreleased working copy instead, build the wheel locally:
yarn generate python-sdk # pip venv, no Docker
cd packages/sdk-python
python3 -m build # or `uv build`, if you have it
pip install dist/labelbox_recursion_sdk-*.whl
Requires Python 3.11+ (the generated client imports typing.Self).
Tooling: pip + hatchling, no lockfile — matching mcp_server, the repo's only
other Python package. uv is not a repo dependency, and yarn ci check python-sdk:all
deliberately uses stdlib venv + pip so the gate needs nothing beyond the
interpreter. Both work locally; uv is just faster. There is no uv.lock or
requirements.txt on purpose: this is a library, so it declares dependency
ranges and lets the consuming application own the resolution.
The committed call surface
python-surface.json is the one generated artifact here that is committed.
It records, per operationId, how the generated client is actually called: the
facade call path, the positional path parameters in signature order, the keyword
parameters, the request body's model class and whether that class is
from_dict-constructible, every alternative of a union-typed body with the wire
field names that discriminate them, and whether the operation's parsed response
is a plain list (which has no .to_dict()).
It exists because the docs pipeline composes Python examples:
yarn generate recipes runs at every build, in jobs with no Python
interpreter, so it cannot introspect the generated tree. Reading a committed map
keeps recipe generation JavaScript-only.
Composed, not yet displayed: the examples ship in the CLI manifest and are
printed by recursion recipes <id> --format py, but the web docs panel still renders
only the TypeScript, CLI, and cURL tabs. Adding the Python tab is a deliberate
follow-up. Whoever does it must render a recipe's setup and main buckets as
one block — the Python surface wraps both in a single async def main(): so the
concatenation a reader copies is runnable, which means main alone is an
indented fragment.
It is read out of the generated signatures, never derived from the spec's
parameter names — the same rule endpointModule follows for module names. The
generator's casing is not guessable: jobV2Id becomes job_v2_id, which a
hand-written camelCase transform would plausibly render job_v_2_id, producing
examples that agree with our guess instead of with the client. yarn dx ci:python-sdk fails if the committed map is untracked or if regeneration
changes it.
Regenerating
# Regenerate the client from the committed spec snapshot (packages/sdk-ts/openapi.json).
yarn generate python-sdk
# Everything CI runs: regenerate, build the wheel, install it into a clean
# virtualenv, and run the test suite against that installed distribution.
yarn ci check python-sdk:all
Refreshing the snapshot itself is the TypeScript SDK's job
(yarn generate sdk --refresh-spec); this package only ever reads it.
The generator-private projection
The committed snapshot is a mixed OAS 3.0/3.1 document: Nest + nestjs-zod
emit 3.0 (nullable: true, boolean exclusiveMinimum) while the merged Managed
Agents Go schemas are already JSON Schema 2020-12 (type: "null",
prefixItems). Relabelling one as 3.1 would silently discard the 3.0 halves'
meaning, since nullable is not a 3.1 keyword.
So yarn generate python-sdk writes a generator-private projection
(openapi.generated.json, gitignored) that converts rather than relabels, and
feeds the generator that. It never touches the snapshot, the TypeScript SDK, or
the backend. Each transform, and why it preserves semantics, is documented in
tools/dx/src/commands/python-sdk.ts and pinned by
tools/dx/src/commands/python-sdk.test.ts.
The projection is not cosmetic — it is measurably what makes generation complete. From the unprojected spec the generator emits 342/343 operations with parse failures on the recursive-union endpoints; from the projection, 343/343 with one allowlisted warning.
Generation then fails on any generator diagnostic that is not explicitly
allowlisted, and on any opted-in operation missing from either the generated
tree or the facade. The generator exits 0 while printing "Client was generated,
but some pieces may be missing", so neither is implied by a successful run. The
one allowlisted warning is an application/x-yaml alternative request encoding
on a single Managed Agents operation; the application/json variant every other
operation uses is generated normally. The allowlist is keyed by operation and
signature, so the same warning elsewhere still fails.
Not a Yarn workspace
packages/AGENTS.md requires every new package under packages/ to be
registered in the root workspaces array, tools/dx/src/context.ts,
tools/dx/src/commands/workspace.ts, and the backend Dockerfile. None of that
applies here: those steps exist so Node consumers can require.resolve() the
package's dist/ and so yarn install --immutable sees every manifest. This
package has no package.json, is not in the Node dependency graph, and is never
installed into the backend image. Registration is keyed on package.json
discovery (tools/dx/src/workspace-registration.test.ts), so there is nothing to
register.
Release files for labelbox-recursion-sdk 0.0.94
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| labelbox_recursion_sdk-0.0.94.tar.gz | 1.1 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| labelbox_recursion_sdk-0.0.94-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 4.0 MB
Release files / labelbox_recursion_sdk-0.0.94.tar.gz
| Download URL | labelbox_recursion_sdk-0.0.94.tar.gz |
|---|---|
| Size | 1.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
03d55454a8f3dcd06b6ce30f8ec518f87f1d472ad50fe5504c78cc959f54c9b9
|
|
BLAKE2b-256 checksum How to use checksums |
9ba8c1920e8c28b7f9192ecbaf6fd51526799d37ab37bb27f5b5737d1818c60b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency logRelease files / labelbox_recursion_sdk-0.0.94-py3-none-any.whl
| Download URL | labelbox_recursion_sdk-0.0.94-py3-none-any.whl |
|---|---|
| Size | 3.0 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c5c9a4c996665e58a1ec134e71188a8444e4ad3ee10e9a9d6b7769e04fd3c3e1
|
|
BLAKE2b-256 checksum How to use checksums |
29c692f48a61328afb0457bfb9067de1acc3d867b387c1efd50c1936af8ca13d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency log