so1n / pait Goto Github PK
View Code? Open in Web Editor NEWPait(π tool) - Python Modern API Tools, easier to use web frameworks/write API routing
License: Apache License 2.0
Pait(π tool) - Python Modern API Tools, easier to use web frameworks/write API routing
License: Apache License 2.0
TipException can be a good indication to the developer of which route the current error is generated by, as follows:
from starlette.responses import JSONResponse
from pait.app.starlette import pait
from pait.field import Header
@pait()
def demo(token: str = Header.i()) -> JSONResponse:
return JSONResponse({"token": token})
if __name__ == "__main__":
import uvicorn
from starlette.applications import Starlette
app: Starlette = Starlette()
app.add_route("/api/demo", demo, methods=["GET"])
uvicorn.run(app)
This route throws a TipException when handling a request without a token parameter:
If you set the Logging Debug to True, some logs will be output to tell the developer the exact location of the exception, as follows
Although the original exception can be obtained through TipException.exc, in some cases it is necessary to get the original exception directly or to be able to use a custom TipException.
Some of the features in Pydantic version 2.0 do not work properly in version 2.1+, so version 2.0 is no longer supported. e.g. pydantic/issues/7772
example and mock mixed use is likely to cause problems, and example should not be a callable, it is time to split them up
Change List:
The declaration of the JsonResponse in the example is too complicated:
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})
Json is a common API response format, can define some rules to make the definition of JsonResponse more simple, such as :
class DemoResponseModel(BaseModel):
"""demo response"""
uid: int = Field()
user_name: str = Field()
@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})
Very happy to wait until the release of Pydantic V2, and the adaptation of Pydantic V2 will be completed before the Pait version iterates to 1.0.0 beta
I want to experience pait, but the English documentation is not perfect. Do you have any plans for the English documentation?
Currently, each time a request is processed, the function signature is parsed again to implement dependency injection, which is very time consuming and unnecessary. It needs to be parsed in advance by preloading to reduce the time taken to process the request.
However, optimizing this feature may result in the inability to support some of the features of CBV, as shown in the following code:
class CbvDemo():
uid: str = Query.i()
def get(self) -> None:
pass
hardware-system-information
System: Linux
Node Name: so1n-PC
Release: 5.15.77-amd64-desktop
Version: #1 SMP Wed Nov 9 15:59:34 CST 2022
Machine: x86_64
CPU Physical cores: 6
CPU Total cores: 12
CPU Max Frequency: 4500.00Mhz
CPU Min Frequency: 800.00Mhz
CPU Current Frequency: 2548.32Mhz
Memory: 32GB
Implement an APIRoute, it's similar to Pait, but it's easy to register routes into the app.It is implemented as follows:
class APIRoute(object):
def __init__(self, **kwargs) -> None:
pass
def inject(self, app: Any, **kwargs: Any) -> None:
pass
def include_sub_route(self, route: "APIRoute") -> None:
pass
def add_api_route(self, **kwargs) -> None:
pass
def add_route(self, **kwargs) -> None:
pass
inject
method: responsible for registering the routes in APIRoute into the appinclude_sub_route
:This method adds the route from another APIRoute, but modifies some attributes, e.g. APIRoute(path='/api').include_sub_route(path='/demo', func=demo), then demo ends up with a path of /api/demo
.In the current version, the following routing parameters are not supported:
from pait.app.any import pait
class Demo(object):
pass
@pait()
def demo(a: int = Demo()):
pass
This type of parameter will now be supported, and when this type of parameter is used, it will be preferred to see if there is an a
value in the context's kwargs, and if there is, it will be used directly, otherwise the value of the a variable will be Demo()
.
Normal use:
from pydantic import BaseModel, Field
class RespModel(BaseModel):
code: int = Field(0, description="api code")
msg: str = Field("success", description="api status msg")
data: dict = Field(description="success result")
class DataModel(BaseModel):
uid: int = Field(description="user id", gt=10, lt=1000)
user_name: str = Field(description="user name", min_length=2, max_length=10)
age: int = Field(description="age", gt=1, lt=100)
class MyRespModel(RespModel):
data: DataModel = Field(description="success result")
from pait.app.any import pait
@pait(response_model_list=[MyRespModel])
def demo():
pass
Generic model use:
from pydantic import BaseModel, Field
from typing import TypeVar, Generic
T = TypeVar("T")
class RespModel(BaseModel, Generic[T]):
code: int = Field(0, description="api code")
msg: str = Field("success", description="api status msg")
data: T = Field(description="success result")
class DataModel(BaseModel):
uid: int = Field(description="user id", gt=10, lt=1000)
user_name: str = Field(description="user name", min_length=2, max_length=10)
age: int = Field(description="age", gt=1, lt=100)
from pait.app.any import pait
@pait(response_model_list=[RespModel[DataModel]])
def demo():
pass
Currently pait has doc routes and grpc routes that have some duplicate logic, which requires an add route method that can be adapted to all web frameworks
TagModel was created as an OpenAPI function, but in practice it can also act as an attribute of a route and be used by the apply func.
In this feature, TagModel will add two parameters openapi_include
and label
. When openapi_include
is True, TagModel can be used by OpenAPI, and label
is the attribute value of the route, which is similar to sanic ctx.
With the increase of plug-ins, the subsequent development of plug-ins will be more troublesome and need to optimize the management and use of plug-ins
Since the class-based Depend
is passed an instance during initialization, it will lead to class attribute data conflicts in the case of high concurrency, as shown in the following code:
from pait import field
class DemoDepend(object):
uid: str = field.Query.i()
def __call__(self) -> str:
return str
@pait()
def demo(uid: str = field.Depend.i(DemoDepend())) -> None:
passs
When multiple requests are hit, the value of uid in DemoDepend
is not fixed, but it is certain that it can only have one value at the same time. This means that when multiple requests hit, multiple different routing functions will read the same value. This situation is very bad.
To solve this problem, it is necessary to improve the initialization of the class-based Depend
, so that it is passed a class and initialized when the request hits.
For example
from pait.app.starlette import pait
from pait.field import Json
from starlette.responses import JSONResponse
@pait()
def demo(uid: str = Json.t()) -> JSONResponse:
return JSONResponse({"uid": uid})
When executing the demo route, the following error message :
AttributeError: 'coroutine' object has no attribute 'get'
At first, only the request object of different web frameworks were considered for compatibility, and as the version iterated, more and more code was duplicated. Now we need to use the adapter method for the response object, route object, add_route method, etc. to be compatible, so as to facilitate the subsequent iterative development of the function
Note: The first version of the API does not consider compatibility and should not be opened to the outside
Is your feature request related to a problem? Please describe.
Currently, the function signature of the routing function is extracted in the initialization phase, and then when the request hits the route, the corresponding value is obtained from the request object based on the function signature and the value is injected into the routing function.
In the current version this is a viable solution, but each new function related to reading routing function signatures requires a different parsing method (e.g. openapi is a different set of parsing methods), forcing the project to generate redundant code
Describe the solution you'd like
The following code:
def demo(
a: str = Query.t(),
b: int = Query.t()
) -> None:
pass
When Pait
resolves the routing function during the initialization phase, the following rules are generated for this routing function:
PaitRule(name="a", type_=str, field=Query.t())
PaitRule(name="b", type_=int, field=Query.t())
This is the most basic rule, based on which the method of generating OpenAPI documents can be made easier and the speed of dependency injection can be accelerated.
In addition to this, some additional features can be added, such as auto-fill type, such as route code
def demo(
a = Query.t(default=""),
b: int = Query.t()
) -> None:
pass
Pait
has the ability to generate rules for routing, as follows:
PaitRule(name="a", type_=str, field=Query.t())
PaitRule(name="b", type_=int, field=Query.t())
However, there is one drawback, Pait
cannot properly resolve the properties of Cbv routes during the initialization phase
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.