Add kaya-openapi package for automatic OpenAPI spec generation
CI / Build Pip package (push) Successful in 2m49s
CI / Build Pip package (push) Successful in 2m49s
- New packages/kaya-openapi with OpenAPIMixin, @operation decorator, and generate_spec() that walks the routing tree - Enables kaya-core's Tree.register to expose the original handler callback as an instance attribute for metadata introspection - Registers GET /openapi.json and GET /docs (Swagger UI) routes - Supports and path parameters, docstring descriptions, @operation metadata, and excludes wildcard/WS routes - Adds example/openapi.py, updates CI, README, and requirements
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
# 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}` → `/users/{user_id}` (string path parameter)
|
||||
- `/users/${user_id:int}` → `/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')
|
||||
```
|
||||
Reference in New Issue
Block a user