sanic-api 0.2.9__py3-none-any.whl → 0.3.0a2__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 (44) hide show
  1. sanic_api/__init__.py +3 -21
  2. sanic_api/api/__init__.py +17 -2
  3. sanic_api/api/model.py +0 -46
  4. sanic_api/api/request.py +138 -0
  5. sanic_api/api/response.py +34 -0
  6. sanic_api/app.py +275 -0
  7. sanic_api/config/__init__.py +1 -1
  8. sanic_api/config/setting.py +71 -30
  9. sanic_api/logger/__init__.py +0 -1
  10. sanic_api/logger/config.py +99 -77
  11. sanic_api/logger/extension.py +129 -0
  12. sanic_api/openapi/__init__.py +0 -7
  13. sanic_api/openapi/extension.py +41 -0
  14. {example → sanic_api/utils}/__init__.py +0 -0
  15. sanic_api/{enum.py → utils/enum.py} +58 -88
  16. {sanic_api-0.2.9.dist-info → sanic_api-0.3.0a2.dist-info}/METADATA +30 -10
  17. sanic_api-0.3.0a2.dist-info/RECORD +19 -0
  18. {sanic_api-0.2.9.dist-info → sanic_api-0.3.0a2.dist-info}/WHEEL +1 -1
  19. example/__main__.py +0 -19
  20. example/api/__init__.py +0 -1
  21. example/api/t1/__init__.py +0 -2
  22. example/api/t1/t1_1.py +0 -22
  23. example/api/t1/t1_2.py +0 -9
  24. example/api/t2/__init__.py +0 -1
  25. example/api/t2/t2.py +0 -9
  26. example/api/t2/t3/__init__.py +0 -1
  27. example/api/t2/t3/t3.py +0 -9
  28. example/api/user.py +0 -89
  29. example/api/validator.py +0 -97
  30. example/app.py +0 -62
  31. example/settings.py +0 -10
  32. sanic_api/api/api.py +0 -177
  33. sanic_api/api/exception.py +0 -46
  34. sanic_api/api/extend.py +0 -25
  35. sanic_api/api/handle_exception.py +0 -61
  36. sanic_api/api/validators.py +0 -76
  37. sanic_api/config/sanic.py +0 -19
  38. sanic_api/config/sanic_api.py +0 -11
  39. sanic_api/logger/extend.py +0 -71
  40. sanic_api/logger/sanic_http.py +0 -68
  41. sanic_api/openapi/openapi.py +0 -102
  42. sanic_api/utils.py +0 -112
  43. sanic_api-0.2.9.dist-info/RECORD +0 -38
  44. {sanic_api-0.2.9.dist-info → sanic_api-0.3.0a2.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,71 +0,0 @@
1
- import logging
2
- import logging.config
3
- import sys
4
- import time
5
-
6
- from loguru import logger
7
-
8
- # noinspection PyProtectedMember
9
- from loguru._defaults import env
10
- from sanic import HTTPResponse, Request
11
- from sanic.application.constants import Mode
12
- from sanic.server import HttpProtocol
13
- from sanic_ext import Extension
14
-
15
- from sanic_api.logger.config import InterceptHandler
16
- from sanic_api.logger.sanic_http import SanicHttp
17
-
18
-
19
- class LoggerExtend(Extension):
20
- """
21
- 处理日志的扩展
22
- """
23
-
24
- name = "LoggerExtend"
25
-
26
- def startup(self, bootstrap) -> None:
27
- if not self.included():
28
- return
29
-
30
- log_level = logging.DEBUG if self.app.state.mode is Mode.DEBUG else logging.INFO
31
- log_format = env(
32
- "LOGURU_FORMAT",
33
- str,
34
- "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
35
- "<red>{extra[type]: <10}</red> | "
36
- "<level>{level: <8}</level> | "
37
- "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - {extra[req_id]}<level>{message}</level>",
38
- )
39
- logger.remove()
40
- logger.add(sys.stdout, colorize=True, format=log_format)
41
-
42
- logging.basicConfig(handlers=[InterceptHandler()], level=log_level, force=True)
43
-
44
- HttpProtocol.HTTP_CLASS = SanicHttp
45
- self.app.on_request(self.proc_request, priority=999)
46
- self.app.on_response(self.proc_response, priority=0)
47
-
48
- async def proc_request(self, request: Request):
49
- """
50
- 处理请求的中间件
51
- Args:
52
- request:
53
-
54
- Returns:
55
-
56
- """
57
- request.ctx.st = time.perf_counter()
58
-
59
- async def proc_response(self, request: Request, response: HTTPResponse):
60
- """
61
- 处理响应的中间件
62
- Args:
63
- request: 请求响应
64
- response: 响应
65
-
66
- Returns:
67
-
68
- """
69
- request.ctx.et = time.perf_counter()
70
-
71
- return response
@@ -1,68 +0,0 @@
1
- from sanic import Request
2
- from sanic.http import Http
3
- from sanic.log import access_logger
4
-
5
- from sanic_api.utils import json_dumps
6
-
7
-
8
- class SanicHttp(Http):
9
- def log_response(self) -> None:
10
- """
11
- 自定义输出访问日志
12
- Returns:
13
-
14
- """
15
- req, res = self.request, self.response
16
-
17
- dt = (req.ctx.et - req.ctx.st) * 1000
18
- size = getattr(self, "response_bytes_left", getattr(self, "response_size", -1))
19
- req_args = self.get_req_args(req)
20
- extra = {
21
- "status": getattr(res, "status", 0),
22
- "byte": self.format_size(size),
23
- "host": "UNKNOWN",
24
- "request": "nil",
25
- "time": f"{dt:.4f} ms",
26
- "req_args": f" args: {req_args}" if req_args else "",
27
- }
28
- if req is not None:
29
- if req.remote_addr or req.ip:
30
- extra["host"] = f"{req.remote_addr or req.ip}:{req.port}"
31
- extra["request"] = f"{req.method} {req.url}"
32
- access_logger.info("", extra=extra)
33
-
34
- def format_size(self, size: float):
35
- """
36
- 格式化输出大小
37
- :param size: 大小
38
- :return: 返回格式化的字符串
39
- """
40
- for count in ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB"]:
41
- if -1024.0 < size < 1024.0:
42
- return f"{size:3.1f} {count}"
43
- size /= 1024.0
44
- return f"{size:3.1f} YB"
45
-
46
- def get_req_args(self, request: Request) -> str:
47
- """
48
- 获取请求参数
49
- Args:
50
- request: 请求
51
-
52
- Returns:
53
- 返回具有 json、query、form参数的json
54
- """
55
- data = {}
56
- for attr in ["args", "form"]:
57
- attr_data = {}
58
- for k, v in getattr(request, attr).items():
59
- if type(v) == list and len(v) == 1:
60
- attr_data[k] = v[0]
61
- else:
62
- attr_data[k] = v
63
- if attr_data:
64
- data[attr] = attr_data
65
- if request.json:
66
- data["json"] = request.json
67
-
68
- return json_dumps(data) if data else ""
@@ -1,102 +0,0 @@
1
- from pydantic import BaseModel
2
- from sanic import Sanic
3
- from sanic_ext.extensions.openapi.builders import (
4
- OperationBuilder,
5
- OperationStore,
6
- SpecificationBuilder,
7
- )
8
- from sanic_ext.extensions.openapi.definitions import Schema
9
- from sanic_ext.extensions.openapi.types import Array, Object
10
- from sanic_ext.utils.route import get_all_routes
11
-
12
- from sanic_api.api import API
13
- from sanic_api.api.model import ListRespModel
14
- from sanic_api.api.validators import get_handler_param
15
-
16
-
17
- # noinspection PyProtectedMember
18
- def auto_doc(app: Sanic):
19
- config = app.config
20
- specification = SpecificationBuilder()
21
-
22
- for (
23
- uri,
24
- route_name,
25
- _route_parameters,
26
- method_handlers,
27
- _host,
28
- ) in get_all_routes(app, config.OAS_URL_PREFIX):
29
- uri = uri if uri == "/" else uri.rstrip("/")
30
-
31
- for method, _handler in method_handlers:
32
- if (
33
- (method == "OPTIONS" and app.config.OAS_IGNORE_OPTIONS)
34
- or (method == "HEAD" and app.config.OAS_IGNORE_HEAD)
35
- or method == "TRACE"
36
- ):
37
- continue
38
-
39
- if hasattr(_handler, "view_class"):
40
- _handler = getattr(_handler.view_class, method.lower())
41
- operation: OperationBuilder = OperationStore()[_handler]
42
-
43
- if operation._exclude or "openapi" in operation.tags:
44
- continue
45
-
46
- api_cls = get_handler_param(_handler)
47
- if not api_cls:
48
- continue
49
-
50
- api: API = api_cls()
51
-
52
- # 读取蓝图上面的 blueprint.ctx.desc 属性来代替name设置中文tag名
53
- if len(route_name.split(".")) > 1:
54
- blueprint = app.blueprints[route_name.split(".")[0]]
55
- blueprint.ctx.desc = blueprint.ctx.desc or blueprint.name
56
- api.tags.insert(0, blueprint.ctx.desc)
57
-
58
- # 设置接口的标签和描述
59
- tags = set(api.tags)
60
- operation.tag(*tags)
61
- tags_str = " ".join([f"[{tag}](/docs#tag/{tag})" for tag in tags])
62
- operation.describe(description=f"### 标签: {tags_str}\n{api.description}")
63
-
64
- if api.json_req_type:
65
- body_type = api.json_req_type
66
- mine_type = "application/json"
67
- elif api.form_req_type:
68
- body_type = api.form_req_type
69
- mine_type = "application/x-www-form-urlencoded"
70
- else:
71
- body_type, mine_type, body_dict = ["", "", {}]
72
-
73
- if body_type:
74
- body_schema: dict = body_type.schema(ref_template="#/components/schemas/{model}")
75
- body_dict = {
76
- mine_type: Object(body_schema["properties"]),
77
- }
78
- for model_name, schema_model in body_schema.get("definitions", {}).items():
79
- specification.add_component("schemas", model_name, schema_model)
80
- body_dict[mine_type]._fields["required"] = body_schema.get("required", [])
81
- operation.body(body_dict)
82
-
83
- if api.query_req_type:
84
- for k, v in api.query_req_type.schema()["properties"].items(): # type: (str, dict)
85
- operation.parameter(k, Schema(**v))
86
-
87
- if api.response_type and issubclass(api.response_type, BaseModel):
88
- resp_schema = api.response_type.schema(ref_template="#/components/schemas/{model}")
89
- schema: Schema = Object(resp_schema["properties"])
90
- if issubclass(api.response_type, ListRespModel):
91
- schema = Array(schema)
92
- for model_name, schema_model in resp_schema.get("definitions", {}).items():
93
- specification.add_component("schemas", model_name, schema_model)
94
- operation.response(
95
- status=200,
96
- content={"application/json": schema},
97
- description="成功",
98
- )
99
- specification.add_component("schemas", api.response_type.__name__, schema)
100
-
101
- operation._app = app
102
- specification.operation(uri, method, operation)
sanic_api/utils.py DELETED
@@ -1,112 +0,0 @@
1
- from decimal import Decimal
2
- from importlib import import_module
3
- from pathlib import Path
4
- from typing import Dict, List, Optional
5
-
6
- import orjson
7
- from sanic import Blueprint, Request, Sanic
8
- from sanic.blueprint_group import BlueprintGroup
9
- from sanic.exceptions import ServerError as SanicServerError
10
-
11
-
12
- def getpath_by_root(path: str) -> Path:
13
- """
14
- 根据根目录获取路径
15
- 基于 os.getcwd() 的同级路径、父目录来获取
16
- Args:
17
- path: 相对server的子路径
18
-
19
- Returns:
20
- 完整路径
21
- """
22
- return (Path.cwd() / path).absolute()
23
-
24
-
25
- def json_dumps(data: dict, default=None) -> str:
26
- """
27
- 调用orjson进行dumps
28
- Args:
29
- data: 数据
30
- default: 数量处理方法
31
-
32
- Returns:
33
- 返回json字符串
34
- """
35
-
36
- def _default(item):
37
- if isinstance(item, Decimal):
38
- return float(item.to_eng_string())
39
-
40
- json_bytes = orjson.dumps(
41
- data,
42
- default=default or _default,
43
- option=orjson.OPT_APPEND_NEWLINE | orjson.OPT_INDENT_2,
44
- )
45
- return json_bytes.decode("utf-8")
46
-
47
-
48
- def get_current_request() -> Optional[Request]:
49
- """ "
50
- 获取当前请求
51
- """
52
- try:
53
- return Request.get_current()
54
- except SanicServerError:
55
- return None
56
-
57
-
58
- def auto_blueprint(sanic_app: Sanic, base_api_module_name: str) -> None:
59
- """
60
- 自动生成蓝图
61
- Args:
62
- sanic_app: app
63
- base_api_module_name: api层模块名称
64
-
65
- Returns:
66
-
67
- """
68
- # 导入base_api_module_name模块并获取其文件夹路径
69
- base_api_dir: Path = Path.cwd() / base_api_module_name
70
-
71
- # 创建根API蓝图组
72
- root_group: BlueprintGroup = BlueprintGroup(base_api_module_name)
73
-
74
- blueprint_group_map: Dict[str, BlueprintGroup] = {}
75
-
76
- # 遍历所有__init__.py文件,查找蓝图并创建对应的蓝图组
77
- init_files: List[Path] = list(base_api_dir.glob("**/__init__.py"))
78
- for file in reversed(init_files):
79
- # 忽略__init__.py
80
- init_file: Path = file.parent
81
- # 获取该蓝图所在的模块路径和名称
82
- module_path: str = init_file.relative_to(base_api_dir.parent).with_suffix("").as_posix()
83
- module_name: str = module_path.replace("/", ".")
84
-
85
- # 导入蓝图所在的模块,并获取该模块下的所有蓝图
86
- module = import_module(module_name, base_api_module_name)
87
- blueprints = [getattr(module, attr) for attr in dir(module) if isinstance(getattr(module, attr), Blueprint)]
88
- # 拆分模块路径,创建对应的蓝图组并添加到父级蓝图组中
89
- parts = [path for path in module_path.split("/") if path not in [base_api_module_name, init_file.name]]
90
-
91
- if len(blueprints) == 1:
92
- blueprint = blueprints[0]
93
- if not parts:
94
- blueprint_group = blueprint_group_map.get(init_file.name)
95
- if blueprint_group:
96
- blueprint.url_prefix = ""
97
- blueprint_group.append(blueprint)
98
- root_group.append(blueprint_group)
99
- else:
100
- root_group.append(blueprint)
101
- else:
102
- for part in parts:
103
- group = blueprint_group_map.get(part, BlueprintGroup(part))
104
- group.append(blueprint)
105
- blueprint_group_map[part] = group
106
- else:
107
- group = BlueprintGroup(init_file.name)
108
- group.extend(blueprints)
109
- root_group.append(group)
110
-
111
- # 将根API蓝图组添加到应用中
112
- sanic_app.blueprint(root_group)
@@ -1,38 +0,0 @@
1
- example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- example/__main__.py,sha256=shOhgu0-e98nRuhXGzhi3rWsiZmpXZ9SDBLhgwvsFIY,605
3
- example/api/__init__.py,sha256=itThNDyCx_NwzUqClx_8igf1FHakygEz9y2v8tJamkE,34
4
- example/api/t1/__init__.py,sha256=KJ90I8L3UhOgUXA_uyoD06Rqz01mzhPeDqA7lpZXY38,66
5
- example/api/t1/t1_1.py,sha256=78QCk_BwCs_6s9nnkuWPGoHaNosujvUcJVsCjvn_Xhg,634
6
- example/api/t1/t1_2.py,sha256=071pQz-611GuY_DSKnZkRKdSh0J15fVPphTMvodexqc,233
7
- example/api/t2/__init__.py,sha256=Ni_NSedOlsH73KmeW5nW4J26MhBmbHg_1NbrJ3d72Co,29
8
- example/api/t2/t2.py,sha256=NqXZOKKZUmuLd2ce8TwtbBdIEkScbma8ngK5d_xxWXc,248
9
- example/api/t2/t3/__init__.py,sha256=2PBSrojyYILtU7rVFTBh-ULIFmboXQTbBr1vJ7h7c50,29
10
- example/api/t2/t3/t3.py,sha256=Kw7h0I43CMpan3tSmlecFHcQTao_X9dXVmYJUSd-Wz0,248
11
- example/api/user.py,sha256=BwAKVaaCylu_RfwcmIs2459ojgI9_hjg5fA9Whytcog,1844
12
- example/api/validator.py,sha256=-eZcXOIhaB-N-MlhVyHFzK6u6CnNBALKJsfC2nOyNho,1722
13
- example/app.py,sha256=_MJrYtVxcUz2a_ULYlXmqwPzRwaDyx3OF3QIZhJDaL0,1202
14
- example/settings.py,sha256=0HkZcxc7ArZfP_x31A9amL-cOw98XVjAxszOMj0PHII,134
15
- sanic_api/__init__.py,sha256=U2PtA7oaz_UtUqNg40fnpTAwroxxUle4XaEfi1EqDJY,348
16
- sanic_api/api/__init__.py,sha256=0Tb6LsOFscOqYCzgiZbR7UqP_cPGOOE-5is8YclWhI4,51
17
- sanic_api/api/api.py,sha256=SmGoSS7bnhVNH_RYMLXS3dCUuZD2rWy7ZXXVbi_9MBc,5486
18
- sanic_api/api/exception.py,sha256=hs4SpDLQjqGCKgQOa1WNocbTqheoIulr5Bplpm0Mc0w,1216
19
- sanic_api/api/extend.py,sha256=So_X0KgkYeOhDwFIziU1Q8qkeoPeIRisBU5nOMpJaUw,706
20
- sanic_api/api/handle_exception.py,sha256=Opq18Y1loRfBKtr_UTAyLMLT-C4vORnHFRhHKFNKX0o,1206
21
- sanic_api/api/model.py,sha256=vdIBfkJzQpAKKG5LpdFlH9UlbrNhUlcrG0rmFgh6QUY,1071
22
- sanic_api/api/validators.py,sha256=TCb0eRfKfTt11HN-x6u7Ci2j6cpH6UKDKGceaKU2TxE,2078
23
- sanic_api/config/__init__.py,sha256=G59ZgoVTr9dSvjDKh6y3_CkFn_uwbKPi0C3wlvK37xw,37
24
- sanic_api/config/sanic.py,sha256=BLKJgwsLVJvT1YlmW21FQXEwxSIHAhngu9Zwqe8Qh6M,636
25
- sanic_api/config/sanic_api.py,sha256=jIgrlOizjh3eXxiUhQ9RFiG5noAuvjTju27a4mL-xgU,373
26
- sanic_api/config/setting.py,sha256=oGvqkUhQVbPWhoPLPZ6uVOwwSpw8JWqJtzjvYa4YLFA,719
27
- sanic_api/enum.py,sha256=bBbYeys4CHI22sD_My8EIewOOznXb0nQmuIPKzZn-EM,2040
28
- sanic_api/logger/__init__.py,sha256=HPUhYmmzI-NHOo6uf8NGmMlSSN3gkGrmUN6TB5Qhxmc,33
29
- sanic_api/logger/config.py,sha256=1eEEp3YpoTCywgOeE8OZp4X0ghF4E9W52FBEAlTCVfw,2511
30
- sanic_api/logger/extend.py,sha256=rD7NxUFZE2esPUbpOlYul5ZNnDTjepPsu9iLgZ9JA00,1911
31
- sanic_api/logger/sanic_http.py,sha256=Nl1dM54XLTRO99rmzUbpR_vi-wSN_eK_9pei1kNJ7UE,2102
32
- sanic_api/openapi/__init__.py,sha256=_n-pklcQHcaor8WrJMGxc4HYNggZBfe3mmbkCN57fVI,146
33
- sanic_api/openapi/openapi.py,sha256=2Y3J8CJeT5LTLJGsQoCS98QGAZcyE3HgRTKWT2LKoao,4147
34
- sanic_api/utils.py,sha256=9dv_FWHb6ynuzoupOvIFPkar5Y0dlrFF977LjKIysHc,3564
35
- sanic_api-0.2.9.dist-info/WHEEL,sha256=B19PGBCYhWaz2p_UjAoRVh767nYQfk14Sn4TpIZ-nfU,87
36
- sanic_api-0.2.9.dist-info/METADATA,sha256=T7Z6L2QAvzOSjG8PQ3c84ekwDew70rP3WKk60U8mi7w,2312
37
- sanic_api-0.2.9.dist-info/licenses/LICENSE.txt,sha256=T20w-F8AfuFO9CHy2mSk_An6T9eV4X4rGA01i-gp4M4,1090
38
- sanic_api-0.2.9.dist-info/RECORD,,