Giter Site home page Giter Site logo

so1n / pait Goto Github PK

View Code? Open in Web Editor NEW
50.0 4.0 3.0 4.35 MB

Pait(π tool) - Python Modern API Tools, easier to use web frameworks/write API routing

License: Apache License 2.0

Python 99.93% Shell 0.07%
python type-hints pydantic flask openapi sanic tornado redoc swagger starlette

pait's Introduction

Pait(π tool) - Python Modern API Tools, easier to use web frameworks/write API routing

Coverage

PyPI - Python Version PyPI

GitHub Workflow Status GitHub release (release name instead of tag name) Test

Support framework


Documentation: https://so1n.me/pait/

中文文档: https://so1n.me/pait-zh-doc/


pait

Pait is an api tool that can be used in any python web framework, the features provided are as follows:

  • Integrate into the Type Hints ecosystem to provide a safe and efficient API interface coding method.
  • Automatic verification and type conversion of request parameters (depends on Pydantic and inspect, currently supports Pydantic V1 and V2 versions).
  • Automatically generate openapi files and support UI components such as Swagger,Redoc,RapiDoc and Elements.
  • TestClient support, response result verification of test cases。
  • Plugin expansion, such as parameter relationship dependency verification, Mock response, etc.。
  • gRPC GateWay (After version 1.0, this feature has been migrated to grpc-gateway)
  • Automated API testing
  • WebSocket support
  • SSE support

Note:

  • mypy check 100%

  • python version >= 3.8 (support postponed annotations)

Installation

pip install pait

Simple Example

from typing import Type
import uvicorn  # type: ignore
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 Body
from pait.openapi.doc_route import add_doc_route
from pait.model.response import JsonResponseModel
from pydantic import BaseModel, Field


class DemoResponseModel(JsonResponseModel):
    """demo post api response model"""
    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 = Body.i(description="user id", gt=10, lt=1000),
    user_name: str = Body.i(description="user name", min_length=2, max_length=4)
) -> JSONResponse:
    return JSONResponse({'uid': uid, 'user_name': user_name})


app = Starlette(routes=[Route('/api', demo_post, methods=['POST'])])
add_doc_route(app)
uvicorn.run(app)

See documentation for more features

Support Web framework

Framework Description
Flask All features supported
Sanic All features supported
Starlette All features supported
Tornado All features supported
Django Coming soon

If the web framework is not supported(which you are using).

Can be modified sync web framework according to pait.app.flask

Can be modified async web framework according to pait.app.starlette

Performance

The main operating principle of Pait is to convert the function signature of the route function into Pydantic Model through the reflection mechanism when the program is started, and then verify and convert the request parameters through Pydantic Model when the request hits the route.

These two stages are all automatically handled internally by Pait. The first stage only slightly increases the startup time of the program, while the second stage increases the response time of the routing, but it only consumes 0.00005(s) more than manual processing. The specific benchmark data and subsequent optimization are described in #27.

Example

For more complete examples, please refer to example

pait's People

Contributors

dependabot[bot] avatar so1n avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

pait's Issues

Add Pydantic V2 Support

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

Tag supports tag-only functionality

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.

Pait[0.9.0]Simple routing support

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

No longer restricting key parameters of routing

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

Data conflicts in high concurrency with class-based `Depend`

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.

Pait[0.9.0] Support custom enable `TipException` to wrap exceptions that occur in pait

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:
image
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
image

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.

Pait[0.9.0] Splitting mock values to solve the problem of mixing example and mock

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 Mock plugin adds a new parameter example_column_name to specify the value of the Field from which the mock data is to be retrieved
  • Refactor the Mock plugin's API to reduce duplicate code

Pait[0.9.0]Optimize the management and use of plug-ins

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

  • 1.No longer distinguish between front or back plugins by is_core, but by different classes to facilitate Type Hint checking
  • 2.Field supports custom parameters for plugins
  • 3.Plugin classification, e.g. by request, response, parameter
  • 4.Complementary plug-in test cases
  • x.Optimize the overhead of the plugin when routing calls, including whether to call asynchronously, class initialization, etc.

Improving the speed of dependency injection

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

Pait[0.9.0] Add an adapter module to encapsulate a unified layer of calls for all web frameworks

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

More robust dependency injection rule generation

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

Response model support generic pydantic model

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.