# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['pait',
 'pait.api_doc',
 'pait.api_doc.html',
 'pait.app',
 'pait.app.base',
 'pait.app.flask',
 'pait.app.sanic',
 'pait.app.starlette',
 'pait.app.tornado',
 'pait.model',
 'pait.util']

package_data = \
{'': ['*']}

install_requires = \
['PyYAML>=5.4.1,<6.0.0', 'pydantic>=1.7.3,<2.0.0']

setup_kwargs = {
    'name': 'pait',
    'version': '0.7.2',
    'description': 'Pait is a Python api tool. Pait enables your Python web framework to have type checking, parameter type conversion, interface document generation and can display your documents through Redoc or Swagger (power by inspect, pydantic)',
    'long_description': '# pait\nPait is an api tool that can be used in any python web framework (currently only `flask`, `starlette`, `sanic`, `tornado` are supported, other frameworks will be supported once Pait is stable).\n\nPait allows the Python Web framework to have functions such as parameter type checking, type conversion (depending on Pydantic and inspect), and document output.\n\n> Note:\n>\n> mypy check 100%\n>\n> test coverage 95%+ (exclude api_doc)\n>\n> python version >= 3.7 (support postponed annotations)\n>\n> The function is being expanded... the documentation may not be perfect\n>\n> The following code does not specify, all default to use the `starlette` framework.\n>\n> There is no test case for the document output function, and the function is still being improved\n\n[中文文档](https://github.com/so1n/pait/blob/master/README_ZH.md)\n# Feature\n - [x] Parameter checksum automatic conversion (parameter check depends on `Pydantic`)\n - [x] Parameter dependency verification\n - [x] Automatically generate openapi files\n - [x] Support swagger, redoc routing\n - [x] return mock response\n - [x] TestClient support, support response result verification\n - [ ] Support more types of http requests (currently only supports RESTful api)\n - [ ] Combine faker to provide better mock response\n - [ ] Local api document management\n# Installation\n```Bash\npip install pait\n```\n# Usage\n## 1.type checking and parameter type conversion\n### 1.1.Use in route handle\nA simple starlette route handler example:\n```Python\nimport uvicorn\nfrom starlette.applications import Starlette\nfrom starlette.requests import Request\nfrom starlette.responses import JSONResponse\nfrom starlette.routing import Route\n\n\nasync def demo_post(request: Request) -> JSONResponse:\n    body_dict: dict = await request.json()\n    uid: int = body_dict.get(\'uid\', 0)\n    user_name: str = body_dict.get(\'user_name\', "")\n    # The following code is only for demonstration, in general, we do some wrapping\n    if not uid:\n        raise ValueError(\'xxx\')\n    if type(uid) != int:\n        raise TypeError(\'xxxx\')\n    if 10 <= uid <= 1000:\n        raise ValueError(\'xxx\')\n\n    if not user_name:\n        raise ValueError(\'xxx\')\n    if type(user_name) != str:\n        raise TypeError(\'xxxx\')\n    if 2 <= len(user_name) <= 4:\n        raise ValueError(\'xxx\')\n\n    return JSONResponse(\n        {\n            \'result\': {\n                \'uid\': body_dict[\'uid\'],\n                \'user_name\': body_dict[\'user_name\']\n            }\n        }\n    )\n\n\napp = Starlette(\n    routes=[\n        Route(\'/api\', demo_post, methods=[\'POST\']),\n    ]\n)\n\n\nuvicorn.run(app)\n```\nuse pait in starletter route handler:\n\n```Python\nimport uvicorn  # type: ignore\nfrom starlette.applications import Starlette\nfrom starlette.responses import JSONResponse\nfrom starlette.routing import Route\n\nfrom pait.app.starlette import pait\nfrom pait.field import Body\n\n\n@pait()\nasync def demo_post(\n    uid: int = Body.i(description="user id", gt=10, lt=1000),\n    user_name: str = Body.i(description="user name", min_length=2, max_length=4)\n) -> JSONResponse:\n    return JSONResponse({\'result\': {\'uid\': uid, \'user_name\': user_name}})\n\n\napp = Starlette(\n    routes=[\n        Route(\'/api\', demo_post, methods=[\'POST\']),\n    ]\n)\n\nuvicorn.run(app)\n```\nIt can be seen from the above that the code has been refined a lot. All of this is the role of the `pait` decorator. It finds out how to get the value through the function signature, what is the type of the value, and what is the key corresponding to the value. After assembly, it is handed over to `Pydantic` for verification and conversion, and then returned to the corresponding parameters of the routing function according to the function signature.\n\nfor example param: uid\n```Python3\nfrom pait.field import Body\n\nuid: int = Body.i(description="user id", gt=10, lt=1000)\n```\n`pait` will be split into the following parts:\n\n```\n<key>: <type> = <request data>\n```\nThe key is the parameter name, type is the parameter type, and request data is the other description of the parameter. For example, body represents the data of the request body, gt is the minimum parameter, and lt is the maximum parameter.\n\n\n\nHere is just a simple demo, because we write the model can be reused, so you can save a lot of development time, the above parameters are only used to a way to write, the following will introduce pait support for the two ways to write and use.\n\n### 1.2.Parameter expression supported by pait\npait in order to facilitate the use of users, support a variety of writing methods (mainly the difference between TypeHints)\n- TypeHints is BaseModel, mainly used for parameters from multiple `Field`, and want to reuse model:\n\n    BaseModel can be used only for args parameters:\n    ```Python\n    from pait.app.starlette import pait\n    from pait.field import Body, Header\n    from pydantic import BaseModel\n    ```\n\n\n    class TestModel(BaseModel):\n        uid: int = Body.i()\n        content_type: str = Header.i(default=\'Content-Type\')\n\n\n    @pait()\n    async def test(model: BaseModel):\n        return {\'result\': model.dict()}\n    ```\n- TypeHints is Pydantic.BaseModel, mainly used for parameters are derived from the same `Field`, and want to take the model:\n\n    BaseModel can only be used with kwargs parameters, and the type hints of the parameters must be a class that inherits from `pydantic.BaseModel`, using the example:\n    ````Python\n    from pydantic import BaseModel\n\n    from pait.app.starlette import pait\n    from pait.field import Body\n\n\n    class TestModel(BaseModel):\n        uid: int\n        user_name: str\n\n\n    @pait()\n    async def test(model: BaseModel = Body.i()):\n        return {\'result\': model.dict()}\n    ````\n- When TypeHints is not one of the above two cases:\n\n    can only be used for kwargs parameters and type hints are not the above two cases, if the value is rarely reused, or if you do not want to create a Model, you can consider this approach\n    ```Python\n    from pait.app.starlette import pait\n    from pait.field import Body\n    ```\n\n\n    @pait()\n    async def test(uid: int = Body.i(), user_name: str = Body.i()):\n        return {\'result\': {\'uid\': uid, \'user_name\': user_name}}\n    ```\n### 1.3.Field\nField will help pait know how to get data from request.\nBefore introducing the function of Field, let’s take a look at the following example. `pait` will obtain the body data of the request according to Field.Body, and obtain the value with the parameter named key. Finally, the parameter is verified and assigned to the uid.\n\n> Note: Use Field.Body() directly, `mypy` will check that the type does not match, then just change to Field.Body.i() to solve the problem.\n```Python\nfrom pait.app.starlette import pait\nfrom pait.field import Body\n\n\n@pait()\nasync def demo_post(\n    # get uid from request body data\n    uid: int = Body.i()\n) -> None:\n    pass\n```\n`Pait` also supports other functions when performing parameter verification and conversion, all of which are supported by the parameters of `<request data>`:\n\n- default: the function of providing default values, if the request parameter does not have the value of this parameter , The value is used by default\n\n- alias: Since `Content-Type` cannot be named in Python variables, it can only be named with `content_type` according to the naming convention of `Python`, and `content_type` cannot get the value directly from the header, so it can Set alias to `Content-Type`, so that `Pait` can get the value of `Content-Type` in the Header and assign it to the `content_type` variable.\n\n- raw_return: If the value is True, `Pait` will not use the parameter name to get the data, but directly assign the entire data to the corresponding parameter.\n\n```Python\nfrom pait.app.starlette import pait\nfrom pait.field import Body, Header\n\n\n@pait()\nasync def demo_post(\n    # get uid from request body data\n    uid: int = Body.i(default=100),\n    # get Content-Type from header\n    content_type: str = Header.i(alias=\'Content-Type\'),\n    header_dict: str = Header.i(raw_return=True)\n\n):\n    pass\n```\nThe above only demonstrates the Body and Header of the field, but there are other fields as well::\n- Field.Body   Get the json data of the current request\n- Field.Cookie Get the cookie data of the current request\n- Field.File   Get the file data of the current request, depending on the web framework will return different file object types\n- Field.Form   Get the form data of the current request, if there are multiple duplicate keys, only the first one will be returned\n- Field.Header Get the header data of the current request\n- Field.Path   Get the path data of the current request (e.g. /api/{version}/test, you can get the version data)\n- Field.Query  Get the url parameters of the current request and the corresponding data, if there are multiple duplicate keys, only the first one will be returned\n- Field.MultiQuery Get the url parameter data of the current request, and return the list corresponding to the key\n- Field.MultiForm Get the form data of the current request, return the list corresponding to the key\n\nAll the fields above are inherited from `pydantic.fields.FieldInfo`, most of the parameters here are for api documentation, see for specific usage[pydantic doc](https://pydantic-docs.helpmanual.io/usage/schema/#field-customisation)\n\n\nIn addition there is a field named Depends, he inherits from `object`, he provides the function of dependency injection, he only supports one parameter and the type of function, and the function\'s parameters are written in the same way as the routing function, the following is an example of the use of Depends, through Depends, you can reuse in each function to get the token function:\n\n```Python\nfrom pait.app.starlette import pait\nfrom pait.field import Body, Depends\n\n\ndef demo_depend(uid: str = Body.i(), password: str = Body.i()) -> str:\n    # fake db\n    token: str = db.get_token(uid, password)\n    return token\n\n\n@pait()\nasync def test_depend(token: str = Depends.i(demo_depend)) -> dict:\n    return {\'token\': token}\n```\n\n### 1.4.requests object\nAfter using `Pait`, the proportion of the number of times the requests object is used will decrease, so `pait` does not return the requests object. If you need the requests object, you can fill in the parameters like `requests: Requests` (you need to use the TypeHints format) , You can get the requests object corresponding to the web framework\n```Python\nfrom starlette.requests import Request\n\nfrom pait.app.starlette import pait\nfrom pait.field import Body\n\n\n@pait()\nasync def demo_post(\n        request: Request,\n        # get uid from request body data\n        uid: int = Body.i()\n) -> None:\n    pass\n```\n\n### 1.5.Exception\n#### 1.5.1Exception Handling\nPait will leave the exception to the user to handle it. Under normal circumstances, pait will only throw the exception of `pydantic` and `PaitBaseException`. The user needs to catch the exception and handle it by himself, for example:\n```Python\nfrom starlette.applications import Starlette\nfrom starlette.requests import Request\nfrom starlette.responses import Response\n\nfrom pait.exceptions import PaitBaseException\nfrom pydantic import ValidationError\n\nasync def api_exception(request: Request, exc: Exception) -> None:\n    """\n    Handle exception code\n    """\n    if isinstance(exc, PaitBaseException):\n        pass\n    elif isinstance(exc, ValidationError):\n        pass\n    else:\n        pass\n\nAPP = Starlette()\nAPP.add_exception_handler(PaitBaseException, api_exception)\nAPP.add_exception_handler(ValidationError, api_exception)\n```\n#### 1.5.2Error Tip\nWhen you use `pait` incorrectly, `pait` will indicate in the exception the file path and line number of the function.\n```Bash\n  File "/home/so1n/github/pait/pait/func_param_handle.py", line 101, in set_value_to_kwargs_param\n    f\'File "{inspect.getfile(func_sig.func)}",\'\nPaitBaseException: \'File "/home/so1n/github/pait/example/starlette_example.py", line 28, in demo_post\\n kwargs param:content_type: <class \\\'str\\\'> = Header(key=None, default=None) not found value, try use Header(key={key name})\'\n```\nIf you need more information, can set the log level to debug to get more detailed information\n```Python\nDEBUG:root:\nasync def demo_post(\n    ...\n    content_type: <class \'str\'> = Header(key=None, default=None) <-- error\n    ...\n):\n    pass\n```\n## 2.Document Generation\n\n`pait` will automatically capture the request parameters and url, method and other information of the routing function.\nIn addition, it also supports labeling some relevant information. These labels will only be loaded into the memory when the Python program starts running, and will not affect the performance of the request, as in the following example:\n\n```Python\nfrom pait.app.starlette import pait\nfrom pait.model.status import PaitStatus\n\nfrom example.param_verify.model import UserSuccessRespModel, FailRespModel\n\n\n@pait(\n  author=("so1n",),\n  group="user",\n  status=PaitStatus.release,\n  tag=("user", "post"),\n  response_model_list=[UserSuccessRespModel, FailRespModel],\n)\ndef demo() -> None:\n  pass\n```\nParam:\n- author: List of authors who wrote the interface\n- group: The group to which the interface belongs (This option is currently not used for openapi)\n- status: The status of the interface, currently only supports several states of `PaitStatus` (This option will only be used for openapi and marked as deprecated if it is offline)\n  - default status:\n    - undefined: undefined\n  - in development:\n    - design: Interface design\n    - dev: Under development and testing\n  - Development completed:\n    - integration: integration test\n    - complete: development completed\n    - test: testing\n  - online:\n    - release: online\n  - offline:\n    - abnormal: The interface is abnormal and needs to be offline\n    - maintenance: In maintenance\n    - archive: archive\n    - abandoned: abandoned\n- tag: interface tag\n- response_model_list: return data, Need to inherit from `pait.model.PaitResponseModel`, Since `pait` is an extension of the web framework and will not modify the code of the framework, this parameter will not be used for ordinary request judgment (nor should it be used in the production environment). It is currently only used for document generation, mock response generation and TestClient verification.\n\n### 2.1.openapi\n#### 2.1.1openapi doc output\nCurrently pait supports most of the functions of openapi, a few unrealized features will be gradually improved through iterations (response-related more complex)\n\nThe openapi module of pait supports the following parameters (more parameters will be provided in the next version):\n- title: openapi\'s title\n- open_api_info: openapi\'s info param\n- open_api_tag_list: related description of openapi tag\n- open_api_server_list: openapi server list\n- type_: The type of output, optionally json and yaml\n- filename: Output file name, or if empty, output to terminal\n\nThe following is the sample code output from the openapi documentation (modified by the 1.1 code). See [Example code](https://github.com/so1n/pait/tree/master/example/api_doc) and [doc example](https://github.com/so1n/pait/blob/master/example/api_doc/example_doc)\n```Python\nimport uvicorn\n\nfrom starlette.applications import Starlette\nfrom starlette.routing import Route\nfrom starlette.responses import JSONResponse\n\nfrom pait.field import Body\nfrom pait.app.starlette import pait\nfrom pydantic import (\n    BaseModel,\n    conint,\n    constr,\n)\n\n\n# Create a Model based on Pydantic.BaseModel\nclass PydanticModel(BaseModel):\n    uid: conint(gt=10, lt=1000)  # Whether the auto-check type is int, and whether it is greater than or equal to 10 and less than or equal to 1000\n    user_name: constr(min_length=2, max_length=4)  # Whether the auto-check type is str, and whether the length is greater than or equal to 2, less than or equal to 4\n\n\n\n# Decorating functions with the pait decorator\n@pait()\nasync def demo_post(\n    # pait through the Body () to know the current need to get the value of the body from the request, and assign the value to the model,\n    # and the structure of the model is the above PydanticModel, he will be based on our definition of the field automatically get the value and conversion and judgment\n    model: PydanticModel = Body.i()\n):\n    # Get the corresponding value to return\n    return JSONResponse({\'result\': model.dict()})\n\n\napp = Starlette(\n    routes=[\n        Route(\'/api\', demo_post, methods=[\'POST\']),\n    ]\n)\n\nuvicorn.run(app)\n# --------------------\n\nfrom pait.app.starlette import load_app\nfrom pait.api_doc.open_api import PaitOpenApi\n\n\n# Extracting routing information to pait\'s data module\npait_dict = load_app(app)\n# Generate openapi for routing based on data from the data module\nPaitOpenApi(pait_dict)\n```\n#### 2.1.2.OpenApi Route\n`Pait` currently supports openapi.json routing, and also supports page display of `Redoc` and `Swagger`, and these only need to call the `add_doc_route` function to add three routes to the `app` instance:\n- /openapi.json\n- /redoc\n- /swagger\nIf you want to define a prefix, such as /doc/openapi.json, just pass in /doc through the prefix parameter. Specific examples are as follows:\n```Python\nimport uvicorn  # type: ignore\nfrom pydantic import BaseModel, Field\nfrom starlette.applications import Starlette\nfrom starlette.responses import JSONResponse\nfrom starlette.routing import Route\n\nfrom pait.app.starlette import pait\nfrom pait.field import Body\n\n\n# Create a Model based on Pydantic.BaseModel\nclass UserModel(BaseModel):\n    # Whether the auto-check type is int, and whether it is greater than or equal to 10 and less than or equal to 1000\n    uid: int = Field(description="user id", gt=10, lt=1000)\n    # Whether the auto-check type is str, and whether the length is greater than or equal to 2, less than or equal to 4\n    user_name: str = Field(description="user name", min_length=2, max_length=4)\n\n\n# Decorating functions with the pait decorator\n@pait()\nasync def demo_post(\n    # pait through the Body () to know the current need to get the value of the body from the request, and assign the value to the model,\n    # and the structure of the model is the above PydanticModel, he will be based on our definition of the field automatically get the value and conversion and judgment\n    model: UserModel = Body.i()\n) -> JSONResponse:\n    # Get the corresponding value to return\n    return JSONResponse({\'result\': model.dict()})\n\n\napp = Starlette(\n    routes=[\n        Route(\'/api\', demo_post, methods=[\'POST\']),\n    ]\n)\n# Inject the route into the app\nadd_doc_route(app)\n# Inject the route into the app, and prefix it with /doc\nadd_doc_route(app, prefix=\'/doc\')\n```\n### 2.2.Other doc output\n> Note: The function is being improved...\n\nIn addition to parameter verification and conversion, pait also provides the ability to output api documentation, which can be configured with simple parameters to output perfect documentation.\n\nNote: Currently only md documents and openapi documents for json and yaml are supported for output.For the output of md, see\n[doc example](https://github.com/so1n/pait/blob/master/example/api_doc/example_doc)\n\n\n## 3.Implicit and explicit introduction\n`pait` provides support for multiple frameworks. If only one of the frameworks is installed in a project, then you can use implicit import:\n```Python3\nfrom pait.app import add_doc_route, load_app, pait\n\n```\nHowever, if multiple frameworks are installed at the same time, the above introduction will throw an error. It is recommended to use explicit introduction, such as:\n```Python3\nfrom pait.app.starlette import add_doc_route, load_app, pait\n\n```\n## 4.config, data and load_app\n- data\n\n  Since `pait` provides functional support through a decorator, all data is injected into meta data when the compiler is started, providing support for subsequent document generation and other functions.\n- load_app\n\n  There are a lot of routing function information in meta data, but it lacks key parameters such as `url`, `method`, etc.\nSo you also need to use load_app to bind the relevant parameters to the routing function data decorated by the `pait` decorator in the meta data. The method of use is very simple, but remember that you must register all routes before calling:\n```Python3\n  from starlette.applications import Starlette\n\n  from pait.app.starlette import load_app\n\n  app: Starlette = Starlette()\n  # error\n  load_app(app)\n  # --------\n  # app.add_route\n  # --------\n\n  # success\n  load_app(app)\n  ```\n- config\nconfig can provide some configuration support for `pait`, it needs to be initialized as soon as possible. The best initialization position is to initialize before app initialization, and only one initialization is allowed during the entire runtime.\n  ```Python\n  from starlette.applications import Starlette\n\n  from pait.app.starlette import load_app\n  from pait.g import config\n\n  config.init_config(author="so1n")\n  app: Starlette = Starlette()\n  # --------\n  # app.add_route\n  # --------\n  load_app(app)\n  ```\n\nParameter introduction:\n- author: The global default API author, if the author parameter in `@pait` is empty, it will call `config.author` by default.\n- status: The global default API status, if the status in `@pait` is empty, it will be called by default to `config.status`\n- enable_mock_response: Decide whether this run will return a normal response or a mock response based on `response_model`\n- enable_mock_response_filter_fn: Multiple `response_model` are supported by default, and the mock response only takes the first `response_model` by default. If you feel that this does not meet the `response_model` you want, you can configure this function to return the results you want\n- block_http_method_set: Some web frameworks will automatically help add some routing functions to request methods such as `HEAD`. `pait` cannot recognize which are added by the framework and which are added by the user. Users can block some `methods` through this parameter\n- default_response_model_list: When designing some API interfaces, there are usually some default exception responses, and repeated configuration is very troublesome. can apply to the global by configuring this parameter\n- json_type_default_value_dict: Configure the default value of the json type\n## 5.TestClientHelper\n`pait` encapsulates a corresponding `TestCLientHelper` class for each framework, through which test cases can be written more conveniently, and the result data structure can be compared with `response_model` for verification. [starlette example](https://github.com/so1n/pait/blob/master/tests/test_app/test_starlette.py#L80)\n\nParameter Description:\n  - client: The test client corresponding to the framework\n  - func: Corresponding to the routing function decorated by `pait`\n  - pait_dict: `pait` meta data, if it is empty, it will be automatically generated internally\n  - body_dict: Requested json data\n  - cookie_dict: Requested cookie data\n  - file_dict: Requested file data\n  - form_dict: Requested form data\n  - header_dict: Requested header data\n  - path_dict: Requested path data\n  - query_dict: Requested query data\n\n\nDescription of response_model generation results:\nIf response_model has a `default` value, it will be directly referenced to the `default` value, otherwise the default value of the value type is used (configurable through config)\n## 6.How to used in other web framework?\nIf the web framework is not supported, which you are using.\nCan be modified sync web framework according to [pait.app.flask](https://github.com/so1n/pait/blob/master/pait/app/flask.py)\n\nCan be modified async web framework according to [pait.app.starlette](https://github.com/so1n/pait/blob/master/pait/app/starlette.py)\n## 7.IDE Support\nWhile pydantic will work well with any IDE out of the box.\n- [PyCharm plugin](https://pydantic-docs.helpmanual.io/pycharm_plugin/)\n- [Mypy plugin](https://pydantic-docs.helpmanual.io/mypy_plugin/)\n\n## 8.Full example\nFor more complete examples, please refer to [example](https://github.com/so1n/pait/tree/master/example)\n',
    'author': 'So1n',
    'author_email': 'so1n897046026@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/so1n/pait',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.7,<4.0',
}


setup(**setup_kwargs)
