Metadata-Version: 2.4
Name: apiquart
Version: 0.0.1
Summary: The minimalistic API framework for Quart — input/output validation and OpenAPI from Pydantic
Project-URL: Homepage, https://github.com/lymagics/apiquart
Project-URL: Bug Tracker, https://github.com/lymagics/apiquart/issues
Author: apiquart contributors
License: MIT
License-File: LICENSE
Keywords: api,async,openapi,pydantic,quart,rest,swagger,validation
Classifier: Environment :: Web Environment
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.9
Requires-Dist: pydantic<3,>=2.5
Requires-Dist: quart>=0.19
Provides-Extra: auth
Requires-Dist: quart-httpauth; extra == 'auth'
Provides-Extra: dev
Requires-Dist: black; extra == 'dev'
Requires-Dist: coverage; extra == 'dev'
Requires-Dist: hypercorn; extra == 'dev'
Requires-Dist: hypothesis; extra == 'dev'
Requires-Dist: language-tool-python; extra == 'dev'
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: pyhamcrest; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-asyncio; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest-fail-slow; extra == 'dev'
Requires-Dist: pytest-randomly; extra == 'dev'
Requires-Dist: pytest-repeat; extra == 'dev'
Requires-Dist: pyyaml; extra == 'dev'
Requires-Dist: quart-httpauth; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Provides-Extra: docs
Requires-Dist: furo; extra == 'docs'
Requires-Dist: sphinx; extra == 'docs'
Provides-Extra: yaml
Requires-Dist: pyyaml; extra == 'yaml'
Description-Content-Type: text/markdown

# apiquart

The minimalistic API framework for [Quart](https://github.com/pallets/quart) —
declarative request/response validation and automatic **OpenAPI 3.1** docs,
built on [Pydantic v2](https://docs.pydantic.dev/).

apiquart brings [APIFlask](https://apiflask.com/)-style ergonomics to Quart's
async-native world. It is a thin layer over vanilla Quart and Pydantic: an
`APIQuart` app is a `quart.Quart` subclass, your schemas are plain Pydantic
models, and removing every decorator still leaves a working route.

- **`@app.input`** — parse and validate the request (json/query/form/files/
  headers/cookies/path); the validated model instance is injected as an argument.
- **`@app.output`** — serialize the return value (ORM object, dict, or model)
  through Pydantic.
- **`@app.doc`** — attach OpenAPI operation metadata.
- **`@app.auth_required`** — first-class auth via
  [quart-httpauth](https://github.com/lymagics/quart-httpauth).
- **OpenAPI 3.1** generated straight from your Pydantic models, with four
  interactive doc UIs behind a single `/docs` endpoint.

## Installation

```
pip install apiquart
```

The distribution name is `apiquart`; the import name is `apiquart`. Optional
extras: `apiquart[auth]` (quart-httpauth), `apiquart[yaml]` (YAML spec output).

## Quickstart

```python
from apiquart import APIQuart, BaseModel, abort

app = APIQuart(__name__, title="Pets API", version="1.0.0")


class PetIn(BaseModel):
    name: str
    category: str = "other"


class PetOut(PetIn):
    id: int


pets: dict[int, dict] = {}


@app.post("/pets")
@app.input(PetIn, arg_name="pet")
@app.output(PetOut, status_code=201)
async def create_pet(pet):                      # pet is a validated PetIn
    pet_id = len(pets) + 1
    pets[pet_id] = {"id": pet_id, **pet.model_dump()}
    return pets[pet_id]


@app.get("/pets/<int:pet_id>")
@app.output(PetOut)
async def get_pet(pet_id):
    if pet_id not in pets:
        abort(404, message="Pet not found")
    return pets[pet_id]
```

Run it with any ASGI server:

```
hypercorn module:app
```

- OpenAPI spec: `GET /openapi.json`
- Interactive docs: `GET /docs` (Swagger UI by default; try `?ui=redoc`,
  `?ui=rapidoc`, `?ui=elements`)

## Input locations

`@app.input(Model, location=...)` supports `json` (default), `query`, `form`,
`files`, `form_and_files`, `headers`, `cookies` and `path`. The validated model
is injected as `f"{location}_data"` unless you pass `arg_name=`. Multiple
`@app.input` decorators stack for different locations. Invalid input yields a
`422` with the standard error envelope.

## Output

`@app.output(Model, status_code=200)` validates and serializes the handler's
return value (`model.model_validate(obj).model_dump(mode="json")`). Handlers may
return `obj`, `(obj, status)`, or `(obj, status, headers)`. Use `EmptyModel`
with `status_code=204` to document an empty response.

## Errors

Every error is a single JSON envelope:

```json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request contains invalid data",
    "detail": {"json": [{"loc": ["name"], "msg": "Field required", "type": "missing"}]}
  }
}
```

Raise one with `abort(404, message="Pet not found")` or
`raise HTTPError(...)`. Quart's own `HTTPException`s are reshaped into the same
envelope. Customize the shape with `@app.error_processor`.

## Authentication

```python
from quart_httpauth import HTTPTokenAuth
from apiquart import APIQuart

app = APIQuart(__name__)
auth = HTTPTokenAuth(scheme="Bearer")


@auth.verify_token
async def verify(token):
    return await User.from_token(token)


@app.get("/admin/stats")
@app.auth_required(auth, roles="admin")
async def stats():
    return {"user": auth.current_user()}
```

apiquart introspects the auth object and registers the matching OpenAPI security
scheme (Basic → `http/basic`, Bearer → `http/bearer`, custom header → `apiKey`).

## Pagination

```python
from apiquart import BaseModel, PaginationMeta, pagination_builder


class PetsPage(BaseModel):
    data: list[PetOut]
    pagination: PaginationMeta


@app.get("/pets")
@app.input(PetQuery, location="query")
@app.output(PetsPage)
async def list_pets(query_data):
    items, total = await Pet.paginate(query_data.page, query_data.per_page)
    return {
        "data": items,
        "pagination": pagination_builder(query_data.page, query_data.per_page, total),
    }
```

## Blueprints and class-based views

`APIBlueprint` carries the same decorators so blueprint routes contribute to the
spec. Quart's `MethodView` is supported — decorate the HTTP methods directly.

See [`examples/`](examples/) for runnable apps.

## License

MIT.
