OpenAPI Support

AppKernel generates a live OpenAPI 3.1.0 specification from your registered services. Call enable_openapi() after registering all services:

from appkernel import AppKernelEngine, HttpClientConfig

kernel = AppKernelEngine('my-app', cfg_dir='./config')
kernel.register(User, methods=['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
kernel.enable_openapi(title='My API', version='1.0.0')
kernel.run()

This exposes three endpoints:

  • /openapi.json — the machine-readable OpenAPI 3.1.0 specification.

  • /docs — Swagger UI (interactive browser).

  • /redoc — ReDoc documentation browser.

To suppress the UI endpoints and serve only the raw spec:

kernel.enable_openapi(include_docs=False)

The tags parameter on register() applies a tag to every endpoint of that registration, which is useful for grouping all endpoints of an API version together:

kernel.register(UserV1Service(), url_base='/v1/', tags=['v1'])
kernel.register(UserV2Service(), url_base='/v2/', tags=['v2'])

Registration tags are merged with per-decorator tags, with registration tags listed first. See API Versioning for a full walkthrough.

enable_openapi returns self and can be chained:

kernel.register(User).deny_all().require(Role('user'), methods='GET')
kernel.enable_openapi(title='My API').run()

Decorator metadata

All @action and @resource decorators accept optional OpenAPI kwargs. They do not affect runtime behaviour — they are used only for documentation.

Kwarg

Purpose

summary

Short operation summary (shown as the title in Swagger UI).

tags

List of tag strings used to group operations (e.g. ['Payments', 'v2']).

request_model

Explicit Model subclass for the request body schema. Overrides type-hint inference.

response_model

Explicit Model subclass for the 200/201 response schema. Overrides type-hint inference.

query_params

List of query parameter names to document (e.g. ['start', 'stop']).

deprecated

True marks the operation as deprecated in the spec (renders with strikethrough in Swagger UI). Has no effect on runtime behaviour.

Example:

class PaymentService:

    @resource(
        method='POST',
        summary='Authorise a payment',
        tags=['Payments'],
        request_model=Payment,
        response_model=Payment,
    )
    def authorise(self, payload):
        ...

    @resource(
        method='GET',
        query_params=['start', 'stop'],
        summary='List payments in a date range',
        tags=['Payments'],
    )
    def list_payments(self, start=None, stop=None):
        ...

    @resource(
        method='POST',
        path='/legacy-pay',
        deprecated=True,
        summary='Deprecated — use POST /payments/pay instead',
    )
    def legacy_pay(self, payload):
        ...

Type-hint inference

When request_model or response_model are omitted, AppKernel inspects the method’s type annotations to infer schemas automatically:

class UserService:

    @resource(method='POST')
    def create_user(self, user: User) -> User:
        ...

The first non-self parameter typed as a Model subclass becomes the request body schema. The -> ReturnType annotation becomes the 200/201 response schema.

When a method has no type hints, the schema falls back to {"type": "object"}. Add type hints or use explicit request_model / response_model kwargs to get precise documentation.

Priority order for schema resolution:

  1. Explicit request_model / response_model decorator kwarg.

  2. Type-hint inference from the method signature.

  3. Model class (for CRUD routes registered with register()).

  4. {"type": "object"} fallback.

Validator constraints

Model field schemas are built by get_json_schema() and include constraints derived from validator metadata:

Validator

OpenAPI constraint

Min(n) on int / float

"minimum": n

Max(n) on int / float

"maximum": n

Min(n) on str

"minLength": n

Max(n) on str

"maxLength": n

Regexp(pattern)

"pattern": pattern

Email()

"format": "email"

NotEmpty() on list

"minItems": 1

Unique() on list

"uniqueItems": true

Response envelope

Collection responses (GET /resources/) are wrapped in the AppKernel envelope:

{
    "_type":  "list",
    "_items": [ { ... }, { ... } ],
    "_links": { "self": { "href": "/resources/" } }
}

The generated spec documents collection GET responses with an inline schema:

{
    "type": "object",
    "properties": {
        "_type":  { "type": "string" },
        "_items": { "type": "array", "items": { "$ref": "#/components/schemas/User" } },
        "_links": { "type": "object" }
    }
}

Single-item GET, POST, and PUT responses reference the model schema directly ({"$ref": "#/components/schemas/User"}). DELETE responses document the OperationResult shape with a result count field.

Query DSL

AppKernel supports a rich URL query DSL for collection endpoints that cannot be fully expressed as standard OpenAPI parameters:

GET /users/?name=~john        # name contains 'john'
GET /users/?sequence=>10      # sequence greater than 10
GET /users/?roles:[Admin,User] # roles in the given list

These are documented as freeform string query parameters. The standard pagination and query parameters (page, page_size, sort_by, sort_order, query) are added automatically to every collection GET route.

Full DSL syntax is described in Repositories.

Programmatic usage

The generator can also be invoked directly without registering HTTP routes:

from appkernel.openapi import OpenAPISchemaGenerator

generator = OpenAPISchemaGenerator(title='My API', version='1.0.0')
spec = generator.generate()

import json
print(json.dumps(spec, indent=2))