Skip to content

How to use Type

The type of the variable - Type is used to indicate the final type of the variable value. By customizing Type, can expand the verification rules of the variable, as shown in the following example code:

from pait.app.any import pait
from pait import field

@pait()
def demo(
    a: str = field.Body.i(),
    b: int = field.Body.i(),
) -> dict:
    return {"a": a, "b": b}
When the code is run, Pait will internally convert the function signature into the following Pydantic.BaseModel:
from pydantic import BaseModel, Field

class Demo(BaseModel):
    a: str = Field()
    b: int = Field()

Therefore, the Type in the route function can become very flexible. Can use Type like Pydantic Field Types. It is also possible to use some of the features of Pait by writing a Type that conforms to the Pait specification.

1.The value of Type is Pydantic.BaseModel

After a period of development, will find that some route function parameters can be reused. In this case, can use the scheme where the value of Type is Pydantic.BaseModel to convert the parameters of the route function into pydantic.Basemodel.

The following sample code, in the first highlighted code there is a class called DemoModel, it has uid, name and age three attributes. In addition to this, there are two different route functions demo and demo1 in the code. The demo takes all the values from the Url Path and passes them to the DemoModel for validation before returning the data generated by the .dict method. While demo1 is very similar to the route function demo, but the source of the data is changed from Url Path to Json Body.

docs_source_code/introduction/how_to_use_type/flask_with_model_demo.py
from flask import Flask, Response, jsonify
from pydantic import BaseModel, Field

from pait import _pydanitc_adapter, field
from pait.app.flask import pait

if _pydanitc_adapter.is_v1:

    class DemoModel(BaseModel):
        uid: str = Field(max_length=6, min_length=6, regex="^u")
        name: str = Field(min_length=4, max_length=10)
        age: int = Field(ge=0, le=100)

else:

    class DemoModel(BaseModel):  # type: ignore
        uid: str = Field(max_length=6, min_length=6, pattern="^u")
        name: str = Field(min_length=4, max_length=10)
        age: int = Field(ge=0, le=100)


@pait()
def demo(demo_model: DemoModel = field.Query.i(raw_return=True)) -> Response:
    return jsonify(demo_model.dict())


@pait()
def demo1(demo_model: DemoModel = field.Json.i(raw_return=True)) -> Response:
    return jsonify(demo_model.dict())


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


if __name__ == "__main__":
    app.run(port=8000)
docs_source_code/introduction/how_to_use_type/starlette_with_model_demo.py
from pydantic import BaseModel, Field
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route

from pait import _pydanitc_adapter, field
from pait.app.starlette import pait

if _pydanitc_adapter.is_v1:

    class DemoModel(BaseModel):
        uid: str = Field(max_length=6, min_length=6, regex="^u")
        name: str = Field(min_length=4, max_length=10)
        age: int = Field(ge=0, le=100)

else:

    class DemoModel(BaseModel):  # type: ignore
        uid: str = Field(max_length=6, min_length=6, pattern="^u")
        name: str = Field(min_length=4, max_length=10)
        age: int = Field(ge=0, le=100)


@pait()
async def demo(demo_model: DemoModel = field.Query.i(raw_return=True)) -> JSONResponse:
    return JSONResponse(demo_model.dict())


@pait()
async def demo1(demo_model: DemoModel = field.Json.i(raw_return=True)) -> JSONResponse:
    return JSONResponse(demo_model.dict())


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


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/introduction/how_to_use_type/sanic_with_model_demo.py
from pydantic import BaseModel, Field
from sanic import Sanic, json, response

from pait import _pydanitc_adapter, field
from pait.app.sanic import pait

if _pydanitc_adapter.is_v1:

    class DemoModel(BaseModel):
        uid: str = Field(max_length=6, min_length=6, regex="^u")
        name: str = Field(min_length=4, max_length=10)
        age: int = Field(ge=0, le=100)

else:

    class DemoModel(BaseModel):  # type: ignore
        uid: str = Field(max_length=6, min_length=6, pattern="^u")
        name: str = Field(min_length=4, max_length=10)
        age: int = Field(ge=0, le=100)


@pait()
async def demo(demo_model: DemoModel = field.Query.i(raw_return=True)) -> response.HTTPResponse:
    return json(demo_model.dict())


@pait()
async def demo1(demo_model: DemoModel = field.Json.i(raw_return=True)) -> response.HTTPResponse:
    return json(demo_model.dict())


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


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/introduction/how_to_use_type/tornado_with_model_demo.py
from pydantic import BaseModel, Field
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler

from pait import _pydanitc_adapter, field
from pait.app.tornado import pait

if _pydanitc_adapter.is_v1:

    class DemoModel(BaseModel):
        uid: str = Field(max_length=6, min_length=6, regex="^u")
        name: str = Field(min_length=4, max_length=10)
        age: int = Field(ge=0, le=100)

else:

    class DemoModel(BaseModel):  # type: ignore
        uid: str = Field(max_length=6, min_length=6, pattern="^u")
        name: str = Field(min_length=4, max_length=10)
        age: int = Field(ge=0, le=100)


class DemoHandler(RequestHandler):
    @pait()
    def get(self, demo_model: DemoModel = field.Query.i(raw_return=True)) -> None:
        self.write(demo_model.dict())


class Demo1Handler(RequestHandler):
    @pait()
    def post(self, demo_model: DemoModel = field.Json.i(raw_return=True)) -> None:
        self.write(demo_model.dict())


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


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

Note

If the raw_return of the Field object is not True, Pait will obtain the value from the requested resource with the Key as demo_model

Next run the code and call the curl command:

  ~ curl "http://127.0.0.1:8000/api/demo?uid=u12345&name=so1n&age=10"
{"uid":"u12345","name":"so1n","age":10}
  ~ curl "http://127.0.0.1:8000/api/demo1" -X POST -d '{"uid": "u12345", "name": "so1n", "age": 10}' --header "Content-Type: application/json"
{"uid":"u12345","name":"so1n","age":10}

The output shows that both route functions work fine, but in this case all the fields of BaseModel can only use the same Field type, so another approach can be taken.

2.The value of Type is the special Pydantic.BaseModel

The Field object of Pait is a pydantic.FieldInfo object that carries the resource identifier, so it can be used in pydantic.BaseModel. In this way the DemoModel object mentioned earlier can be rewritten as follows.

from pait import field

from pydantic import BaseModel

class DemoModel(BaseModel):
    uid: str = field.Query.i(max_length=6, min_length=6, regex="^u")
    name: str = field.Body.i(min_length=4, max_length=10)
    age: int = field.Header.i(ge=0, le=100)
can see that the Field object is different for each property of DemoModel, but in order for Pait to load DemoModel correctly the form of the parameters filled in needs to be changed from <name>:<type>=<default> to <name>:<type>. as follows:
@pait()
def demo(demo_model: DemoModel) -> None:
    pass

2.1.DefaultField

In addition, can also use the DefaultField of Pait, which can automatically replace the Field of each attribute in pydantic.BaseModel according to the Field defined by the route function. The following is still the same Ordinary DemoModel:

from pydantic import BaseModel, Field

class DemoModel(BaseModel):
    uid: str = Field(max_length=6, min_length=6, regex="^u")
    name: str = Field(min_length=4, max_length=10)
    age: int = Field(ge=0, le=100)
Then, The DefaultField for the demo route is specified as Query and the DefaultField for the demo1 route is specified as Body via the default_field_class attribute in the pait decorator:
from pait import field
from pait.app.any import pait

@pait(default_field_class=field.Query)
def demo(demo_model: DemoModel) -> None:
    pass

@pait(default_field_class=field.Body)
def demo1(demo_model: DemoModel) -> None:
    pass
In this way, it is possible to use the same DemoModel in a route function that uses a different request resource, the complete code is as follows:

docs_source_code/introduction/how_to_use_type/flask_with_pait_model_demo.py
from uuid import uuid4

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

from pait import _pydanitc_adapter, field
from pait.app.flask import pait

if _pydanitc_adapter.is_v1:

    class DemoModel(BaseModel):
        uid: str = Field(..., max_length=6, min_length=6, regex="^u")
        name: str = Field(..., min_length=4, max_length=10)
        age: int = Field(..., ge=0, le=100)

        request_id: str = field.Header.i(default_factory=lambda: str(uuid4()))

else:

    class DemoModel(BaseModel):  # type: ignore
        uid: str = Field(..., max_length=6, min_length=6, pattern="^u")
        name: str = Field(..., min_length=4, max_length=10)
        age: int = Field(..., ge=0, le=100)

        request_id: str = field.Header.i(default_factory=lambda: str(uuid4()))


@pait(default_field_class=field.Query)
def demo(demo_model: DemoModel) -> Response:
    return jsonify(demo_model.dict())


@pait(default_field_class=field.Body)
def demo1(demo_model: DemoModel) -> Response:
    return jsonify(demo_model.dict())


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


if __name__ == "__main__":
    app.run(port=8000)
docs_source_code/introduction/how_to_use_type/starlette_with_pait_model_demo.py
from uuid import uuid4

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

from pait import _pydanitc_adapter, field
from pait.app.starlette import pait

if _pydanitc_adapter.is_v1:

    class DemoModel(BaseModel):
        uid: str = Field(..., max_length=6, min_length=6, regex="^u")
        name: str = Field(..., min_length=4, max_length=10)
        age: int = Field(..., ge=0, le=100)

        request_id: str = field.Header.i(default_factory=lambda: str(uuid4()))

else:

    class DemoModel(BaseModel):  # type: ignore
        uid: str = Field(..., max_length=6, min_length=6, pattern="^u")
        name: str = Field(..., min_length=4, max_length=10)
        age: int = Field(..., ge=0, le=100)

        request_id: str = field.Header.i(default_factory=lambda: str(uuid4()))


@pait(default_field_class=field.Query)
async def demo(demo_model: DemoModel) -> JSONResponse:
    return JSONResponse(demo_model.dict())


@pait(default_field_class=field.Body)
async def demo1(demo_model: DemoModel) -> JSONResponse:
    return JSONResponse(demo_model.dict())


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


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/introduction/how_to_use_type/sanic_with_pait_model_demo.py
from uuid import uuid4

from pydantic import BaseModel, Field
from sanic import Sanic, json, response

from pait import _pydanitc_adapter, field
from pait.app.sanic import pait

if _pydanitc_adapter.is_v1:

    class DemoModel(BaseModel):
        uid: str = Field(..., max_length=6, min_length=6, regex="^u")
        name: str = Field(..., min_length=4, max_length=10)
        age: int = Field(..., ge=0, le=100)

        request_id: str = field.Header.i(default_factory=lambda: str(uuid4()))

else:

    class DemoModel(BaseModel):  # type: ignore
        uid: str = Field(..., max_length=6, min_length=6, pattern="^u")
        name: str = Field(..., min_length=4, max_length=10)
        age: int = Field(..., ge=0, le=100)

        request_id: str = field.Header.i(default_factory=lambda: str(uuid4()))


@pait(default_field_class=field.Query)
async def demo(demo_model: DemoModel) -> response.HTTPResponse:
    return json(demo_model.dict())


@pait(default_field_class=field.Body)
async def demo1(demo_model: DemoModel) -> response.HTTPResponse:
    return json(demo_model.dict())


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


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/introduction/how_to_use_type/tornado_with_pait_model_demo.py
from uuid import uuid4

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

from pait import _pydanitc_adapter, field
from pait.app.tornado import pait

if _pydanitc_adapter.is_v1:

    class DemoModel(BaseModel):
        uid: str = Field(..., max_length=6, min_length=6, regex="^u")
        name: str = Field(..., min_length=4, max_length=10)
        age: int = Field(..., ge=0, le=100)

        request_id: str = field.Header.i(default_factory=lambda: str(uuid4()))

else:

    class DemoModel(BaseModel):  # type: ignore
        uid: str = Field(..., max_length=6, min_length=6, pattern="^u")
        name: str = Field(..., min_length=4, max_length=10)
        age: int = Field(..., ge=0, le=100)

        request_id: str = field.Header.i(default_factory=lambda: str(uuid4()))


class DemoHandler(RequestHandler):
    @pait(default_field_class=field.Query)
    def get(self, demo_model: DemoModel) -> None:
        self.write(demo_model.dict())


class Demo1Handler(RequestHandler):
    @pait(default_field_class=field.Body)
    def post(self, demo_model: DemoModel) -> None:
        self.write(demo_model.dict())


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


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

The DemoModel in the code adds a property named request_id, which uses a Field object that is a Header object. Since the Default Field function will only replace pydantic.FieldInfo, this means that the request_id property will not be affected by Default Field during runtime, it will still get the data from the Header. Now running the code and calling the curl command to see the following output:

  ~ curl "http://127.0.0.1:8000/api/demo?uid=u12345&name=so1n&age=10"
{"age":10,"name":"so1n","request_id":"6a5827f7-5767-46ef-91c6-f2ae99770865","uid":"u12345"}
  ~ curl "http://127.0.0.1:8000/api/demo1" -X POST -d '{"uid": "u12345", "name": "so1n", "age": 10}' --header "Content-Type: application/json"
{"age":10,"name":"so1n","request_id":"3279944c-6de7-4270-8536-33619641f25e","uid":"u12345"}

Note

Note that under normal circumstances, Pait processes/checks the values of each variable in the order of the variables and throws an error if it finds an illegitimate value, and doesn't process the rest of the values. However, when the value of Type is pydantic.BaseModel, Pait will delegate the parameter to pydantic.BaseModel, and pydantic.BaseModel will check all the values, and then throw the error.

3.Other

3.1.Request Obj

When using Pait, the Request object is used significantly less often, so Pait automatically omits the Request object, for example, the Starlette framework route function code is as follows:

from starlette.requests import Request


async def demo(request: Request) -> None:
    pass
After using Pait in the route function, the code is as follows:
from pait.app.starlette import pait


@pait()
async def demo():
    pass

Note

Note:The Sanic framework requires that route functions must have at least one parameter.

If need to use the Request object in the route function, need to define the route function parameters in the form of <var name>: <RequestType> so that pait can correctly inject the Request object into the corresponding variable. For example:

docs_source_code/introduction/how_to_use_type/flask_with_request_demo.py
from flask import Flask, Request, Response, jsonify

from pait.app.flask import pait


@pait()
def demo(req: Request) -> Response:
    return jsonify({"url": req.path, "method": req.method})


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


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

from pait.app.starlette import pait


@pait()
async def demo(req: Request) -> JSONResponse:
    return JSONResponse({"url": str(req.url.path), "method": req.method})


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


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/introduction/how_to_use_type/sanic_with_request_demo.py
from sanic import Request, Sanic, json, response

from pait.app.sanic import pait


@pait()
async def demo(req: Request) -> response.HTTPResponse:
    return json({"url": req.path, "method": req.method})


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


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/introduction/how_to_use_type/tornado_with_request_demo.py
from tornado.httputil import HTTPServerRequest
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler

from pait.app.tornado import pait


class DemoHandler(RequestHandler):
    @pait()
    def get(self, req: HTTPServerRequest) -> None:
        self.write({"url": req.uri, "method": req.method})


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


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

Run the code and use the curl command to see the following output:

curl "http://127.0.0.1:8000/api/demo"
{"url": "http://127.0.0.1:8000/api/demo", "method": "GET"}

3.2.How to customize a Type that meets Pydantic requirements and has verification function

As mentioned earlier, the Type used in a Pait decorated route function does the same thing as the Type of Pydantic, which means that the Type can be used to extend the checking rules to make up for the lack of a Field object, For example, in a business where users may be located in different countries, it is common to use timestamps to pass the time in order to prevent data errors caused by different time zones, as follows:

docs_source_code/introduction/how_to_use_type/flask_with_datetime_demo.py
import datetime

from flask import Flask, Response, jsonify

from pait import field
from pait.app.flask import pait


@pait()
def demo(timestamp: datetime.datetime = field.Query.i()) -> Response:
    return jsonify({"time": timestamp.isoformat()})


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


if __name__ == "__main__":
    app.run(port=8000)
docs_source_code/introduction/how_to_use_type/starlette_with_datetime_demo.py
import datetime

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


@pait()
async def demo(timestamp: datetime.datetime = field.Query.i()) -> JSONResponse:
    return JSONResponse({"time": timestamp.isoformat()})


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


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/introduction/how_to_use_type/sanic_with_datetime_demo.py
import datetime

from sanic import Sanic, json, response

from pait import field
from pait.app.sanic import pait


@pait()
async def demo(timestamp: datetime.datetime = field.Query.i()) -> response.HTTPResponse:
    return json({"time": timestamp.isoformat()})


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


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/introduction/how_to_use_type/tornado_with_datetime_demo.py
import datetime

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

from pait import field
from pait.app.tornado import pait


class DemoHandler(RequestHandler):
    @pait()
    def get(self, timestamp: datetime.datetime = field.Query.i()) -> None:
        self.write({"time": timestamp.isoformat()})


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


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

However, after running the code and calling the following curl command, can find that Pydantic automatically converts the timestamp to datetime type and the time zone is UTC:

  ~ curl "http://127.0.0.1:8000/api/demo?timestamp=1600000000"
{"time":"2020-09-13T12:26:40+00:00"}
This processing is fine, but assuming that the server for this business is located in some non-UTC and both the database and the program's are dependent on the time zone of the machine, the It would be necessary to perform another time zone conversion each time the data is fetched. This can be solved by writing a Type class that conforms to the Pydantic checksum rule, code show as below:

import datetime
from typing import Callable, Generator, Union


class UnixDatetime(datetime.datetime):

    @classmethod
    def __get_validators__(cls) -> Generator[Callable, None, None]:
        yield cls.validate

    @classmethod
    def validate(cls, v: Union[int, str]) -> datetime.datetime:
        if isinstance(v, str):
            v = int(v)
        return datetime.datetime.fromtimestamp(v)
from pydantic import BeforeValidator
from typing import Union
from typing_extensions import Annotated
from datetime import datetime


def validate(v: Union[int, str]) -> datetime:
    if isinstance(v, str):
        v = int(v)
    return datetime.fromtimestamp(v)

UnixDatetime = Annotated[datetime, BeforeValidator(validate)]

through the sample code can be seen due to Pydantic version of the different, Type implementation is not the same, but their logic is the same, write the completion of the Type can be applied to the code:

docs_source_code/introduction/how_to_use_type/flask_with_unix_datetime_demo.py
import datetime
from typing import Callable, Generator, Union

from flask import Flask, Response, jsonify

from pait import field
from pait._pydanitc_adapter import is_v1
from pait.app.flask import pait

if is_v1:

    class UnixDatetime(datetime.datetime):
        @classmethod
        def __get_validators__(cls) -> Generator[Callable, None, None]:
            yield cls.validate

        @classmethod
        def validate(cls, v: Union[int, str]) -> datetime.datetime:
            if isinstance(v, str):
                v = int(v)
            return datetime.datetime.fromtimestamp(v)

else:
    from pydantic import BeforeValidator
    from typing_extensions import Annotated

    def validate(v: Union[int, str]) -> datetime.datetime:
        if isinstance(v, str):
            v = int(v)
        return datetime.datetime.fromtimestamp(v)

    UnixDatetime = Annotated[datetime.datetime, BeforeValidator(validate)]  # type: ignore


@pait()
def demo(timestamp: UnixDatetime = field.Query.i()) -> Response:
    return jsonify({"time": timestamp.isoformat()})


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


if __name__ == "__main__":
    app.run(port=8000)
docs_source_code/introduction/how_to_use_type/starlette_with_unix_datetime_demo.py
import datetime
from typing import Callable, Generator, Union

from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route

from pait import field
from pait._pydanitc_adapter import is_v1
from pait.app.starlette import pait

if is_v1:

    class UnixDatetime(datetime.datetime):
        @classmethod
        def __get_validators__(cls) -> Generator[Callable, None, None]:
            yield cls.validate

        @classmethod
        def validate(cls, v: Union[int, str]) -> datetime.datetime:
            if isinstance(v, str):
                v = int(v)
            return datetime.datetime.fromtimestamp(v)

else:
    from pydantic import BeforeValidator
    from typing_extensions import Annotated

    def validate(v: Union[int, str]) -> datetime.datetime:
        if isinstance(v, str):
            v = int(v)
        return datetime.datetime.fromtimestamp(v)

    UnixDatetime = Annotated[datetime.datetime, BeforeValidator(validate)]  # type: ignore


@pait()
async def demo(timestamp: UnixDatetime = field.Query.i()) -> JSONResponse:
    return JSONResponse({"time": timestamp.isoformat()})


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


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/introduction/how_to_use_type/sanic_with_unix_datetime_demo.py
import datetime
from typing import Callable, Generator, Union

from sanic import Sanic, json, response

from pait import field
from pait._pydanitc_adapter import is_v1
from pait.app.sanic import pait

if is_v1:

    class UnixDatetime(datetime.datetime):
        @classmethod
        def __get_validators__(cls) -> Generator[Callable, None, None]:
            yield cls.validate

        @classmethod
        def validate(cls, v: Union[int, str]) -> datetime.datetime:
            if isinstance(v, str):
                v = int(v)
            return datetime.datetime.fromtimestamp(v)

else:
    from pydantic import BeforeValidator
    from typing_extensions import Annotated

    def validate(v: Union[int, str]) -> datetime.datetime:
        if isinstance(v, str):
            v = int(v)
        return datetime.datetime.fromtimestamp(v)

    UnixDatetime = Annotated[datetime.datetime, BeforeValidator(validate)]  # type: ignore


@pait()
async def demo(timestamp: UnixDatetime = field.Query.i()) -> response.HTTPResponse:
    return json({"time": timestamp.isoformat()})


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


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app)
docs_source_code/introduction/how_to_use_type/tornado_with_unix_datetime_demo.py
import datetime
from typing import Callable, Generator, Union

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

from pait import field
from pait._pydanitc_adapter import is_v1
from pait.app.tornado import pait

if is_v1:

    class UnixDatetime(datetime.datetime):
        @classmethod
        def __get_validators__(cls) -> Generator[Callable, None, None]:
            yield cls.validate

        @classmethod
        def validate(cls, v: Union[int, str]) -> datetime.datetime:
            if isinstance(v, str):
                v = int(v)
            return datetime.datetime.fromtimestamp(v)

else:
    from pydantic import BeforeValidator
    from typing_extensions import Annotated

    def validate(v: Union[int, str]) -> datetime.datetime:
        if isinstance(v, str):
            v = int(v)
        return datetime.datetime.fromtimestamp(v)

    UnixDatetime = Annotated[datetime.datetime, BeforeValidator(validate)]  # type: ignore


class DemoHandler(RequestHandler):
    @pait()
    def get(self, timestamp: UnixDatetime = field.Query.i()) -> None:
        self.write({"time": timestamp.isoformat()})


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


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

After re-running the modified code and calling the curl command, can find that the returned time value no longer has the time zone:

  ~ curl "http://127.0.0.1:8000/api/demo?timestamp=1600000000"
{"time":"2020-09-13T20:26:40"}