Skip to content

Introduction

Pait is an auxiliary framework and it will not change the usage of Web frameworks. Therefore, before introducing the use of Pait, let's first take a look at the usage of different Web frameworks.

docs_source_code/introduction/flask_hello_world_demo.py
from flask import Flask, Response, jsonify, request


def demo_post() -> Response:
    request_dict = request.json or {}
    uid_str: str = request_dict.get("uid", "")
    username: str = request_dict.get("username", "")

    uid = int(uid_str)  # Don't think about type conversion exceptions for now
    if not (10 < uid < 1000):
        raise ValueError("invalid uid")
    if not (2 <= len(username) <= 4):
        raise ValueError("invalid name")
    # get the corresponding value and return it
    return jsonify({"uid": uid, "username": username})


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


if __name__ == "__main__":
    app.run(port=8000)
docs_source_code/introduction/starlette_hello_world_demo.py
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route


async def demo_post(request: Request) -> JSONResponse:
    request_dict = await request.json()
    uid_str: str = request_dict.get("uid", "")
    username: str = request_dict.get("username", "")

    uid = int(uid_str)  # Don't think about type conversion exceptions for now
    if not (10 < uid < 1000):
        raise ValueError("invalid uid")
    if not (2 <= len(username) <= 4):
        raise ValueError("invalid name")
    # get the corresponding value and return it
    return JSONResponse({"uid": uid, "username": username})


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


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/introduction/sanic_hello_world_demo.py
from sanic.app import Sanic
from sanic.request import Request
from sanic.response import HTTPResponse, json


async def demo_post(request: Request) -> HTTPResponse:
    request_dict = await request.json()
    uid_str: str = request_dict.get("uid", "")
    username: str = request_dict.get("username", "")

    uid = int(uid_str)  # Don't think about type conversion exceptions for now
    if not (10 < uid < 1000):
        raise ValueError("invalid uid")
    if not (2 <= len(username) <= 4):
        raise ValueError("invalid name")
    # get the corresponding value and return it
    return json({"uid": uid, "username": username})


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


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/introduction/tornado_hello_world_demo.py
import json

from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler


class DemoHandler(RequestHandler):
    def post(self) -> None:
        request_dict = json.loads(self.request.body.decode())
        uid_str: str = request_dict.get("uid", "")
        username: str = request_dict.get("username", "")

        uid = int(uid_str)  # Don't think about type conversion exceptions for now
        if not (10 < uid < 1000):
            raise ValueError("invalid uid")
        if not (2 <= len(username) <= 4):
            raise ValueError("invalid name")
        # get the corresponding value and return it
        self.write({"uid": uid, "username": username})


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


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

The logic of this sample code is consistent with the sample code on the homepage. The main feature of the sample code is to register a route into an instance of the Web framework at startup, and after receiving a request with the URL /api and method POST at runtime, the request will be handed over to the route function for processing. The logic of the route function is also very simple. It will verify the data first and return the data only if it meets the requirements. Otherwise, an error will be thrown directly.

Next, we will use Pait in the example code. Their functions are the same, as follows:

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


@pait()
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"])
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


@pait()
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"])])

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


@pait()
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"])

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()

The @pait decorator of the first highlighted code in the sample is the core of all functions of Pait. After using @pait to decorate the route function, Pait will get the function signature through inspect and generate Dependency injection rules. For example, the parameters of the route function in the second highlighted code are all filled in with key parameters in the format of <name>:<type>=<default>. Pait automatically transforms key parameters into its own dependency injection rules at initialization time with the following rules:

Key Meaning Feature
name Parameter Name Pait will use name as the key to get the corresponding value from the requested resource.
type Parameter Type Used for parameter verification or conversion
default Field object of Pait Different Field types represent obtaining values from different request types; the properties of the Field object tell Pait how to get the value from the request and verify it.

Taking the uid parameter above as an example, first, Pait will get json data from the request obj. Second , use uid as the key to get the corresponding value from the json data and convert and verify whether it isint type. Last, determine whether the value is between 10-1000, if not, an error will be reported directly, if so, it will be assigned to the uid variable.

By comparing the first sample code with the code after using Pait,can see that the code after using Pait is simpler, clearer and also improves the robustness of the code.

Note

When using Json() , mypy will detect a type mismatch, so you can ignore this problem through Json.i(). If you need mypy to check the values of default, default_factory and example attributes in Json, it is recommended to use Json.t() directly.