Metadata-Version: 2.4
Name: typizer
Version: 0.1.0
Summary: Официальный Python SDK для Typizer API — извлечение данных из документов в JSON по вашей схеме
Keywords: typizer,sdk,api,document extraction,document parsing,ocr,invoice,json schema,извлечение данных,распознавание документов,счета,УПД
Author: Typizer
Author-email: Typizer <support@typizer.ru>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: Russian
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Dist: httpx>=0.23.1,<0.29.0
Requires-Dist: attrs>=22.2.0
Requires-Python: >=3.11
Project-URL: Homepage, https://typizer.ru
Project-URL: Documentation, https://docs.typizer.ru
Description-Content-Type: text/markdown

# typizer — Python SDK для Typizer API

Официальный Python SDK для [Typizer](https://typizer.ru) — сервиса, который извлекает данные из
документов (счета, УПД, накладные, договоры, сканы и фото) в JSON по вашей схеме.

SDK сгенерирован из OpenAPI-описания Typizer API: одна функция на каждую операцию REST API, с теми
же именами полей и кодами ошибок. Полная документация — [docs.typizer.ru](https://docs.typizer.ru).

## Установка

```sh
pip install typizer
```

Нужен Python 3.11 или новее. Зависимости — только `httpx` и `attrs`.

## Ключ API

1. Зарегистрируйтесь в кабинете [my.typizer.ru](https://my.typizer.ru).
2. Откройте раздел «Ключи API» и создайте ключ со скоупом `runs:write` (для запуска прогонов) —
   подробнее о скоупах в [«Ключах API»](https://docs.typizer.ru/how-to/api-keys/).
3. Ключ (`tz_live_…`) показывается один раз. Сохраните его, например в переменную окружения:

```sh
export TYPIZER_API_KEY="tz_live_…"
```

## Быстрый старт

Извлечь данные из документа по разовой схеме (`POST /v1/extract`). `wait=60` держит запрос до
завершения прогона, но не дольше 60 секунд:

```python
import os
import uuid

import httpx

from typizer import AuthenticatedClient
from typizer.api.runs import extract
from typizer.models import ExtractRequest, ExtractRequestSchema, Problem, Run

client = AuthenticatedClient(
    base_url="https://api.typizer.ru",
    token=os.environ["TYPIZER_API_KEY"],  # сам ключ, без слова Bearer
    timeout=httpx.Timeout(70.0),  # чуть больше, чем wait
)

schema = ExtractRequestSchema.from_dict(
    {
        "type": "object",
        "properties": {
            "invoice_number": {"type": "string"},
            "total": {"type": "number"},
            "inn": {"type": "string", "x-typizer-check": "inn"},
        },
        "required": ["invoice_number", "total"],
    }
)

with client as client:
    run = extract.sync(
        client=client,
        body=ExtractRequest(schema=schema, url="https://typizer.ru/samples/invoice.pdf"),
        wait=60,
        idempotency_key=str(uuid.uuid4()),  # новый ключ на каждую операцию
    )

if isinstance(run, Problem):
    raise RuntimeError(f"{run.code}: {run.detail} ({run.doc_url})")
assert isinstance(run, Run)

print(run.status)  # succeeded | failed | queued | processing
print(run.output)  # результат в формате JSON по вашей схеме
```

Если прогон не успел завершиться за `wait`, придёт `Run` со статусом `queued` или `processing` —
дождитесь его через `typizer.api.runs.get_run` или вебхук (см. ниже и
[«Получить результат»](https://docs.typizer.ru/how-to/get-results/)).

## Типизатор: схема, сохранённая один раз

Схему удобно сохранить как типизатор (`POST /v1/typizers`) и запускать его по `id`:

```python
import os
import uuid

from typizer import AuthenticatedClient
from typizer.api.runs import create_run
from typizer.api.typizers import create_typizer
from typizer.models import CreateRunRequest, CreateTypizerRequest, CreateTypizerRequestSchema, Typizer

client = AuthenticatedClient(base_url="https://api.typizer.ru", token=os.environ["TYPIZER_API_KEY"])

with client as client:
    typizer = create_typizer.sync(
        client=client,
        body=CreateTypizerRequest(
            name="Счета поставщиков",
            schema=CreateTypizerRequestSchema.from_dict(
                {
                    "type": "object",
                    "properties": {
                        "invoice_number": {"type": "string"},
                        "total": {"type": "number"},
                    },
                    "required": ["invoice_number", "total"],
                }
            ),
        ),
    )
    assert isinstance(typizer, Typizer)

    run = create_run.sync(
        typizer.id,
        client=client,
        body=CreateRunRequest(url="https://typizer.ru/samples/invoice.pdf"),
        wait=60,
        idempotency_key=str(uuid.uuid4()),
    )
    print(run)
```

Файл с диска передаётся через multipart: `CreateRunFilesBody(file=File(payload=f, file_name="invoice.pdf"))`
(`File` — из `typizer.types`), для `extract` — `ExtractFilesBody`.

## Синхронные и асинхронные вызовы

Каждая операция — отдельный модуль `typizer.api.<тег>.<операция>` (например,
`typizer.api.runs.extract`) с четырьмя функциями:

| Функция | Что возвращает |
|---|---|
| `sync(...)` | разобранную модель (`Run`, `Typizer`, …), объект ошибки `Problem` или `None` |
| `sync_detailed(...)` | `Response`: `status_code`, `headers`, `content` и `parsed` |
| `asyncio(...)` | то же, что `sync`, но `async` |
| `asyncio_detailed(...)` | то же, что `sync_detailed`, но `async` |

Асинхронный вариант того же запроса:

```python
import asyncio
import os

from typizer import AuthenticatedClient
from typizer.api.runs import get_run
from typizer.models import Run


async def main() -> None:
    client = AuthenticatedClient(base_url="https://api.typizer.ru", token=os.environ["TYPIZER_API_KEY"])
    async with client as client:
        run = await get_run.asyncio("run_…", client=client)
        if isinstance(run, Run):
            print(run.status)


asyncio.run(main())
```

## Обработка ошибок

Любая ошибка API приходит в формате `application/problem+json` (RFC 9457). SDK не бросает
исключение на документированный код ошибки — `sync`/`asyncio` возвращают объект `Problem`:

- `code` — машиночитаемый код (`ProblemCode`: `invalid_request`, `unauthorized`,
  `insufficient_balance`, `rate_limited`, …);
- `detail` — описание конкретного случая, `param` — поле запроса, если виновато оно;
- `doc_url` — страница этого кода: `https://docs.typizer.ru/errors/<code>/`;
- `request_id` — приложите его к обращению в поддержку (support@typizer.ru).

```python
import os
import uuid

from typizer import AuthenticatedClient
from typizer.api.runs import create_run
from typizer.models import CreateRunRequest, Problem, ProblemCode

client = AuthenticatedClient(base_url="https://api.typizer.ru", token=os.environ["TYPIZER_API_KEY"])

response = create_run.sync_detailed(
    "tz_…",
    client=client,
    body=CreateRunRequest(url="https://typizer.ru/samples/invoice.pdf"),
    idempotency_key=str(uuid.uuid4()),
)
problem = response.parsed
if isinstance(problem, Problem):
    if problem.code == ProblemCode.RATE_LIMITED:
        print("повторить через", response.headers.get("Retry-After"), "с")
    elif problem.code == ProblemCode.INSUFFICIENT_BALANCE:
        print("пополните баланс в кабинете")
    else:
        print(problem.code, problem.detail, problem.doc_url, problem.request_id)
```

Повторять имеет смысл `rate_limited` (после `Retry-After`), `upstream_unavailable` и
`internal_error`; при сетевой ошибке повторяйте запрос с тем же `Idempotency-Key` — второй раз
ничего не создастся и не спишется. Остальные коды не исчезнут от повтора: сначала исправьте причину.
Все коды со статусами HTTP — [«Коды ошибок»](https://docs.typizer.ru/reference/errors/), правила
повторов — [«Обработка ошибок и повторов»](https://docs.typizer.ru/how-to/errors/).

Кроме `Problem`, возможны исключения транспортного уровня: `httpx.TimeoutException` (истёк
`timeout` клиента) и `typizer.errors.UnexpectedStatus` — только если клиент создан с
`raise_on_unexpected_status=True` и сервер вернул код, которого нет в описании API.

## Вебхуки

Вместо ожидания результат можно получать вебхуком: укажите `webhook_url` в запросе на прогон или
создайте конечную точку через `typizer.api.webhooks.create_webhook_endpoint`:

```python
import os

from typizer import AuthenticatedClient
from typizer.api.webhooks import create_webhook_endpoint, test_webhook_endpoint
from typizer.models import CreateWebhookEndpointRequest, WebhookEndpoint, WebhookEventType

client = AuthenticatedClient(base_url="https://api.typizer.ru", token=os.environ["TYPIZER_API_KEY"])

endpoint = create_webhook_endpoint.sync(
    client=client,
    body=CreateWebhookEndpointRequest(
        url="https://your-server.example/typizer/webhook",
        events=[WebhookEventType.RUN_SUCCEEDED, WebhookEventType.RUN_FAILED],
    ),
)
assert isinstance(endpoint, WebhookEndpoint)
print(endpoint.secret)  # whsec_… — ключ для проверки подписи, храните как секрет

test_webhook_endpoint.sync(endpoint.id, client=client)  # пришлёт тестовый run.succeeded
```

Вебхуки подписываются по спецификации [Standard Webhooks](https://www.standardwebhooks.com)
(HMAC-SHA256). Функции проверки подписи в SDK нет: готовая реализация на `hmac`/`hashlib` без
сторонних пакетов и тестовый вектор — в [«Приёме вебхуков»](https://docs.typizer.ru/how-to/webhooks/).
Никогда не обрабатывайте тело вебхука, не проверив подпись.

## Документация

- [Документация Typizer](https://docs.typizer.ru)
- [SDK для TypeScript и Python](https://docs.typizer.ru/reference/sdks/)
- [Справочник API](https://docs.typizer.ru/reference/api/) — REST-операция за каждой функцией SDK
- [Первый вызов API](https://docs.typizer.ru/tutorials/first-api-call/)

Вопросы и проблемы — support@typizer.ru.

## Лицензия

MIT — см. файл [LICENSE](LICENSE).
