Metadata-Version: 2.5
Name: recuut
Version: 0.5.0
Summary: Typed merchant integrations for the Recuut Merchant API
Project-URL: Homepage, https://recuut.com
Project-URL: Documentation, https://docs.recuut.com/sdks/python
Project-URL: Changelog, https://docs.recuut.com/sdks/python/changelog
Author: Recuut
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: <3.15,>=3.11
Requires-Dist: datamodel-code-generator==0.72.3
Requires-Dist: httpx<1,>=0.27
Requires-Dist: pydantic-core<3,>=2.27
Requires-Dist: pydantic<3,>=2.10
Requires-Dist: python-dateutil<3,>=2.9
Requires-Dist: typing-extensions<5,>=4.12
Provides-Extra: django
Requires-Dist: asgiref<4,>=3.8; extra == 'django'
Requires-Dist: django<7,>=5.2; extra == 'django'
Requires-Dist: sqlparse<0.7,>=0.6; extra == 'django'
Provides-Extra: fastapi
Requires-Dist: fastapi<1,>=0.115; extra == 'fastapi'
Requires-Dist: starlette<2,>=0.46; extra == 'fastapi'
Description-Content-Type: text/markdown

# Recuut Python SDK

The base package is a framework-neutral client for Recuut's Merchant API.

```bash
pip install recuut
export RECUUT_API_KEY="recuut_test_..."
```

```python
from recuut import Recuut
from recuut.merchant_api import TransactionFilter, TransactionStatus

with Recuut() as client:
    for transaction in client.transactions.list(
        max=100,
        page_size=50,
        filter=TransactionFilter(status=TransactionStatus.SUCCEEDED),
    ):
        print(transaction.id, transaction.status)

    for resource in client.resources.list(max=100, page_size=25):
        print(resource.id, resource.name)
```

The same namespaces are available asynchronously:

```python
from recuut import AsyncRecuut
async with AsyncRecuut(api_key="recuut_live_...") as client:
    page = await client.payers.list(max=100, page_size=25)
    async for payer in page:
        print(payer.identifier, payer.created_at)
```

The top-level resource groups come from Merchant OpenAPI tags, while method
names come from each operation ID: `client.payers.list()`,
`client.transactions.list()`, `client.transactions.get()`, and
`client.resources.list()`. A list call returns the first page immediately.
Its `items`, `count`, resolved `as_of`, and nullable `next` describe that HTTP
page. Iterate over the page to fetch later pages lazily; `max` bounds the total
items yielded by that iterator. Generated filter objects contain backend-owned
selection fields. `page_size` and `order` are sent to the backend, while `max`
only limits SDK iteration.

Every operation accepts `request_options` with per-request `headers` and
`timeout`. Use `with_raw_response` when successful HTTP metadata matters:

```python
with Recuut() as client:
    response = client.resources.with_raw_response.list(
        page_size=25,
        request_options={"headers": {"X-Trace-ID": "trace-123"}},
    )
    print(response.status_code, response.headers, response.data.items)
```

## Test and live isolation

The API key selects one environment. A `recuut_test_*` key sees only test
resources, contracts, payers, and transactions, while a `recuut_live_*` key
sees only live data. Equivalent resources may use the same immutable
integration key in both environments even though their `res_…` and revision
IDs differ. Client methods intentionally accept no organization or environment
parameter.

## Pull typed contracts

Configure the only directory the generator may manage:

```toml
[tool.recuut]
contracts-output-dir = "src/my_app/recuut_contracts"
```

```bash
recuut pull-contracts -v
```

The directory must be named `recuut_contracts`. Recuut creates one
`<integration_key>__generated.py` module per integration and re-exports its
public symbols from a generated `__init__.py`. Existing generated packages keep
their module and symbol names when upgraded. Generated packages expose
`INTEGRATION_KEY` and `GENERATED_INTEGRATIONS` while retaining `RESOURCE_ID` and
`GENERATED_RESOURCES`.

Before creating, updating, or deleting managed files, the command prints a plan
and asks for confirmation. `--yes` applies it non-interactively. `--check` never
writes or prompts and exits unsuccessfully only when generated source has
drifted. Files without Recuut's generated banner are never overwritten or
deleted. The schema digest describes immutable revision value shapes and does
not include mutable pricing policy.

`RECUUT_API_URL` defaults to `https://api.recuut.com`; set it, pass `--api-url`,
or configure `api-url` under `[tool.recuut]` for local or staging use.

## FastAPI

```bash
pip install "recuut[fastapi]"
```

```python
from fastapi import FastAPI
from pydantic import BaseModel
from recuut import recuut
from my_app.recuut_contracts import TextGenerationPayment, TextGenerationV1

app = FastAPI()

class GenerateRequest(BaseModel):
    prompt: str

class GenerateResponse(BaseModel):
    text: str

@app.post("/generate")
@recuut(TextGenerationV1)
async def generate(
    payload: GenerateRequest,
    payment: TextGenerationPayment,
) -> GenerateResponse:
    payment.report(input_tokens=len(payload.prompt.split()))
    return GenerateResponse(text=payload.prompt.upper())
```

The decorator hides and injects `payment`, validates the request before access
negotiation, relays a 402 challenge unchanged, completes a paid transaction,
then attaches `PAYMENT-RESPONSE` and releases the protected result. Sync handlers
run through FastAPI's thread pool. A returned `4xx` or `5xx` fails the
transaction and reports only status, duration, media type, and response size
when available. Response bodies and headers are never sent to recuut. Streaming
responses are rejected in v1.

## Django

```bash
pip install "recuut[django]"
```

```python
from django.http import HttpRequest
from pydantic import BaseModel
from recuut.django import recuut
from my_app.recuut_contracts import TextGenerationPayment, TextGenerationV1

class GenerateRequest(BaseModel):
    prompt: str

class GenerateResponse(BaseModel):
    text: str

@recuut(TextGenerationV1)
async def generate(
    request: HttpRequest,
    payload: GenerateRequest,
    payment: TextGenerationPayment,
) -> GenerateResponse:
    payment.report(input_tokens=len(payload.prompt.split()))
    return GenerateResponse(text=payload.prompt.upper())
```

Django uses the same negotiation and completion client. It validates Pydantic
payloads and responses, runs sync views through Django's thread-sensitive worker
path, relays challenges, reports only safe response metadata, and rejects
streaming before protected content is read.

## Runtime and raw contracts

Framework integrations call the generated `payments.access` and
`transactions.complete` façade methods. Generated payments validate exact
value keys and decimal constraints before completion. For a revision that has
not been pulled into your application, use the explicit escape hatch:

```python
from recuut import RawPayment, RawRevision, recuut

@recuut(RawRevision("acr_00000000000070008000000000000000"))
async def raw_endpoint(payment: RawPayment) -> dict[str, bool]:
    payment.report({"items": 1})
    return {"ok": True}
```

## Errors

Configuration problems raise `RecuutConfigurationError`, and network failures
raise `RecuutTransportError`. Documented non-successful HTTP responses raise a
generated `RecuutResponseError`; future statuses, undocumented error codes, and
malformed payloads raise `RecuutUnexpectedResponseError`. Response errors retain
the exact status, body, and headers so integrations can preserve protocol
challenges.

## Runnable examples

Each example is an independently locked consumer project kept beside the SDK:

- [Plain Python](examples/plain/README.md)
- [FastAPI](examples/fastapi/README.md)
- [Django](examples/django/README.md)
