Metadata-Version: 2.5
Name: labelbox-recursion-sdk
Version: 0.0.27
Summary: Python SDK for the Recursion RL Platform API
Requires-Python: >=3.11
Requires-Dist: attrs>=22.2
Requires-Dist: httpx<0.29,>=0.23
Requires-Dist: python-dateutil>=2.8
Description-Content-Type: text/markdown

# recursion-sdk (Python)

The rl-gym Python SDK, **generated from the same backend OpenAPI snapshot, and
the same `@SdkRoute` opt-in set, as [`@labelbox/recursion-sdk`](../sdk-ts/README.md)**
with [openapi-python-client](https://github.com/openapi-generators/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.

```python
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`:

```ts
// @labelbox/recursion-sdk
const job = await rl.synthesizers.create({
  environmentId,
  body: { name: 'Variant generator', systemPrompt: '…', runConfigVersionId },
});
```

```python
# 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`, not `str`.
- 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.parsed` is `None` and `.content` carries the bytes.

## Install

Not published to a package registry yet — no free Python index has been chosen,
and the TypeScript SDK's private Artifact Registry release is npm-specific.
Build and install the wheel locally:

```sh
yarn dx python-sdk:generate                    # pip venv, no Docker
cd packages/sdk-python
python3 -m build                               # or `uv build`, if you have it
pip install dist/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 dx ci:python-sdk`
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.

## Regenerating

```sh
# Regenerate the client from the committed spec snapshot (packages/sdk-ts/openapi.json).
yarn dx python-sdk:generate

# Everything CI runs: regenerate, build the wheel, install it into a clean
# virtualenv, and run the test suite against that installed distribution.
yarn dx ci:python-sdk
```

Refreshing the snapshot itself is the TypeScript SDK's job
(`yarn dx sdk:generate --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 dx python-sdk:generate` 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.
