Metadata-Version: 2.4
Name: kaya-openapi
Version: 0.0.1
Summary: Automatic OpenAPI specification generation for the Kaya lightweight ASGI web framework
Author-email: Walter Oggioni <oggioni.walter@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/woggioni/kaya
Project-URL: Bug Tracker, https://github.com/woggioni/kaya/issues
Classifier: Development Status :: 3 - Alpha
Classifier: Topic :: Utilities
Classifier: Intended Audience :: System Administrators
Classifier: Intended Audience :: Developers
Classifier: Environment :: Console
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: kaya-core
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: mypy; extra == "dev"
Requires-Dist: ipdb; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: httpx; extra == "dev"
Requires-Dist: httpx-ws; extra == "dev"

# kaya-openapi

Automatic [OpenAPI](https://www.openapis.org/) specification generation for the
[Kaya](https://github.com/woggioni/kaya) lightweight ASGI web framework.

The package provides an `OpenAPIMixin` that inspects a `KayaApp`'s routing
tree and serves:

- an OpenAPI 3.1 JSON document (default: `GET /openapi.json`)
- a Swagger UI page to browse it interactively (default: `GET /docs`)

## Usage

```python
from kaya.core import HttpContext, KayaApp
from kaya.openapi import OpenAPIMixin, operation

app = KayaApp(mixins=[OpenAPIMixin(title='My API', version='1.0.0')])


@app.GET('/users/${user_id:int}')
@operation(summary='Get a user',
           tags=['users'],
           responses={
               200: {'description': 'The user'},
               404: {'description': 'User not found'},
           })
async def get_user(ctx: HttpContext, user_id: int) -> None:
    ...
```

Run the app with any ASGI/RSGI server and open `http://localhost:8000/docs`.

## How routes are mapped

- Static segments and parameters are converted to OpenAPI path templating:
  - `/users/${user_id}` &rarr; `/users/{user_id}` (string path parameter)
  - `/users/${user_id:int}` &rarr; `/users/{user_id}` (integer path parameter)
- Wildcard routes (`*`) are **skipped**: they cannot be expressed in OpenAPI
  path syntax.
- Websocket routes are **skipped**: OpenAPI does not model websockets.
- Method-agnostic routes (registered with `app.route(path)` without methods)
  are documented under **all** standard HTTP methods, since they respond to
  all of them.
- The mixin's own endpoints are excluded from the document unless
  `include_self=True`.

The document is generated on every request to the spec endpoint, so routes
registered after the mixin is applied are always included.

## Operation metadata

The `@operation` decorator attaches OpenAPI metadata to a route handler.
All fragments are plain dicts merged verbatim into the generated operation
object, so any valid OpenAPI 3.1 construct can be used:

```python
@operation(summary='...',            # operation summary
           description='...',        # defaults to the handler docstring
           tags=['users'],
           operation_id='getUser',
           request_body={...},       # OpenAPI requestBody object
           responses={200: {...}},   # per-status-code response objects
           parameters=[...],         # extra/overriding parameter objects
           deprecated=False,
           hidden=False)             # exclude from the document
```

`parameters` entries whose `name` and `in` match an auto-generated path
parameter override it; all others are appended.

## Configuration

```python
OpenAPIMixin(
    title='My API',                # info.title (required)
    version='1.0.0',               # info.version (required)
    description='',                # info.description
    spec_path='/openapi.json',     # where the JSON document is served
    docs_path='/docs',             # where Swagger UI is served
    servers=[{'url': 'https://api.example.com'}],
    openapi_version='3.1.0',
    include_self=False,            # include spec/docs endpoints in the document
)
```

The document can also be generated programmatically without serving it:

```python
from kaya.openapi import generate_spec

spec = generate_spec(app, title='My API', version='1.0.0')
```
