Skip to content

Other

1.Implicit or explicit import

pait supports multiple web frameworks and if only one of them is installed in the project's dependency environment, can use implicit import:

from pait.app.any import pait, load_app, add_simple_route
But if multiple frameworks are installed at the same time, then the implicit import will throw an exception, and it is recommended to use explicit import, as follows:
from pait.app.flask import pait, load_app, add_simple_route
from pait.app.sanic import pait, load_app, add_simple_route
from pait.app.starlette import pait, load_app, add_simple_route
from pait.app.tornado import pait, load_app, add_simple_route

2.Internal Methods

Pait encapsulates a number of common methods. Through these methods developers can quickly develop extension packages without considering compatibility with different web frameworks. OpenAPI routing and grpc-gateway are developed based on these methods.

2.1.data

data is the carrier for each CoreModel. Pait decorates the route function to generate a CoreModel and store it in pait.g.data to support for configuration, documentation, etc feature.

2.2.load_app

The CoreModel stores a lot of information about the route functions, but the route functions are missing key OpenAPI information such as url, method, etc. So you need to use load_app to get more data before using OpenAPI. So before using OpenAPI you need to use load_app to fill in the data, it's very simple to use, but you need to call it after registering all the routes, as follows.

Note

OpenAPI routing automatically calls load_app before initialization

from flask import Flask

from pait.app.flask import load_app

app: Flask = Flask()

load_app(app) # Wrong!!!
# --------
# app.add_url_rule
# --------

load_app(app) #  That's right
app.run()
import uvicorn
from starlette.applications import Starlette

from pait.app.starlette import load_app

app: Starlette = Starlette()
load_app(app) # Wrong!!!
# --------
# app.add_route
# --------

load_app(app) #  That's right
uvicorn.run(app)
from sanic import Sanic

from pait.app.sanic import load_app

app: Sanic = Sanic()
load_app(app) # Wrong!!!

# --------
# app.add_route
# --------

load_app(app) #  That's right
app.run()
from tornado.web import Application
from tornado.ioloop import IOLoop

from pait.app.tornado import load_app

app: Application = Application()
load_app(app) # Wrong!!!
# --------
# app.add_handlers
# --------
load_app(app) #  That's right
app.listen(8000)
IOLoop.instance().start()

2.3.HTTP exceptions

Pait provides an HTTP exception generator function for each web framework, which generates HTTP standard exceptions for web frameworks by parameters such as HTTP status code, error content, Headers, etc. They are used as follows.

from pait.app.flask import http_exception

http_exception(status_code=401, message="Unauthorized", headers={"WWW-Authenticate": "Basic"})
from pait.app.sanic import http_exception

http_exception(status_code=401, message="Unauthorized", headers={"WWW-Authenticate": "Basic"})
from pait.app.starlette import http_exception

http_exception(status_code=401, message="Unauthorized", headers={"WWW-Authenticate": "Basic"})
from pait.app.tornado import http_exception

http_exception(status_code=401, message="Unauthorized", headers={"WWW-Authenticate": "Basic"})

In addition, Pait provides some HTTP exception responses as follows:

from pait.app.any import pait
from pait.model import response

# response.Http400RespModel
# response.Http401RespModel
# response.Http403RespModel
# response.Http404RespModel
# response.Http405RespModel
# response.Http406RespModel
# response.Http407RespModel
# response.Http408RespModel
# response.Http429RespModel

@pait(response_model_list=[response.Http400RespModel])
def demo() -> None:
    pass
At the same time HTTP exception response Model also supports custom creation, the following example of use:
from pait.model import response

# Create a response model with a status code of 500 and content-type as html
response.HttpStatusCodeBaseModel.clone(resp_model=response.HtmlResponseModel, status_code=500)
# Create a response model with status code 500 and content-type set to text
response.HttpStatusCodeBaseModel.clone(resp_model=response.TextResponseModel, status_code=500)

2.4.SimpleRoute

Pait unifies the route registration and response generation of different web frameworks through SimpleRoute. Developers can easily create and register routes through SimpleRoute without considering compatibility.

Note

Unified route response generation is provided by the UnifiedResponsePluginProtocol plugin. The UnifiedResponsePluginProtocol plugin is added to the route function when the route function is registered.

SimpleRoute is used as follows:

docs_source_code/other/flask_with_simple_route_demo.py
from flask import Flask

from pait.app.any import SimpleRoute, add_multi_simple_route, add_simple_route
from pait.app.flask import pait
from pait.model import response


@pait(response_model_list=[response.JsonResponseModel])
def json_route() -> dict:
    return {}


@pait(response_model_list=[response.TextResponseModel])
def text_route() -> str:
    return "demo"


@pait(response_model_list=[response.HtmlResponseModel])
def html_route() -> str:
    return "<h1>demo</h1>"


app: Flask = Flask("demo")
add_simple_route(app, SimpleRoute(route=json_route, url="/json", methods=["GET"]))
add_multi_simple_route(
    app,
    SimpleRoute(route=json_route, url="/json", methods=["GET"]),
    SimpleRoute(route=text_route, url="/text", methods=["GET"]),
    SimpleRoute(route=html_route, url="/html", methods=["GET"]),
    prefix="/api",
    title="api",
)


if __name__ == "__main__":
    app.run(port=8000)
docs_source_code/other/starlette_with_simple_route_demo.py
from starlette.applications import Starlette

from pait.app.any import SimpleRoute, add_multi_simple_route, add_simple_route
from pait.app.starlette import pait
from pait.model import response


@pait(response_model_list=[response.JsonResponseModel])
async def json_route() -> dict:
    return {}


@pait(response_model_list=[response.TextResponseModel])
async def text_route() -> str:
    return "demo"


@pait(response_model_list=[response.HtmlResponseModel])
async def html_route() -> str:
    return "<h1>demo</h1>"


app: Starlette = Starlette()
add_simple_route(app, SimpleRoute(route=json_route, url="/json", methods=["GET"]))
add_multi_simple_route(
    app,
    SimpleRoute(route=json_route, url="/json", methods=["GET"]),
    SimpleRoute(route=text_route, url="/text", methods=["GET"]),
    SimpleRoute(route=html_route, url="/html", methods=["GET"]),
    prefix="/api",
    title="api",
)


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/other/sanic_with_simple_route_demo.py
from sanic import Sanic
from sanic.request import Request

from pait.app.any import SimpleRoute, add_multi_simple_route, add_simple_route
from pait.app.sanic import pait
from pait.model import response


@pait(response_model_list=[response.JsonResponseModel])
async def json_route(request: Request) -> dict:
    return {}


@pait(response_model_list=[response.TextResponseModel])
async def text_route(request: Request) -> str:
    return "demo"


@pait(response_model_list=[response.HtmlResponseModel])
async def html_route(request: Request) -> str:
    return "<h1>demo</h1>"


app: Sanic = Sanic("demo")
add_simple_route(app, SimpleRoute(route=json_route, url="/json", methods=["GET"]))
add_multi_simple_route(
    app,
    SimpleRoute(route=json_route, url="/json", methods=["GET"]),
    SimpleRoute(route=text_route, url="/text", methods=["GET"]),
    SimpleRoute(route=html_route, url="/html", methods=["GET"]),
    prefix="/api",
    title="api",
)


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/other/tornado_with_simple_route_demo.py
from tornado.ioloop import IOLoop
from tornado.web import Application

from pait.app.any import SimpleRoute, add_multi_simple_route, add_simple_route
from pait.app.tornado import pait
from pait.model import response


@pait(response_model_list=[response.JsonResponseModel])
async def json_route() -> dict:
    return {}


@pait(response_model_list=[response.TextResponseModel])
async def text_route() -> str:
    return "demo"


@pait(response_model_list=[response.HtmlResponseModel])
async def html_route() -> str:
    return "<h1>demo</h1>"


app: Application = Application()
add_simple_route(app, SimpleRoute(route=json_route, url="/json", methods=["GET"]))
add_multi_simple_route(
    app,
    SimpleRoute(route=json_route, url="/json", methods=["GET"]),
    SimpleRoute(route=text_route, url="/text", methods=["GET"]),
    SimpleRoute(route=html_route, url="/html", methods=["GET"]),
    prefix="/api",
    title="api",
)


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

The first highlighted code creates three route functions according to the SimpleRoute standard, which is as follows:

  • 1.The route functions need to be decorated by pait, and the response_model_list attribute cannot be empty (the response models of the route functions in the code are JsonResponseModel, TextResponseModel, HtmlResponseModel, these are all required by SimpleRoute, if there is no response model, then SimpleRoute can't register the route function to the web framework.)
  • 2.The return value of the route function changes from a response object to a Python base type, and the returned Python base type needs to be consistent with the response_data of the response model.

The second highlight code is the registration of routes via the add_simple_route and add_multi_simple_route methods, where add_simple_route can only register a single route and add_multi_simple_route can register multiple routes. Both add_simple_route and add_multi_simple_route receive app and SimpleRoute instances, whereas SimpleRoute supports only three attributes, as follows:

Parameters Description
route A route function that conforms to the SimpleRoute standard
url The URL of the route
method HTTP method of the route

In addition, add_multi_simple_route supports two optional parameters, as follows:

Parameters Description
prefix Route prefix, for example, if the prefix is "/api" and the url of SimpleRoute is "/user", the registered route URL is "/api/user".
title The title of the route group. For some frameworks, the route groups or blueprints they use need to be uniquely named, so the title should be different for different add_multi_simple_routes

After running the code, test that the route works properly via the curl command:

curl http://127.0.0.1:8000/json{} curl http://127.0.0.1:8000/api/json{} curl http://127.0.0.1:8000/api/textdemo curl http://127.0.0.1:8000/api/html<h1>demo</h1>

2.5.Set and get web framework properties

Pait provides a unified method for setting and getting attribute values for Web frameworks, which are set_app_attribute and get_app_attribute. The set_app_attribute and get_app_attribute can be used to set and get Web framework attributes at any time, as follows:

docs_source_code/other/flask_with_attribute_demo.py
import httpx
from flask import Flask, Response, current_app, jsonify

from pait.app.any import get_app_attribute, set_app_attribute


def demo_route() -> Response:
    client: httpx.Client = get_app_attribute(current_app, "client")
    return jsonify({"status_code": client.get("http://so1n.me").status_code})


app: Flask = Flask("demo")
app.add_url_rule("/api/demo", "demo", demo_route, methods=["GET"])
set_app_attribute(app, "client", httpx.Client())


if __name__ == "__main__":
    app.run(port=8000)
docs_source_code/other/starlette_with_attribute_demo.py
import httpx
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse

from pait.app.any import get_app_attribute, set_app_attribute


async def demo_route(request: Request) -> JSONResponse:
    client: httpx.AsyncClient = get_app_attribute(request.app, "client")
    return JSONResponse({"status_code": (await client.get("http://so1n.me")).status_code})


app: Starlette = Starlette()
app.add_route("/api/demo", demo_route, methods=["GET"])
set_app_attribute(app, "client", httpx.AsyncClient())


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/other/sanic_with_attribute_demo.py
import httpx
from sanic import HTTPResponse, Request, Sanic, json

from pait.app.any import get_app_attribute, set_app_attribute


async def demo_route(request: Request) -> HTTPResponse:
    client: httpx.AsyncClient = get_app_attribute(request.app, "client")
    return json({"status_code": (await client.get("http://so1n.me")).status_code})


app: Sanic = Sanic("demo")
app.add_route(demo_route, "/api/demo", methods=["GET"])
set_app_attribute(app, "client", httpx.AsyncClient())


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/other/tornado_with_attribute_demo.py
import httpx
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler

from pait.app.any import get_app_attribute, set_app_attribute


class DemoHandler(RequestHandler):
    async def get(self) -> None:
        client: httpx.AsyncClient = get_app_attribute(self.application, "client")
        self.write({"status_code": (await client.get("http://so1n.me")).status_code})


app: Application = Application()
app.add_handlers(".*$", [("/api/demo", DemoHandler)])
set_app_attribute(app, "client", httpx.AsyncClient())


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

After running the code, test that the route works properly via the curl command:

  curl http://127.0.0.1:8000/api/demo
{"status_code": 200}
As you can see by the result, the route function is able to get client and through client get the status_code of the url.

Note

By setting property values for the web framework, can decouple the component from the framework and also make the component more flexible, but it is more recommended to use DI tools to realize decoupling, see Awesome Dependency Injection in Python.

3.How to use Pait with other web frameworks

Currently, Pait is still in the process of rapid iteration, so the main focus is on feature development. If you want to use Pait in other frameworks that are not yet supported, or if you want to extend the functionality, can refer to the two frameworks to make simple adaptations.

For synchronous web frameworks, please refer to pait.app.flask.

For asynchronous web framework, please refer to pait.app.starlette.

4.Example code

See example for more complete examples.

5.Release

For detailed release info, please see CHANGELOG