sanic-api 0.2.9__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. sanic_api/__init__.py +2 -20
  2. sanic_api/api/__init__.py +17 -2
  3. sanic_api/api/model.py +0 -46
  4. sanic_api/api/request.py +157 -0
  5. sanic_api/api/response.py +34 -0
  6. sanic_api/app.py +306 -0
  7. sanic_api/config/__init__.py +1 -1
  8. sanic_api/config/setting.py +69 -14
  9. sanic_api/logger/__init__.py +0 -1
  10. sanic_api/logger/config.py +74 -52
  11. sanic_api/logger/extension.py +130 -0
  12. sanic_api/openapi/__init__.py +0 -7
  13. sanic_api/utils/__init__.py +0 -0
  14. sanic_api/{enum.py → utils/enum.py} +1 -31
  15. sanic_api-0.3.0.dist-info/METADATA +160 -0
  16. sanic_api-0.3.0.dist-info/RECORD +19 -0
  17. {sanic_api-0.2.9.dist-info → sanic_api-0.3.0.dist-info}/WHEEL +1 -1
  18. example/__main__.py +0 -19
  19. example/api/__init__.py +0 -1
  20. example/api/t1/__init__.py +0 -2
  21. example/api/t1/t1_1.py +0 -22
  22. example/api/t1/t1_2.py +0 -9
  23. example/api/t2/__init__.py +0 -1
  24. example/api/t2/t2.py +0 -9
  25. example/api/t2/t3/__init__.py +0 -1
  26. example/api/t2/t3/t3.py +0 -9
  27. example/api/user.py +0 -89
  28. example/api/validator.py +0 -97
  29. example/app.py +0 -62
  30. example/settings.py +0 -10
  31. sanic_api/api/api.py +0 -177
  32. sanic_api/api/exception.py +0 -46
  33. sanic_api/api/extend.py +0 -25
  34. sanic_api/api/handle_exception.py +0 -61
  35. sanic_api/api/validators.py +0 -76
  36. sanic_api/config/sanic.py +0 -19
  37. sanic_api/config/sanic_api.py +0 -11
  38. sanic_api/logger/extend.py +0 -71
  39. sanic_api/logger/sanic_http.py +0 -68
  40. sanic_api/openapi/openapi.py +0 -102
  41. sanic_api/utils.py +0 -112
  42. sanic_api-0.2.9.dist-info/METADATA +0 -96
  43. sanic_api-0.2.9.dist-info/RECORD +0 -38
  44. /example/__init__.py → /sanic_api/openapi/extension.py +0 -0
  45. {sanic_api-0.2.9.dist-info → sanic_api-0.3.0.dist-info}/licenses/LICENSE.txt +0 -0
example/api/user.py DELETED
@@ -1,89 +0,0 @@
1
- from typing import Any, Dict, List
2
- from uuid import uuid4
3
-
4
- from sanic import Blueprint, text
5
- from sanic.request import Request
6
-
7
- from .validator import (
8
- UserAddApi,
9
- UserDelApi,
10
- UserFindOneApi,
11
- UserListApi,
12
- UserUpdateApi,
13
- )
14
-
15
- users_blueprint = Blueprint("users_blueprint", url_prefix="/users")
16
- users_blueprint.ctx.desc = "用户"
17
-
18
-
19
- @users_blueprint.route("/test", methods=["GET"])
20
- async def hello(request):
21
- return text("Hello world!")
22
-
23
-
24
- @users_blueprint.route("/", methods=["POST"])
25
- async def user_add(request: Request, api: UserAddApi):
26
- """
27
- 添加用户
28
- """
29
- api.resp.uid = 1
30
- return api.json_resp()
31
-
32
-
33
- @users_blueprint.route("/<uid>", methods=["DELETE"])
34
- async def user_del(request: Request, uid: int, api: UserDelApi):
35
- """
36
- 刪除一个用户
37
- """
38
-
39
- api.resp.uid = uid
40
-
41
- return api.json_resp()
42
-
43
-
44
- @users_blueprint.route("/<uid>", methods=["PUT"])
45
- async def user_update(request: Request, uid: int, api: UserUpdateApi):
46
- """
47
- 更新一个用户
48
- """
49
-
50
- api.resp.uid = uid
51
-
52
- return api.json_resp()
53
-
54
-
55
- @users_blueprint.route("/<uid>", methods=["GET"])
56
- async def user_one(request: Request, uid: int, api: UserFindOneApi):
57
- """
58
- 获取一个用户
59
- """
60
-
61
- api.resp.uid = 1
62
- api.resp.username = "username"
63
- api.resp.password = uuid4().hex
64
-
65
- return api.json_resp()
66
-
67
-
68
- @users_blueprint.route("/", methods=["GET"])
69
- async def user_list(request: Request, api: UserListApi):
70
- """
71
- 获取一些用户
72
- """
73
-
74
- users: List[Dict[str, Any]] = [
75
- {
76
- "uid": 1,
77
- "username": "user1",
78
- },
79
- {
80
- "uid": 2,
81
- "username": "user2",
82
- },
83
- ]
84
- for user in users:
85
- api.resp.username = user["username"]
86
- api.resp.uid = user["uid"]
87
- api.resp.add_data()
88
-
89
- return api.json_resp()
example/api/validator.py DELETED
@@ -1,97 +0,0 @@
1
- from pydantic import BaseModel, Field
2
-
3
- from sanic_api.api import API
4
- from sanic_api.api.model import ListRespModel, ResponseModel
5
-
6
-
7
- class Pagination(BaseModel):
8
- """
9
- 分页
10
- """
11
-
12
- page: int = Field(1, gt=0, title="页数")
13
- per_page: int = Field(20, title="每页数量", gt=0, lt=100)
14
-
15
-
16
- class ListParam(Pagination):
17
- """
18
- 列表
19
- """
20
-
21
- orderings: str = Field(None, title="排序字段", description='排序字段,多个字段 ”,“ 分割,倒序使用 "-", 如:"id, -name"')
22
-
23
-
24
- class UserModel(BaseModel):
25
- username: str = Field(title="用户名")
26
- password: str = Field(title="密码", description="密码,经过md5加密的")
27
-
28
-
29
- class AddUserReqModel(BaseModel):
30
- """
31
- 添加请求参数
32
- """
33
-
34
- user: UserModel = Field(title="用户信息")
35
-
36
-
37
- class AddUserRespModel(ResponseModel):
38
- """
39
- 响应参数
40
- """
41
-
42
- uid: int = Field(default=None, title="用户ID", description="创建用户的用户ID")
43
-
44
-
45
- class FindOneUserRespModel(UserModel, AddUserRespModel):
46
- """
47
- 响应参数
48
- """
49
-
50
-
51
- class UserListRespModel(ListRespModel, FindOneUserRespModel):
52
- """
53
- 用户列表响应参数
54
- """
55
-
56
-
57
- class UserAddApi(API):
58
- """
59
- 添加用户API
60
- """
61
-
62
- json_req: AddUserReqModel
63
- resp: AddUserRespModel
64
-
65
-
66
- class UserDelApi(API):
67
- """
68
- 删除用户API
69
- """
70
-
71
- resp: AddUserRespModel
72
-
73
-
74
- class UserUpdateApi(UserAddApi):
75
- """
76
- 更新用户API
77
- """
78
-
79
- json_req: AddUserReqModel
80
- resp: AddUserRespModel
81
-
82
-
83
- class UserFindOneApi(API):
84
- """
85
- 查询单个用户API
86
- """
87
-
88
- resp: FindOneUserRespModel
89
-
90
-
91
- class UserListApi(API):
92
- """
93
- 用户列表API
94
- """
95
-
96
- query_req: ListParam
97
- resp: UserListRespModel
example/app.py DELETED
@@ -1,62 +0,0 @@
1
- from sanic import Sanic
2
- from sanic.log import logger
3
-
4
- from example.settings import settings
5
- from sanic_api import init_api
6
- from sanic_api.utils import auto_blueprint
7
-
8
-
9
- async def main_process_start(sanic_app: Sanic):
10
- """
11
- 主进程启动之前调用
12
- Args:
13
- sanic_app: application
14
-
15
- Returns:
16
-
17
- """
18
- sanic_cfg = settings.sanic.dict(by_alias=True)
19
- sanic_app.config.update(sanic_cfg)
20
- logger.info(f"{sanic_app.name} 服务启动")
21
-
22
-
23
- async def main_process_stop(sanic_app: Sanic):
24
- """
25
- 主进程停止之后调用
26
- Args:
27
- sanic_app: application
28
-
29
- Returns:
30
-
31
- """
32
-
33
- logger.info(f"{sanic_app.name} 服务停止")
34
-
35
-
36
- async def before_server_start(sanic_app: Sanic):
37
- """
38
- worker启动之前调用
39
- Args:
40
- sanic_app: application
41
-
42
- Returns:
43
-
44
- """
45
- logger.debug(f"Worler {sanic_app.m.pid} 启动")
46
-
47
-
48
- def app_factory():
49
- """
50
- app 工厂方法
51
- Returns:
52
-
53
- """
54
- app = Sanic(name="test", configure_logging=False)
55
- app.main_process_start(main_process_start)
56
- app.main_process_stop(main_process_stop)
57
- app.before_server_start(before_server_start)
58
-
59
- auto_blueprint(app, "api")
60
- init_api(app)
61
-
62
- return app
example/settings.py DELETED
@@ -1,10 +0,0 @@
1
- from sanic_api.config import DefaultSettings
2
-
3
-
4
- class Settings(DefaultSettings):
5
- """
6
- 设置类
7
- """
8
-
9
-
10
- settings = Settings()
sanic_api/api/api.py DELETED
@@ -1,177 +0,0 @@
1
- import functools
2
- from abc import ABCMeta
3
- from typing import Any, Callable, Dict, Optional
4
-
5
- from pydantic import BaseModel, Field
6
- from pydantic import ValidationError as PyDanticValidationError
7
- from sanic import HTTPResponse, Sanic
8
- from sanic.response import json
9
-
10
- from sanic_api.api.exception import ValidationError
11
- from sanic_api.api.model import ListRespModel, ResponseModel
12
- from sanic_api.enum import EnumBase, ParamEnum, RespCodeEnum
13
- from sanic_api.utils import json_dumps
14
-
15
-
16
- def get_resp_tmp(key: str, default):
17
- """
18
- 获取响应字段模板
19
- """
20
- app = Sanic.get_app()
21
- sanic_api: dict = app.config.get("sanic_api", {})
22
- return sanic_api.get(key, default)
23
-
24
-
25
- class Response:
26
- def __init__(
27
- self,
28
- http_code: int = 200,
29
- headers: Optional[Dict[str, str]] = None,
30
- server_code: EnumBase = RespCodeEnum.SUCCESS,
31
- message: str = "Success",
32
- data: Any = None,
33
- response_type: Optional[type] = None,
34
- ):
35
- """
36
- 初始化函数
37
- Args:
38
- http_code: http响应码 默认是200
39
- headers: 自定义的响应头
40
- server_code: 自定义的服务码
41
- message: 返回的消息
42
- data: 返回的数据
43
- """
44
-
45
- self.http_code = http_code
46
- self.headers = headers
47
- self.message = message
48
- response_type = response_type or Response.get_response_type(type(data))
49
-
50
- if isinstance(data, ListRespModel):
51
- data = data.to_list()
52
- elif isinstance(data, BaseModel):
53
- data = data.dict()
54
-
55
- resp: ResponseModel = response_type()
56
- code_tmp = get_resp_tmp("code_tmp", "code")
57
- msg_tmp = get_resp_tmp("msg_tmp", "msg")
58
- data_tmp = get_resp_tmp("data_tmp", "data")
59
-
60
- setattr(resp, code_tmp, server_code)
61
- setattr(resp, msg_tmp, message)
62
- setattr(resp, data_tmp, data)
63
-
64
- data = resp.dict()
65
-
66
- self.data = data
67
-
68
- def json_resp(self, default: Optional[Callable[[Any], Any]] = None) -> HTTPResponse:
69
- """
70
- 返回json格式的响应
71
- Args:
72
-
73
- """
74
- return json(
75
- body=self.data,
76
- status=self.http_code,
77
- headers=self.headers,
78
- dumps=functools.partial(json_dumps, default=default),
79
- )
80
-
81
- @classmethod
82
- def get_response_type(cls, data_type: type):
83
- """
84
- 获取响应的类型
85
- Args:
86
- data_type: 响应中data的类型
87
-
88
- Returns:
89
-
90
- """
91
- code_tmp = get_resp_tmp("code_tmp", "code")
92
- msg_tmp = get_resp_tmp("msg_tmp", "msg")
93
- data_tmp = get_resp_tmp("data_tmp", "data")
94
-
95
- attr_dict = {
96
- code_tmp: Field(title="业务响应码"),
97
- msg_tmp: Field(title="响应消息"),
98
- data_tmp: Field(title="响应数据"),
99
- "__annotations__": {code_tmp: int, msg_tmp: str, data_tmp: data_type},
100
- }
101
- resp_name = data_type.__name__ if data_type.__name__ != "NoneType" else "Response"
102
- response_type = type(resp_name, (ResponseModel,), attr_dict)
103
- return response_type
104
-
105
- @staticmethod
106
- def _get_tmp(key: str, default):
107
- """
108
- 获取字段模板
109
- """
110
- app = Sanic.get_app()
111
- sanic_api: dict = app.config.get("sanic_api", {})
112
- return sanic_api.get(key, default)
113
-
114
-
115
- class API(metaclass=ABCMeta):
116
- json_req: Optional[BaseModel] = None
117
- form_req: Optional[BaseModel] = None
118
- query_req: Optional[BaseModel] = None
119
- resp: Optional[Any] = None
120
- tags: list = []
121
- description: str = ""
122
-
123
- def __init__(self):
124
- response_type = self.__class__.__annotations__.get("resp")
125
- self.json_req_type = self.__class__.__annotations__.get("json_req")
126
- self.form_req_type = self.__class__.__annotations__.get("form_req")
127
- self.query_req_type = self.__class__.__annotations__.get("query_req")
128
- self.response_type = Response.get_response_type(response_type)
129
- self.resp = response_type()
130
-
131
- def validate_params(self, req_data: dict, param_enum: ParamEnum):
132
- """
133
- 验证参数,参数有问题抛出异常
134
- Args:
135
- req_data: 数据
136
- param_enum: 参数类型
137
-
138
- Returns:
139
-
140
- """
141
- try:
142
- if param_enum == ParamEnum.JSON:
143
- self.json_req = self.json_req_type(**req_data)
144
- elif param_enum == ParamEnum.QUERY:
145
- self.query_req = self.query_req_type(**req_data)
146
- elif param_enum == ParamEnum.FORM:
147
- self.form_req = self.form_req_type(**req_data)
148
- except PyDanticValidationError as e:
149
- raise ValidationError(e.errors()) from e
150
-
151
- def json_resp(
152
- self,
153
- http_code: int = 200,
154
- headers: Optional[Dict[str, str]] = None,
155
- server_code: RespCodeEnum = RespCodeEnum.SUCCESS,
156
- message: str = "Success",
157
- ):
158
- """
159
- 根据响应数据返回json格式的响应
160
- Args:
161
- http_code: http协议响应码
162
- headers: 响应头
163
- server_code: 应用协议响应码
164
- message: 响应消息
165
-
166
- Returns:
167
- 返回一个sanic的HTTPResponse
168
- """
169
-
170
- return Response(
171
- data=self.resp,
172
- http_code=http_code,
173
- headers=headers,
174
- server_code=server_code,
175
- message=message,
176
- response_type=self.response_type,
177
- ).json_resp()
@@ -1,46 +0,0 @@
1
- from typing import Any, Optional
2
-
3
- from sanic.exceptions import SanicException
4
-
5
- from sanic_api.enum import EnumBase, RespCodeEnum
6
- from sanic_api.utils import get_current_request
7
-
8
-
9
- class ServerException(SanicException):
10
- def __init__(
11
- self,
12
- message: Optional[str] = None,
13
- status_code: Optional[int] = None,
14
- server_code: Optional[EnumBase] = None,
15
- quiet: Optional[bool] = None,
16
- context: Any = None,
17
- extra=None,
18
- ):
19
- super().__init__(
20
- message=message,
21
- status_code=status_code,
22
- quiet=quiet,
23
- context=context,
24
- extra=extra,
25
- )
26
- self.server_code = server_code or RespCodeEnum.FAILED
27
- self.message = message or self.server_code.desc
28
- self.status_code = status_code or 200
29
- req = get_current_request()
30
- if req:
31
- req.ctx.exception = self
32
-
33
-
34
- class ValidationInitError(ServerException):
35
- """
36
- 验证器初始化失败
37
- """
38
-
39
-
40
- class ValidationError(ServerException):
41
- """
42
- 参数验证失败
43
- """
44
-
45
- def __init__(self, errors: list, *args, **kwargs):
46
- super().__init__(server_code=RespCodeEnum.PARAM_FAILED, context=errors)
sanic_api/api/extend.py DELETED
@@ -1,25 +0,0 @@
1
- from sanic.exceptions import NotFound
2
- from sanic_ext import Extension
3
-
4
- from sanic_api.api.exception import ServerException
5
-
6
- from .handle_exception import not_found, other_exception, server_exception
7
- from .validators import validators
8
-
9
-
10
- class ApiExtend(Extension):
11
- """
12
- 接口处理扩展
13
- 参数校验、异常拦截等功能
14
- """
15
-
16
- name = "ApiExtend"
17
-
18
- def startup(self, bootstrap) -> None:
19
- if not self.included():
20
- return
21
-
22
- self.app.error_handler.add(ServerException, server_exception)
23
- self.app.error_handler.add(Exception, other_exception)
24
- self.app.error_handler.add(NotFound, not_found)
25
- self.app.on_request(validators, priority=998)
@@ -1,61 +0,0 @@
1
- from sanic import Request
2
- from sanic.log import logger
3
-
4
- from sanic_api.api.api import Response
5
- from sanic_api.api.exception import ServerException
6
- from sanic_api.enum import RespCodeEnum
7
-
8
-
9
- def server_exception(request: Request, e: ServerException):
10
- """
11
- 处理业务异常
12
- Args:
13
- request: 请求
14
- e: 异常信息
15
-
16
- Returns:
17
- 返回处理异常后的响应
18
- """
19
-
20
- return Response(
21
- http_code=e.status_code,
22
- server_code=e.server_code,
23
- message=e.message,
24
- data=e.context,
25
- ).json_resp()
26
-
27
-
28
- def not_found(request: Request, e):
29
- """
30
- 处理 NotFound 异常
31
- Args:
32
- request: 请求信息
33
- e: 异常信息
34
-
35
- Returns:
36
-
37
- """
38
- if "/favicon.ico" == request.path:
39
- return
40
-
41
- logger.warning(f"找不到路由:{request.path}")
42
-
43
-
44
- def other_exception(request: Request, e):
45
- """
46
- 处理其他异常
47
- Args:
48
- request:
49
- e:
50
-
51
- Returns:
52
-
53
- """
54
- logger.exception(e)
55
-
56
- error_name = e.__class__.__name__
57
- return Response(
58
- http_code=500,
59
- server_code=RespCodeEnum.FAILED,
60
- message=f"服务端业务发生未知异常:[{error_name}] {e}",
61
- ).json_resp()
@@ -1,76 +0,0 @@
1
- import inspect
2
-
3
- from sanic import Request
4
-
5
- from sanic_api.api import API
6
- from sanic_api.api.exception import ValidationInitError
7
- from sanic_api.enum import ParamEnum
8
-
9
-
10
- def _do_validation(param_enum: ParamEnum, api: API, data: dict):
11
- if param_enum == ParamEnum.JSON:
12
- req_data = data
13
- elif param_enum in [ParamEnum.QUERY, param_enum.FORM]:
14
- req_data = {}
15
- for k, v in data.items():
16
- if type(v) == list and len(v) == 1:
17
- req_data[k] = v[0]
18
- else:
19
- req_data[k] = v
20
- else:
21
- raise ValidationInitError("未知的验证器类型")
22
-
23
- api.validate_params(req_data, param_enum)
24
-
25
-
26
- def get_handler_param(handler):
27
- """
28
- 获取参数处理器的json、form、query参数
29
- Args:
30
- handler:
31
-
32
- Returns:
33
-
34
- """
35
- sig = inspect.signature(handler)
36
- api_parameter = sig.parameters.get("api")
37
- api_cls = api_parameter.annotation if api_parameter else None
38
- return api_cls
39
-
40
-
41
- async def validators(request: Request):
42
- """
43
- 校验请求参数中间件
44
- Args:
45
- request: 请求
46
-
47
- Returns:
48
-
49
- """
50
- # 如果执行中间价直接就发生了异常则直接抛出
51
- if hasattr(request.ctx, "exception"):
52
- raise request.ctx.exception
53
-
54
- _, handler, _ = request.app.router.get(
55
- request.path,
56
- request.method,
57
- request.headers.getone("host", None),
58
- )
59
-
60
- api_cls = get_handler_param(handler)
61
- if not api_cls:
62
- return
63
-
64
- api: API = api_cls()
65
- if api.json_req_type and api.query_req_type:
66
- raise ValidationInitError("不能同时存在json参数和form参数")
67
-
68
- if api.json_req_type and request.json:
69
- _do_validation(param_enum=ParamEnum.JSON, api=api, data=request.json)
70
- elif api.form_req_type and request.form:
71
- _do_validation(param_enum=ParamEnum.FORM, api=api, data=request.form)
72
- if api.query_req_type and request.query_args:
73
- _do_validation(param_enum=ParamEnum.QUERY, api=api, data=request.args)
74
-
75
- request.match_info.update({"api": api})
76
- request.ctx.api = api
sanic_api/config/sanic.py DELETED
@@ -1,19 +0,0 @@
1
- from pydantic import BaseModel, Field
2
-
3
-
4
- class SanicConfig(BaseModel):
5
- """
6
- sanic 框架本身的配置
7
- """
8
-
9
- # 是否开启OpenAPI规范文档生成
10
- oas: bool = Field(default=True)
11
-
12
- # 访问日志开关
13
- access_log: bool = Field(default=True, alias="ACCESS_LOG", env="ACCESS_LOG")
14
-
15
- # 后台日志记录器,开启可增加一些性能。见:https://sanic.dev/en/plugins/sanic-ext/logger.html
16
- background_log: bool = Field(default=True, alias="LOGGING", env="LOGGING")
17
-
18
- # OpenAPI规范文档的URL前缀
19
- oas_url_prefix: str = Field(default="docs", alias="OAS_URL_PREFIX", env="OAS_URL_PREFIX")
@@ -1,11 +0,0 @@
1
- from pydantic import BaseModel, Field
2
-
3
-
4
- class SanicApiConfig(BaseModel):
5
- """
6
- sanic_api 框架需要的配置
7
- """
8
-
9
- data_tmp: str = Field(title="响应中data类型字段的key", default="data")
10
- code_tmp: str = Field(title="响应中data类型字段的key", default="code")
11
- msg_tmp: str = Field(title="响应中data类型字段的key", default="msg")