Metadata-Version: 2.5
Name: recuut
Version: 0.11.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: anyio<5,>=4; 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>=0.11.0"
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
import asyncio

from recuut import AsyncRecuut

async def main() -> None:
    async with AsyncRecuut() as client:
        page = await client.payers.list(max=100, page_size=25)
        async for payer in page:
            print(payer.identifier, payer.created_at)

asyncio.run(main())
```

Client methods include: `client.payers.list()`,
`client.transactions.list()`, `client.transactions.get()`,
`client.resources.list()`, `client.contracts.list()`, and
`client.payments.access()`. 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)
```

## Receipts

`ReceiptDocument` exposes fields directly, such as `receipt.id` and
`receipt.payment`. The SDK validates the receipt against its transaction.

## 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 share an immutable resource
key and public `rvk_…` revision key. Their `res_…` Resource IDs and internal
revision rows remain separate. 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 = "recuut_contracts"
```

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

The directory must be named `recuut_contracts`. recuut creates one
`<resource_key>__generated.py` module per resource 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
`RESOURCE_KEY` and `GENERATED_RESOURCES`. They bind revisions by the
public `rvk_…` ID without embedding an environment-specific Resource ID.

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 when generated source has
drifted. Authentication, connection, or schema errors also fail the command.
Files without recuut's generated banner are never overwritten or
deleted. The check covers every published revision visible to the key. The
schema digest describes immutable revision value shapes and does not include
environment-specific Resource IDs or 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.

Input and output variable values must be non-negative integers, including zero.
Use Python integers or canonical digit strings with at most 18 digits. Decimal
values, floats, booleans, signs and exponents are rejected. Prices retain their
decimal precision.

## FastAPI

```bash
pip install "recuut[fastapi]>=0.11.0"
```

```python
from typing import Self

from fastapi import FastAPI
from pydantic import BaseModel, Field, model_validator
from recuut.fastapi import recuut
from recuut_contracts import (
    TextGenerationV1,
    TextGenerationV1Payment,
)

app = FastAPI()

class GenerateRequest(BaseModel):
    prompt: str
    input_tokens: int = Field(
        default=0,
        exclude=True,
        ge=0,
        json_schema_extra={"readOnly": True},
    )

    @model_validator(mode="after")
    def derive_input_tokens(self) -> Self:
        self.input_tokens = len(self.prompt.split())
        return self

class GenerateResponse(BaseModel):
    text: str

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

Generated Payment `complete(...)` and `acomplete(...)` methods require every handler-reported value, so mypy
and Pyright reject an omitted or misspelled keyword. Values marked as request
input are captured from an exact handler argument, one nested Pydantic field,
or an explicit `input_map`. Every input variable must be captured before access;
invalid or missing captured values return `422` before any payment request. For revisions
without input keys, the access dependency can relay a 402 challenge before FastAPI
validates a required body. The decorator leaves validated application inputs
unchanged. `complete` prepares and validates the response before settlement; the decorator releases that prepared response and attaches
`PAYMENT-RESPONSE`. 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 not supported.

## Django REST Framework

```bash
pip install "recuut[django]>=0.11.0" djangorestframework
```

Use your normal serializer and view. Validate before payment access; save only after access succeeds.

```python
from dataclasses import dataclass

from recuut.django import recuut
from rest_framework import serializers
from rest_framework.decorators import api_view
from rest_framework.response import Response

from recuut_contracts import TextGenerationV1, TextGenerationV1Payment

@dataclass
class GenerateResult:
    text: str
    output_tokens: int

class GenerateSerializer(serializers.Serializer):
    prompt = serializers.CharField(max_length=10_000, write_only=True)
    text = serializers.CharField(read_only=True)

    def create(self, validated_data) -> GenerateResult:
        # Replace this sample with your logic and measured usage.
        text = validated_data["prompt"].upper()
        return GenerateResult(text=text, output_tokens=len(text.split()))

@api_view(["POST"])
@recuut(TextGenerationV1)
def generate(request, payment: TextGenerationV1Payment) -> Response:
    serializer = GenerateSerializer(data=request.data)
    serializer.is_valid(raise_exception=True)

    refusal = payment.access(input_tokens=len(serializer.validated_data["prompt"].split()))
    if refusal is not None:
        return refusal

    result = serializer.save()
    return payment.complete(Response(serializer.data), output_tokens=result.output_tokens)
```

`@recuut` binds payment to the request. After access succeeds, it reports failure if the view raises or returns `4xx`/`5xx`.
A refusal is a native HTTP response; return it immediately. `complete()` finalizes
and renders the response before settlement, attaches the receipt, and returns
the native response. Put the same decorator directly on `APIView.post()` for a
class-based view. Preserve your existing authentication, permissions, and CSRF policy.

Plain Django views are also supported. They retain their application's request
parsing and validation. Sync views use `access()` / `complete()`; async Django
views use `await aaccess()` / `await acomplete()`. DRF examples use sync views.
Streaming responses are unsupported.

## Shared payment lifecycle

Django and FastAPI use the same authorization, completion and failure policy.
Framework adapters own request headers and final response preparation. FastAPI
captures validated inputs automatically; Django supplies them explicitly after
its normal validation. Completion happens once, after response validation and
rendering. Success is not released until recuut returns a successful receipt.

Pulled contracts expose typed `access`, `aaccess`, `complete`, and `acomplete`
methods. Both completion methods return the supplied native Django response or
typed FastAPI value while the FastAPI decorator releases the
prepared response. Every reported field remains a required completion keyword.
Pull contracts again when upgrading. Previously generated `RecuutResult`
completion helpers remain supported by their existing decorated handlers.

For a revision that has not been pulled, use the explicit mapping fallback:

```python
from recuut import RawPayment, RawRevision
from recuut.fastapi import recuut
from fastapi import FastAPI

app = FastAPI()
revision = RawRevision(revision_key="rvk_00000000000070008000000000000000")

@app.post("/raw")
@recuut(revision)
def raw_endpoint(payment: RawPayment) -> dict[str, bool]:
    return payment.complete({"ok": True}, values={"items": 1})
```

## Errors

Invalid contract construction raises `RecuutConfigurationError`. Detectable
decorator configuration problems warn by default or raise in strict mode.
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)
