Metadata-Version: 2.4
Name: klarient
Version: 0.1.0
Summary: A typed Python framework for modeling and generating API clients.
Author-email: Ludvik Jerabek <83429267+ludvikjerabek@users.noreply.github.com>
License-Expression: MIT
Project-URL: Documentation, https://github.com/ludvikjerabek/klarient#readme
Project-URL: Issues, https://github.com/ludvikjerabek/klarient/issues
Project-URL: Repository, https://github.com/ludvikjerabek/klarient
Keywords: api,asyncio,http,openapi,rest,typed
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
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: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: requests
Requires-Dist: requests>=2.32; extra == "requests"
Provides-Extra: httpx
Requires-Dist: httpx>=0.27; extra == "httpx"
Provides-Extra: aiohttp
Requires-Dist: aiohttp>=3.10; extra == "aiohttp"
Provides-Extra: all
Requires-Dist: aiohttp>=3.10; extra == "all"
Requires-Dist: httpx>=0.27; extra == "all"
Requires-Dist: requests>=2.32; extra == "all"
Provides-Extra: dev
Requires-Dist: aiohttp>=3.10; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: httpx>=0.27; extra == "dev"
Requires-Dist: pyflakes>=3.4; extra == "dev"
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: requests>=2.32; extra == "dev"
Requires-Dist: ruff>=0.15; extra == "dev"
Requires-Dist: twine>=6; extra == "dev"
Requires-Dist: vulture>=2.16; extra == "dev"
Dynamic: license-file

# Klarient

Klarient is a typed Python framework for building small, readable REST API
wrappers.

The goal is to let an API wrapper look like the API domain instead of a pile of
raw HTTP calls. Resources model the URI tree, request objects model inputs, and
response objects model typed results while preserving HTTP metadata.

Klarient is early pre-alpha software. The design is usable, but the public API
may still change as more real APIs are modeled.

## Install

```bash
pip install klarient
```

Install a transport extra for the HTTP library you want to use:

```bash
pip install "klarient[requests]"
pip install "klarient[httpx]"
pip install "klarient[aiohttp]"
```

## Basic Shape

A Klarient wrapper usually has four parts:

- a client class
- resource classes that mirror the REST URI tree
- request objects for typed query or body data
- response objects for typed access to API responses

```python
from enum import StrEnum

from klarient import (
    JSONBodyRequest,
    RequestField,
    RequestsTransport,
    ResourcePath,
    ResponseDict,
    SyncClient,
    SyncResource,
)


class TaskStatus(StrEnum):
    OPEN = "open"
    DONE = "done"


class TaskCreate(JSONBodyRequest):
    def __init__(
        self,
        *,
        title: str | None = None,
        status: TaskStatus | None = None,
    ) -> None:
        super().__init__(title=title, status=status)

    title = RequestField[str]()
    status = RequestField[TaskStatus]()


class Task(ResponseDict):
    @property
    def id(self) -> int:
        return int(self["id"])

    @property
    def title(self) -> str:
        return str(self["title"])

    @property
    def status(self) -> TaskStatus:
        return TaskStatus(str(self["status"]))


class TaskResponse(ResponseDict):
    @property
    def data(self) -> Task:
        return Task(self["data"], response=self.http_response)


class TaskResource(SyncResource[SyncClient]):
    def retrieve(self) -> TaskResponse:
        return self._executor.get(TaskResponse)


class TasksResource(SyncResource[SyncClient]):
    def __getitem__(self, task_id: int | str) -> TaskResource:
        return TaskResource(self, segment=ResourcePath.segment(task_id))

    def create(self, options: TaskCreate) -> TaskResponse:
        return self._executor.post(TaskResponse, options)


class TasksClient(SyncClient):
    def __init__(self, *, base_url: str) -> None:
        super().__init__(
            base_url=base_url,
            transport=RequestsTransport(),
        )
        self.tasks = TasksResource(self, segment="api/tasks")
```

Usage:

```python
client = TasksClient(base_url="https://api.example.test")

created = client.tasks.create(
    TaskCreate(title="Write docs", status=TaskStatus.OPEN)
)

print(created.data.id)
print(created.data.title)
print(created.data.status)

task = client.tasks[created.data.id].retrieve()
print(task.data.status)
```

## Request Modeling

Use typed request objects instead of passing dictionaries through your public
wrapper API.

```python
from klarient import QueryFieldSpec, QueryRequest, QuerySerialization


class TaskQuery(QueryRequest):
    def __init__(
        self,
        *,
        status: list[TaskStatus] | None = None,
        page: int | None = None,
    ) -> None:
        super().__init__(status=status, page=page)

    status = RequestField[list[TaskStatus]](
        query=QueryFieldSpec(serialization=QuerySerialization.REPEAT)
    )
    page = RequestField[int]()
```

That repeated field is encoded as repeated query values such as:

```text
?status=open&status=done
```

## Response Modeling

Response models keep the original HTTP response available:

```python
response = client.tasks[123].retrieve()

print(response.data.title)
print(response.status)
print(response.http_response.headers)
print(response.native_response)
```

Use `ResponseDict` for JSON objects, `ResponseList` for JSON arrays, and derive
from `ResponseBase` when a response owns a different representation such as
text, XML, or bytes.

## Project Status

Klarient is currently focused on the core framework:

- transport-independent HTTP primitives
- sync and async clients
- typed REST resources
- typed request field helpers
- typed response models
- reusable pagination strategies
- pluggable transport adapters for requests, httpx, and aiohttp

Examples and broader documentation will live outside the core package while the
framework stabilizes.
