Skip to content

OpenAPI

1.Introduction

Pait in addition to parameter type conversion and checking, but also supports the automatic generation of route function OpenAPI data. You only need to write the code of the route function, Pait can generate the corresponding OpenAPI documentation of the route function, such as Documentation Home of the sample code:

docs_source_code/introduction/flask_demo.py
from typing import Type

from flask import Flask, Response, jsonify
from pydantic import BaseModel, Field

from pait.app.flask import pait
from pait.field import Json
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute


class DemoResponseModel(JsonResponseModel):
    class ResponseModel(BaseModel):
        uid: int = Field()
        user_name: str = Field()

    description: str = "demo response"
    response_data: Type[BaseModel] = ResponseModel


@pait(response_model_list=[DemoResponseModel])
def demo_post(
    uid: int = Json.t(description="user id", gt=10, lt=1000),
    username: str = Json.t(description="user name", min_length=2, max_length=4),
) -> Response:
    return jsonify({"uid": uid, "user_name": username})


app = Flask("demo")
app.add_url_rule("/api", "demo", demo_post, methods=["POST"])
AddDocRoute(app)


if __name__ == "__main__":
    app.run(port=8000)
docs_source_code/introduction/starlette_demo.py
from typing import Type

from pydantic import BaseModel, Field
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route

from pait.app.starlette import pait
from pait.field import Json
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute


class DemoResponseModel(JsonResponseModel):
    class ResponseModel(BaseModel):
        uid: int = Field()
        user_name: str = Field()

    description: str = "demo response"
    response_data: Type[BaseModel] = ResponseModel


@pait(response_model_list=[DemoResponseModel])
async def demo_post(
    uid: int = Json.t(description="user id", gt=10, lt=1000),
    username: str = Json.t(description="user name", min_length=2, max_length=4),
) -> JSONResponse:
    return JSONResponse({"uid": uid, "user_name": username})


app = Starlette(routes=[Route("/api", demo_post, methods=["POST"])])
AddDocRoute(app)


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/introduction/sanic_demo.py
from typing import Type

from pydantic import BaseModel, Field
from sanic.app import Sanic
from sanic.response import HTTPResponse, json

from pait.app.sanic import pait
from pait.field import Json
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute


class DemoResponseModel(JsonResponseModel):
    class ResponseModel(BaseModel):
        uid: int = Field()
        user_name: str = Field()

    description: str = "demo response"
    response_data: Type[BaseModel] = ResponseModel


@pait(response_model_list=[DemoResponseModel])
async def demo_post(
    uid: int = Json.t(description="user id", gt=10, lt=1000),
    username: str = Json.t(description="user name", min_length=2, max_length=4),
) -> HTTPResponse:
    return json({"uid": uid, "user_name": username})


app = Sanic(name="demo")
app.add_route(demo_post, "/api", methods=["POST"])
AddDocRoute(app)


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/introduction/tornado_demo.py
from typing import Type

from pydantic import BaseModel, Field
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler

from pait.app.tornado import pait
from pait.field import Json
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute


class DemoResponseModel(JsonResponseModel):
    class ResponseModel(BaseModel):
        uid: int = Field()
        user_name: str = Field()

    description: str = "demo response"
    response_data: Type[BaseModel] = ResponseModel


class DemoHandler(RequestHandler):
    @pait(response_model_list=[DemoResponseModel])
    def post(
        self,
        uid: int = Json.t(description="user id", gt=10, lt=1000),
        username: str = Json.t(description="user name", min_length=2, max_length=4),
    ) -> None:
        self.write({"uid": uid, "user_name": username})


app: Application = Application([(r"/api", DemoHandler)])
AddDocRoute(app)


if __name__ == "__main__":
    app.listen(8000)
    IOLoop.instance().start()

After running the code and visiting: http://127.0.0.1:8000/swagger in your browser you can see the SwaggerUI page:

As you can see through the page, the data displayed on the Swagger page includes the data of the response object, the data labeled by the Pait decorator, and the parameters of the route function.

2.OpenAPI properties of route functions

Binding OpenAPI attributes to a route function is very simple, just need to fill in the Pait decorator with the corresponding attributes. Common route function attribute bindings are in the following code:

from pait.app.any import pait
from pait.model.tag import Tag

demo_tag = Tag("demo tag", desc="demo tag desc")


@pait(
    desc="demo func desc",
    name="demo",
    summary="demo func summary",
    tag=(demo_tag,)
)
def demo() -> None:
    pass
The OpenAPI information for the route function is specified through the Pait attributes, which do the following:

Attributes OpenAPI Attributes Description
desc description Documentation of the interface in detail
name operation_id The name of the interface
summary summary Brief description of the interface
tag tag The OpenAPI tag of the interface

Note

  • 1.In most cases, the name attribute is just part of the operation_id attribute and Pait does not guarantee that name is exactly equal to operation_id.
  • 2.Tag should be guaranteed to be globally unique

However, the name and desc attributes can also be obtained from the route function name and the __doc__ of the route function For example, the name and desc attributes of the route function in the following code are consistent with the code above:

from pait.app.any import pait
from pait.model.tag import Tag

demo_tag = Tag("demo tag", desc="demo tag desc")


@pait(
    summary="demo func summary",
    tag=(demo_tag,)
)
def demo() -> None:
    """demo func desc"""
    pass

In addition to the above attributes, OpenAPI has an attribute called deprecated, which is primarily used to mark whether an interface has been deprecated. Pait does not directly support the marking of the deprecated attribute, but instead determines whether the deprecated of a route function is True by using PaitStatus, which is very simple to use, as in the following code:

from pait.app.any import pait
from pait.model.status import PaitStatus


@pait(status=PaitStatus.test)
def demo() -> None:
    pass
This code indicates that the route function is under test and deprecated is False, for more statuses and whether it is deprecated or not see the following table.

status value stage deprecated description
undefined default False undefined, default status
design development False design
dev development False development and testing
integration_testing development False integration testing
complete development complete False development complete
test development complete False testing
pre_release release False pre release
release release False release
abnormal offline True Temporary offline
maintenance offline False Maintenance
archive offline True archive
abandoned offline True abandoned, will not be used again

3.The response object of the route function

In the previous introduction, a list of response objects for a route function is defined via response_model_list, which contains one or more response objects.

Note

It is recommended to use only one response object, if there is more than one, most non-OpenAPI feature(e.g. plugins) will default to using only the first response object.

Pait provides a variety of response objects, as listed below:

Response Object Name Description
JsonResponseModel Object whose response is Json
XmlResponseModel Object whose response is Xml
TextResponseModel Objects whose response is text
HtmlResponseModel Objects whose response is Html
FileResponseModel Objects whose response is File

Pait only provides response objects for common response types, if there is no applicable response object, can define a response object that meets the requirements through pait.model.response.BaseResponseModel. which is a container for the different properties of the OpenAPI response object, as follows.

Attribute Name Description
response_data Define the response data, if it is response data with a structure then it should be a pydantic.BaseModel describing the structure
media_type The Media Type of the response object
name The name of the response object.
description The description of the response object
header The header of the response object, the value should be pydantic.BaseModel not Dict
status_code The Http status code of the response object, defaults to (200, )
openapi_schema The openapi.schema of the response object

Most response objects can be defined through these properties. The sample code is as follows:

docs_source_code/openapi/how_to_use_openapi/flask_demo.py
from typing import List

from flask import Flask, Response, jsonify
from pydantic import BaseModel, Field

from pait.app.flask import pait
from pait.field import Query
from pait.model.response import BaseResponseModel
from pait.openapi.doc_route import AddDocRoute


class MyJsonResponseModel(BaseResponseModel):
    class ResponseModel(BaseModel):
        class UserModel(BaseModel):
            name: str = Field(..., example="so1n")
            uid: int = Query.t(description="user id", gt=10, lt=1000)
            age: int = Field(..., gt=0)

        code: int = Field(..., ge=0)
        msg: str = Field(...)
        data: List[UserModel]

    class HeaderModel(BaseModel):
        x_token: str = Field(..., alias="X-Token")
        content_type: str = Field(..., alias="Content-Type")

    response_data = ResponseModel
    description = "demo json response"
    media_type = "application/json; charset=utf-8"
    header = HeaderModel
    status_code = (200, 201, 404)


@pait(response_model_list=[MyJsonResponseModel])
def demo(
    uid: int = Query.t(description="user id", gt=10, lt=1000),
    age: int = Query.t(description="age", gt=0),
    username: str = Query.t(description="user name", min_length=2, max_length=4),
) -> Response:
    resp = jsonify({"code": 0, "msg": "", "data": [{"name": username, "uid": uid, "age": age}]})
    resp.headers.add("X-Token", "12345")
    resp.headers["Content-Type"] = "application/json; charset=utf-8"
    return resp


app = Flask("demo")
app.add_url_rule("/api/demo", "demo", demo, methods=["GET"])
AddDocRoute(app)


if __name__ == "__main__":
    app.run(port=8000)
docs_source_code/openapi/how_to_use_openapi/starlette_demo.py
from typing import List

from pydantic import BaseModel, Field
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route

from pait.app.starlette import pait
from pait.field import Query
from pait.model.response import BaseResponseModel
from pait.openapi.doc_route import AddDocRoute


class MyJsonResponseModel(BaseResponseModel):
    class ResponseModel(BaseModel):
        class UserModel(BaseModel):
            name: str = Field(..., example="so1n")
            uid: int = Query.t(description="user id", gt=10, lt=1000)
            age: int = Field(..., gt=0)

        code: int = Field(..., ge=0)
        msg: str = Field(...)
        data: List[UserModel]

    class HeaderModel(BaseModel):
        x_token: str = Field(..., alias="X-Token")
        content_type: str = Field(..., alias="Content-Type")

    response_data = ResponseModel
    description = "demo json response"
    media_type = "application/json; charset=utf-8"
    header = HeaderModel
    status_code = (200, 201, 404)


@pait(response_model_list=[MyJsonResponseModel])
async def demo(
    uid: int = Query.t(description="user id", gt=10, lt=1000),
    age: int = Query.t(description="age", gt=0),
    username: str = Query.t(description="user name", min_length=2, max_length=4),
) -> JSONResponse:
    resp = JSONResponse({"code": 0, "msg": "", "data": [{"name": username, "uid": uid, "age": age}]})
    resp.headers.append("X-Token", "12345")
    resp.headers["Content-Type"] = "application/json; charset=utf-8"
    return resp


app = Starlette(routes=[Route("/api/demo", demo, methods=["GET"])])
AddDocRoute(app)


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/openapi/how_to_use_openapi/sanic_demo.py
from typing import List

from pydantic import BaseModel, Field
from sanic.app import Sanic
from sanic.response import HTTPResponse, json

from pait.app.sanic import pait
from pait.field import Query
from pait.model.response import BaseResponseModel
from pait.openapi.doc_route import AddDocRoute


class MyJsonResponseModel(BaseResponseModel):
    class ResponseModel(BaseModel):
        class UserModel(BaseModel):
            name: str = Field(..., example="so1n")
            uid: int = Query.t(description="user id", gt=10, lt=1000)
            age: int = Field(..., gt=0)

        code: int = Field(..., ge=0)
        msg: str = Field(...)
        data: List[UserModel]

    class HeaderModel(BaseModel):
        x_token: str = Field(..., alias="X-Token")
        content_type: str = Field(..., alias="Content-Type")

    response_data = ResponseModel
    description = "demo json response"
    media_type = "application/json; charset=utf-8"
    header = HeaderModel
    status_code = (200, 201, 404)


@pait(response_model_list=[MyJsonResponseModel])
async def demo(
    uid: int = Query.t(description="user id", gt=10, lt=1000),
    age: int = Query.t(description="age", gt=0),
    username: str = Query.t(description="user name", min_length=2, max_length=4),
) -> HTTPResponse:
    resp = json({"code": 0, "msg": "", "data": [{"name": username, "uid": uid, "age": age}]})

    resp.headers.add("X-Token", "12345")
    resp.headers["Content-Type"] = "application/json; charset=utf-8"
    return resp


app = Sanic(name="demo")
app.add_route(demo, "/api/demo", methods=["GET"])
AddDocRoute(app)


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/openapi/how_to_use_openapi/tornado_demo.py
from typing import List

from pydantic import BaseModel, Field
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler

from pait.app.tornado import pait
from pait.field import Query
from pait.model.response import BaseResponseModel
from pait.openapi.doc_route import AddDocRoute


class MyJsonResponseModel(BaseResponseModel):
    class ResponseModel(BaseModel):
        class UserModel(BaseModel):
            name: str = Field(..., example="so1n")
            uid: int = Query.t(description="user id", gt=10, lt=1000)
            age: int = Field(..., gt=0)

        code: int = Field(..., ge=0)
        msg: str = Field(...)
        data: List[UserModel]

    class HeaderModel(BaseModel):
        x_token: str = Field(..., alias="X-Token")
        content_type: str = Field(..., alias="Content-Type")

    response_data = ResponseModel
    description = "demo json response"
    media_type = "application/json; charset=utf-8"
    header = HeaderModel
    status_code = (200, 201, 404)


class DemoHandler(RequestHandler):
    @pait(response_model_list=[MyJsonResponseModel])
    def get(
        self,
        uid: int = Query.t(description="user id", gt=10, lt=1000),
        age: int = Query.t(description="age", gt=0),
        username: str = Query.t(description="user name", min_length=2, max_length=4),
    ) -> None:
        self.write({"code": 0, "msg": "", "data": [{"name": username, "uid": uid, "age": age}]})
        self.add_header("X-Token", "12345")
        self.set_header("Content-Type", "application/json; charset=utf-8")


app: Application = Application([(r"/api/demo", DemoHandler)])
AddDocRoute(app)


if __name__ == "__main__":
    app.listen(8000)
    IOLoop.instance().start()

The first highlighted code is a response object which indicates that the Http status codes may be 200, 201 and 404. The Media Type is application/json. The Header has properties X-Token and Content-Type. And most importantly, the data structure of the response body is defined as follows:

{
  "code": 0,
  "msg": "",
  "data": [
    {
      "name": "so1n",
      "sex": "man",
      "age": 18
    }
  ]
}
The second highlighted code binds the response object to the route function. Now run the code and visit 127.0.0.1:8000/redoc in your browser, you can see that the current page displays the OpenAPI data of the route function in full, as follows

Note

Since Redoc presents data in a much more parsimonious way than Swagger, this case uses Redoc to present data. In fact Pait supports a variety of OpenAPI UI pages, see OpenAPI routes for details:.

4.Field

The page in the previous section contains not only the data of the response object, but also the data of the request parameters. such as the uid parameter, which is declared as required and is also declared to be of type integer with a value in the range of 10-1000.

These request parameters are declared through Field objects, which not only validate the parameters but also provide data for OpenAPI. In addition to this, the Field object has some properties that are specialized for OpenAPI, they include:

Attributes description
links OpenAPI link function, used to specify parameters associated with a response object
media_type Defines the Media Type of a parameter, currently only Body, Json, File, Form, MultiForm are used, it is recommended to use only one Media Type for an route function.
openapi_serialization Define the serialization of the parameters, please refer to serialization
example Define an example value for the parameter; Pait supports factory functions, but converting to OpenAPI will result in a fixed value generated in the moment
openapi_include If the value is False, Pait will not consider this parameter when generating the Open API

Links is a feature of OpenAPI that is used to specify that a request parameter from interface A is associated with a piece of data in the response object from interface B. For example:

docs_source_code/openapi/how_to_use_openapi/flask_link_demo.py
import hashlib
from typing import Type

from flask import Flask
from pydantic import BaseModel, Field

from pait import field
from pait.app.flask import pait
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute
from pait.openapi.openapi import LinksModel


class LoginRespModel(JsonResponseModel):
    class ResponseModel(BaseModel):  # type: ignore
        class DataModel(BaseModel):
            token: str

        code: int = Field(0, description="api code")
        msg: str = Field("success", description="api status msg")
        data: DataModel

    description: str = "login response"
    response_data: Type[BaseModel] = ResponseModel


link_login_token_model: LinksModel = LinksModel(LoginRespModel, "$response.body#/data/token", desc="test links model")


@pait(response_model_list=[LoginRespModel])
def login_route(
    uid: str = field.Json.i(description="user id"), password: str = field.Json.i(description="password")
) -> dict:
    return {"code": 0, "msg": "", "data": {"token": hashlib.sha256((uid + password).encode("utf-8")).hexdigest()}}


@pait()
def get_user_route(token: str = field.Header.i("", description="token", links=link_login_token_model)) -> dict:
    if token:
        return {"code": 0, "msg": ""}
    else:
        return {"code": 1, "msg": ""}


app = Flask("demo")
app.add_url_rule("/api/login", "login", login_route, methods=["POST"])
app.add_url_rule("/api/get-user-info", "get_user_info", get_user_route, methods=["GET"])
AddDocRoute(app)


if __name__ == "__main__":
    app.run(port=8000)
docs_source_code/openapi/how_to_use_openapi/starlette_link_demo.py
import hashlib
from typing import Type

from pydantic import BaseModel, Field
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route

from pait import field
from pait.app.starlette import pait
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute
from pait.openapi.openapi import LinksModel


class LoginRespModel(JsonResponseModel):
    class ResponseModel(BaseModel):  # type: ignore
        class DataModel(BaseModel):
            token: str

        code: int = Field(0, description="api code")
        msg: str = Field("success", description="api status msg")
        data: DataModel

    description: str = "login response"
    response_data: Type[BaseModel] = ResponseModel


link_login_token_model: LinksModel = LinksModel(LoginRespModel, "$response.body#/data/token", desc="test links model")


@pait(response_model_list=[LoginRespModel])
async def login_route(
    uid: str = field.Json.i(description="user id"), password: str = field.Json.i(description="password")
) -> JSONResponse:
    return JSONResponse(
        {"code": 0, "msg": "", "data": {"token": hashlib.sha256((uid + password).encode("utf-8")).hexdigest()}}
    )


@pait()
async def get_user_route(
    token: str = field.Header.i(
        "",
        description="token",
        links=link_login_token_model,
    )
) -> JSONResponse:
    if token:
        return JSONResponse({"code": 0, "msg": ""})
    else:
        return JSONResponse({"code": 1, "msg": ""})


app = Starlette(
    routes=[
        Route("/api/login", login_route, methods=["POST"]),
        Route("/api/get-user-info", get_user_route, methods=["GET"]),
    ]
)
AddDocRoute(app)


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/openapi/how_to_use_openapi/sanic_link_demo.py
import hashlib
from typing import Type

from pydantic import BaseModel, Field
from sanic.app import Sanic
from sanic.response import HTTPResponse, json

from pait import field
from pait.app.sanic import pait
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute
from pait.openapi.openapi import LinksModel


class LoginRespModel(JsonResponseModel):
    class ResponseModel(BaseModel):  # type: ignore
        class DataModel(BaseModel):
            token: str

        code: int = Field(0, description="api code")
        msg: str = Field("success", description="api status msg")
        data: DataModel

    description: str = "login response"
    response_data: Type[BaseModel] = ResponseModel


link_login_token_model: LinksModel = LinksModel(LoginRespModel, "$response.body#/data/token", desc="test links model")


@pait(response_model_list=[LoginRespModel])
async def login_route(
    uid: str = field.Json.i(description="user id"), password: str = field.Json.i(description="password")
) -> HTTPResponse:
    return json({"code": 0, "msg": "", "data": {"token": hashlib.sha256((uid + password).encode("utf-8")).hexdigest()}})


@pait()
def get_user_route(
    token: str = field.Header.i(
        "",
        description="token",
        links=link_login_token_model,
    )
) -> HTTPResponse:
    if token:
        return json({"code": 0, "msg": ""})
    else:
        return json({"code": 1, "msg": ""})


app = Sanic(name="demo")
app.add_route(login_route, "/api/login", methods=["POST"])
app.add_route(get_user_route, "/api/get-user-info", methods=["GET"])
AddDocRoute(app)


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/openapi/how_to_use_openapi/tornado_link_demo.py
import hashlib
from typing import Type

from pydantic import BaseModel, Field
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler

from pait import field
from pait.app.tornado import pait
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute
from pait.openapi.openapi import LinksModel


class LoginRespModel(JsonResponseModel):
    class ResponseModel(BaseModel):  # type: ignore
        class DataModel(BaseModel):
            token: str

        code: int = Field(0, description="api code")
        msg: str = Field("success", description="api status msg")
        data: DataModel

    description: str = "login response"
    response_data: Type[BaseModel] = ResponseModel


link_login_token_model: LinksModel = LinksModel(LoginRespModel, "$response.body#/data/token", desc="test links model")


class LoginHandler(RequestHandler):
    @pait(response_model_list=[LoginRespModel])
    async def post(
        self, uid: str = field.Json.i(description="user id"), password: str = field.Json.i(description="password")
    ) -> None:
        self.write(
            {"code": 0, "msg": "", "data": {"token": hashlib.sha256((uid + password).encode("utf-8")).hexdigest()}}
        )


class GetUserHandler(RequestHandler):
    @pait()
    def get(
        self,
        token: str = field.Header.i(
            "",
            description="token",
            links=link_login_token_model,
        ),
    ) -> None:
        if token:
            self.write({"code": 0, "msg": ""})
        else:
            self.write({"code": 1, "msg": ""})


app: Application = Application(
    [(r"/api/login", LoginHandler), (r"/api/get-user-info", GetUserHandler)],
)
AddDocRoute(app)


if __name__ == "__main__":
    app.listen(8000)
    IOLoop.instance().start()

This example defines a login route function--login_route and a route function to get user details -- get_user_route. The route function to get the user details needs a token parameter to verify the user and get the user id, which is generated by the login route function, so the token parameter of the user details route function is related to the token in the response data of the login route function.

In order for OpenAPI to recognize that the token parameter is associated with a token in the response object. First create an instance named link_login_token_model that is bound to the LoginRespModel response object and indicates the parameter to be bound by the expression $response.body#/data/token". Then the link_login_token_model is assigned to the links attribute of the Field of the token in the get_user_route route function, and this completes the association once.

After running the code and visiting http://127.0.0.1:8000/swagger in your browser you will see the following page: can see through the page that the Response column of the login interface shows the Links data on the far right.

Note

Currently, many OpenAPI tools only provide simple Links support. For more information on the use and description of Links, see Swagger Links.

5.OpenAPI generation

In the OpenAPI ecosystem, at its core is a piece of OpenAPI-conforming json or yaml text, which can be used in OpenAPI pages such as Swagger, or imported for use in tools such as Postman. Pait will delegate the collected data to AnyAPI to be processed and generate an OpenAPI object, which supports being converted into a variety of human-readable text or pages.

Note

AnyAPI is separated from Pait and is currently for Pait use only, more features will be added to AnyAPI in subsequent releases.

The following is an example of generating an OpenAPI object and generating output content based on the OpenAPI object:

docs_source_code/openapi/how_to_use_openapi/flask_with_output_demo.py
from typing import Any, Type

from any_api.openapi.to.markdown import Markdown
from flask import Flask, Response, jsonify
from pydantic import BaseModel, Field

from pait.app.flask import pait
from pait.field import Json
from pait.g import config
from pait.model.response import JsonResponseModel
from pait.openapi.openapi import OpenAPI


class DemoResponseModel(JsonResponseModel):
    class ResponseModel(BaseModel):
        uid: int = Field()
        user_name: str = Field()

    description: str = "demo response"
    response_data: Type[BaseModel] = ResponseModel


@pait(response_model_list=[DemoResponseModel])
def demo_post(
    uid: int = Json.t(description="user id", gt=10, lt=1000),
    username: str = Json.t(description="user name", min_length=2, max_length=4),
) -> Response:
    return jsonify({"uid": uid, "user_name": username})


app = Flask("demo")
app.add_url_rule("/api", "demo", demo_post, methods=["POST"])


def my_serialization(content: str, **kwargs: Any) -> str:
    import json

    import yaml  # type: ignore

    return yaml.dump(json.loads(json.dumps(content, cls=config.json_encoder), **kwargs))


openapi_model = OpenAPI(app)

print("json", openapi_model.content())
print("yaml", openapi_model.content(serialization_callback=my_serialization))
for i18n_lang in ("zh-cn", "en"):
    print(f"{i18n_lang} md", Markdown(openapi_model, i18n_lang=i18n_lang).content)
docs_source_code/openapi/how_to_use_openapi/starlette_with_output_demo.py
from typing import Any, List

from any_api.openapi.to.markdown import Markdown
from pydantic import BaseModel, Field
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route

from pait.app.starlette import pait
from pait.field import Query
from pait.g import config
from pait.model.response import BaseResponseModel
from pait.openapi.openapi import OpenAPI


class MyJsonResponseModel(BaseResponseModel):
    class ResponseModel(BaseModel):
        class UserModel(BaseModel):
            name: str = Field(..., example="so1n")
            uid: int = Query.t(description="user id", gt=10, lt=1000)
            age: int = Field(..., gt=0)

        code: int = Field(..., ge=0)
        msg: str = Field(...)
        data: List[UserModel]

    class HeaderModel(BaseModel):
        x_token: str = Field(..., alias="X-Token")
        content_type: str = Field(..., alias="Content-Type")

    response_data = ResponseModel
    description = "demo json response"
    media_type = "application/json; charset=utf-8"
    header = HeaderModel
    status_code = (200, 201, 404)


@pait(response_model_list=[MyJsonResponseModel])
async def demo(
    uid: int = Query.t(description="user id", gt=10, lt=1000),
    age: int = Query.t(description="age", gt=0),
    username: str = Query.t(description="user name", min_length=2, max_length=4),
) -> JSONResponse:
    resp = JSONResponse({"code": 0, "msg": "", "data": [{"name": username, "uid": uid, "age": age}]})
    resp.headers.append("X-Token", "12345")
    resp.headers["Content-Type"] = "application/json; charset=utf-8"
    return resp


app = Starlette(routes=[Route("/api/demo", demo, methods=["GET"])])


def my_serialization(content: str, **kwargs: Any) -> str:
    import json

    import yaml  # type: ignore

    return yaml.dump(json.loads(json.dumps(content, cls=config.json_encoder), **kwargs))


openapi_model = OpenAPI(app)

print("json", openapi_model.content())
print("yaml", openapi_model.content(serialization_callback=my_serialization))
for i18n_lang in ("zh-cn", "en"):
    print(f"{i18n_lang} md", Markdown(openapi_model, i18n_lang=i18n_lang).content)
docs_source_code/openapi/how_to_use_openapi/sanic_with_output_demo.py
from typing import Any, List

from any_api.openapi.to.markdown import Markdown
from pydantic import BaseModel, Field
from sanic.app import Sanic
from sanic.response import HTTPResponse, json

from pait.app.sanic import pait
from pait.field import Query
from pait.g import config
from pait.model.response import BaseResponseModel
from pait.openapi.openapi import OpenAPI


class MyJsonResponseModel(BaseResponseModel):
    class ResponseModel(BaseModel):
        class UserModel(BaseModel):
            name: str = Field(..., example="so1n")
            uid: int = Query.t(description="user id", gt=10, lt=1000)
            age: int = Field(..., gt=0)

        code: int = Field(..., ge=0)
        msg: str = Field(...)
        data: List[UserModel]

    class HeaderModel(BaseModel):
        x_token: str = Field(..., alias="X-Token")
        content_type: str = Field(..., alias="Content-Type")

    response_data = ResponseModel
    description = "demo json response"
    media_type = "application/json; charset=utf-8"
    header = HeaderModel
    status_code = (200, 201, 404)


@pait(response_model_list=[MyJsonResponseModel])
async def demo(
    uid: int = Query.t(description="user id", gt=10, lt=1000),
    age: int = Query.t(description="age", gt=0),
    username: str = Query.t(description="user name", min_length=2, max_length=4),
) -> HTTPResponse:
    resp = json({"code": 0, "msg": "", "data": [{"name": username, "uid": uid, "age": age}]})

    resp.headers.add("X-Token", "12345")
    resp.headers["Content-Type"] = "application/json; charset=utf-8"
    return resp


app = Sanic(name="demo")
app.add_route(demo, "/api/demo", methods=["GET"])


def my_serialization(content: str, **kwargs: Any) -> str:
    import json

    import yaml  # type: ignore

    return yaml.dump(json.loads(json.dumps(content, cls=config.json_encoder), **kwargs))


openapi_model = OpenAPI(app)

print("json", openapi_model.content())
print("yaml", openapi_model.content(serialization_callback=my_serialization))
for i18n_lang in ("zh-cn", "en"):
    print(f"{i18n_lang} md", Markdown(openapi_model, i18n_lang=i18n_lang).content)
docs_source_code/openapi/how_to_use_openapi/tornado_with_output_demo.py
from typing import Any, List

from any_api.openapi.to.markdown import Markdown
from pydantic import BaseModel, Field
from tornado.web import Application, RequestHandler

from pait.app.tornado import pait
from pait.field import Query
from pait.g import config
from pait.model.response import BaseResponseModel
from pait.openapi.openapi import OpenAPI


class MyJsonResponseModel(BaseResponseModel):
    class ResponseModel(BaseModel):
        class UserModel(BaseModel):
            name: str = Field(..., example="so1n")
            uid: int = Query.t(description="user id", gt=10, lt=1000)
            age: int = Field(..., gt=0)

        code: int = Field(..., ge=0)
        msg: str = Field(...)
        data: List[UserModel]

    class HeaderModel(BaseModel):
        x_token: str = Field(..., alias="X-Token")
        content_type: str = Field(..., alias="Content-Type")

    response_data = ResponseModel
    description = "demo json response"
    media_type = "application/json; charset=utf-8"
    header = HeaderModel
    status_code = (200, 201, 404)


class DemoHandler(RequestHandler):
    @pait(response_model_list=[MyJsonResponseModel])
    def get(
        self,
        uid: int = Query.t(description="user id", gt=10, lt=1000),
        age: int = Query.t(description="age", gt=0),
        username: str = Query.t(description="user name", min_length=2, max_length=4),
    ) -> None:
        self.write({"code": 0, "msg": "", "data": [{"name": username, "uid": uid, "age": age}]})
        self.add_header("X-Token", "12345")
        self.set_header("Content-Type", "application/json; charset=utf-8")


app: Application = Application([(r"/api/demo", DemoHandler)])


def my_serialization(content: str, **kwargs: Any) -> str:
    import json

    import yaml  # type: ignore

    return yaml.dump(json.loads(json.dumps(content, cls=config.json_encoder), **kwargs))


openapi_model = OpenAPI(app)

print("json", openapi_model.content())
print("yaml", openapi_model.content(serialization_callback=my_serialization))
for i18n_lang in ("zh-cn", "en"):
    print(f"{i18n_lang} md", Markdown(openapi_model, i18n_lang=i18n_lang).content)

The first step in the sample code is to create openapi_model, which in the process of creation will get the app corresponding interface and data. The second step is to call the content method of openapi_model, which has a serialization_callback parameter with a default value of json.dump. So a direct call to openapi_model.content() will generate the following JSON text.

Json example (the example text is long, please open it as needed)
{
  "openapi": "3.0.0",
  "info": {
    "title": "AnyApi",
    "description": "API Documentation",
    "version": "0.0.1"
  },
  "servers": [],
  "paths": {
    "/api": {
      "options": {
        "tags": [
          "default"
        ],
        "summary": "",
        "description": "",
        "operationId": "demo_options",
        "parameters": [],
        "requestBody": {
          "description": "",
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Demo_Post__Main___Demo_Post"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "demo response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResponseModel"
                }
              }
            }
          }
        },
        "deprecated": false,
        "pait_info": {
          "group": "root",
          "status": "undefined",
          "author": [
            ""
          ],
          "pait_id": "__main___demo_post"
        }
      },
      "post": {
        "tags": [
          "default"
        ],
        "summary": "",
        "description": "",
        "operationId": "demo_post",
        "parameters": [],
        "requestBody": {
          "description": "",
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Demo_Post__Main___Demo_Post"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "demo response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResponseModel"
                }
              }
            }
          }
        },
        "deprecated": false,
        "pait_info": {
          "group": "root",
          "status": "undefined",
          "author": [
            ""
          ],
          "pait_id": "__main___demo_post"
        }
      }
    }
  },
  "tags": [
    {
      "name": "default",
      "description": ""
    }
  ],
  "components": {
    "schemas": {
      "Demo_Post__Main___Demo_Post": {
        "title": "Demo_Post__Main___Demo_Post",
        "type": "object",
        "properties": {
          "uid": {
            "title": "Uid",
            "description": "user id",
            "exclusiveMinimum": 10,
            "exclusiveMaximum": 1000,
            "type": "integer"
          },
          "username": {
            "title": "Username",
            "description": "user name",
            "maxLength": 4,
            "minLength": 2,
            "type": "string"
          }
        },
        "required": [
          "uid",
          "username"
        ]
      },
      "ResponseModel": {
        "title": "ResponseModel",
        "type": "object",
        "properties": {
          "uid": {
            "title": "Uid",
            "type": "integer"
          },
          "user_name": {
            "title": "User Name",
            "type": "string"
          }
        },
        "required": [
          "uid",
          "user_name"
        ]
      }
    }
  }
}

In addition, the sample code also customizes a function that serializes to yaml -- my_serialization and generates the following yaml text via openapi_model.content(serialization_callback=my_serialization):

Yaml example (the example text is long, please open it as needed)
components:
  schemas:
    Demo_Post__Main___Demo_Post:
      properties:
        uid:
          description: user id
          exclusiveMaximum: 1000
          exclusiveMinimum: 10
          title: Uid
          type: integer
        username:
          description: user name
          maxLength: 4
          minLength: 2
          title: Username
          type: string
      required:
      - uid
      - username
      title: Demo_Post__Main___Demo_Post
      type: object
    ResponseModel:
      properties:
        uid:
          title: Uid
          type: integer
        user_name:
          title: User Name
          type: string
      required:
      - uid
      - user_name
      title: ResponseModel
      type: object
info:
  description: API Documentation
  title: AnyApi
  version: 0.0.1
openapi: 3.0.0
paths:
  /api:
    options:
      deprecated: false
      description: ''
      operationId: demo_options
      pait_info:
        author:
        - ''
        group: root
        pait_id: __main___demo_post
        status: undefined
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Demo_Post__Main___Demo_Post'
        description: ''
        required: false
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResponseModel'
          description: demo response
      summary: ''
      tags:
      - default
    post:
      deprecated: false
      description: ''
      operationId: demo_post
      pait_info:
        author:
        - ''
        group: root
        pait_id: __main___demo_post
        status: undefined
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Demo_Post__Main___Demo_Post'
        description: ''
        required: false
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResponseModel'
          description: demo response
      summary: ''
      tags:
      - default
servers: []
tags:
- description: ''
  name: default

Finally, the Markdown method of AnyAPI is also used to generate Markdown documents in different languages.

Chinese Markdown example(The sample text is long, please open it as needed, and only show the original data)
# AnyApi
### 名称: demo.demo
**标签**: default
- 路径: /api/demo
- 方法: get
- 请求:
    **query**

     |名称|默认|类型|描述|示例|其它|
     |---|---|---|---|---|---|
     |uid|`必填`|integer|user id||exclusiveMinimum:10;<br>exclusiveMaximum:1000|
     |age|`必填`|integer|age||exclusiveMinimum:0|
     |username|`必填`|string|user name||maxLength:4;<br>minLength:2|


- 响应
**描述**: demo json response
*Header*

    - 200:application/json; charset=utf-8
    **响应信息**

         |名称|默认|类型|描述|示例|其它|
         |---|---|---|---|---|---|
         |code|`必填`|integer|||minimum:0|
         |msg|`必填`|string||||
         |data|`必填`|array|||items:{'$ref': '#/components/schemas/UserModel'}|

        **响应示例**

        ```json
        {
            "code": 0,
            "msg": "",
            "data": {
                "name": "so1n",
                "uid": 0,
                "age": 0
            }
        }
        ```
**描述**: demo json response
*Header*

    - 201:application/json; charset=utf-8
    **响应信息**

         |名称|默认|类型|描述|示例|其它|
         |---|---|---|---|---|---|
         |code|`必填`|integer|||minimum:0|
         |msg|`必填`|string||||
         |data|`必填`|array|||items:{'$ref': '#/components/schemas/UserModel'}|

        **响应示例**

        ```json
        {
            "code": 0,
            "msg": "",
            "data": {
                "name": "so1n",
                "uid": 0,
                "age": 0
            }
        }
        ```
**描述**: demo json response
*Header*

    - 404:application/json; charset=utf-8
    **响应信息**

         |名称|默认|类型|描述|示例|其它|
         |---|---|---|---|---|---|
         |code|`必填`|integer|||minimum:0|
         |msg|`必填`|string||||
         |data|`必填`|array|||items:{'$ref': '#/components/schemas/UserModel'}|

        **响应示例**

        ```json
        {
            "code": 0,
            "msg": "",
            "data": {
                "name": "so1n",
                "uid": 0,
                "age": 0
            }
        }
        ```
English Markdown example (the sample text is long, please open it as needed, and only display the original data)
# AnyApi
### Name: demo.demo
**Tag**: default
- Path: /api/demo
- Method: get
- Request:
    **query**

     |Name|Default|Type|Desc|Example|Other|
     |---|---|---|---|---|---|
     |uid|`Required`|integer|user id||exclusiveMinimum:10;<br>exclusiveMaximum:1000|
     |age|`Required`|integer|age||exclusiveMinimum:0|
     |username|`Required`|string|user name||maxLength:4;<br>minLength:2|


- Response
**Desc**: demo json response
*Header*

    - 200:application/json; charset=utf-8
    **Response Info**

         |Name|Default|Type|Desc|Example|Other|
         |---|---|---|---|---|---|
         |code|`Required`|integer|||minimum:0|
         |msg|`Required`|string||||
         |data|`Required`|array|||items:{'$ref': '#/components/schemas/UserModel'}|

        **Response Example**

        ```json
        {
            "code": 0,
            "msg": "",
            "data": {
                "name": "so1n",
                "uid": 0,
                "age": 0
            }
        }
        ```
**Desc**: demo json response
*Header*

    - 201:application/json; charset=utf-8
    **Response Info**

         |Name|Default|Type|Desc|Example|Other|
         |---|---|---|---|---|---|
         |code|`Required`|integer|||minimum:0|
         |msg|`Required`|string||||
         |data|`Required`|array|||items:{'$ref': '#/components/schemas/UserModel'}|

        **Response Example**

        ```json
        {
            "code": 0,
            "msg": "",
            "data": {
                "name": "so1n",
                "uid": 0,
                "age": 0
            }
        }
        ```
**Desc**: demo json response
*Header*

    - 404:application/json; charset=utf-8
    **Response Info**

         |Name|Default|Type|Desc|Example|Other|
         |---|---|---|---|---|---|
         |code|`Required`|integer|||minimum:0|
         |msg|`Required`|string||||
         |data|`Required`|array|||items:{'$ref': '#/components/schemas/UserModel'}|

        **Response Example**

        ```json
        {
            "code": 0,
            "msg": "",
            "data": {
                "name": "so1n",
                "uid": 0,
                "age": 0
            }
        }
        ```