sanic-api 0.3.0a6__py3-none-any.whl → 0.4.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.
sanic_api/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from sanic_api.logger.extension import LoggerExtend
2
2
 
3
- __version__ = "0.3.0a6"
3
+ __version__ = "0.4.0"
sanic_api/api/__init__.py CHANGED
@@ -1,17 +1 @@
1
1
  from sanic_api.api.request import Request
2
- from sanic_api.api.response import BaseResp, BaseRespTml, TempModel
3
-
4
- """
5
- class BaseResponseModel(BaseModel):
6
- "直接返回str、int、float、bool等类型"
7
- ...
8
-
9
- class JsonResponseModel(BaseModel):
10
- "返回JSON的Pydantic结构"
11
- ...
12
-
13
- class ListResponseModel(BaseModel):
14
- "返回JSON的Pydantic结构的列表"
15
- ...
16
-
17
- """
sanic_api/api/error.py ADDED
@@ -0,0 +1,19 @@
1
+ from sanic.errorpages import JSONRenderer
2
+
3
+ from sanic_api.config.setting import JsonRespSettings
4
+
5
+
6
+ class ErrorJSONRenderer(JSONRenderer):
7
+ json_resp_setting: JsonRespSettings | None = None
8
+
9
+ def _generate_output(self, *, full: bool) -> dict:
10
+ data = super()._generate_output(full=full)
11
+ if not self.json_resp_setting or not self.json_resp_setting.use_tml:
12
+ return data
13
+
14
+ data = {
15
+ self.json_resp_setting.code_field_name: self.json_resp_setting.error_code,
16
+ self.json_resp_setting.msg_field_name: data["message"],
17
+ self.json_resp_setting.data_field_name: data,
18
+ }
19
+ return data
sanic_api/api/request.py CHANGED
@@ -4,8 +4,12 @@ from typing import get_origin
4
4
 
5
5
  from pydantic import BaseModel
6
6
  from sanic import Request as SanicRequest
7
+ from sanic.compat import Header
8
+ from sanic.response import JSONResponse
7
9
  from sanic_ext.extensions.openapi.builders import OperationStore
8
10
 
11
+ from sanic_api.config.setting import JsonRespSettings
12
+
9
13
 
10
14
  class Request(SanicRequest):
11
15
  """
@@ -13,6 +17,8 @@ class Request(SanicRequest):
13
17
  加入便于接口参数获取校验的方法
14
18
  """
15
19
 
20
+ json_resp_setting: JsonRespSettings
21
+
16
22
  json_data: BaseModel
17
23
  form_data: BaseModel
18
24
  query_data: BaseModel
@@ -22,8 +28,38 @@ class Request(SanicRequest):
22
28
  _form_data_type: type[BaseModel] | None
23
29
  _query_data_type: type[BaseModel] | None
24
30
 
25
- def __init__(self, *args, **kwargs):
26
- super().__init__(*args, **kwargs)
31
+ def json_resp(
32
+ self,
33
+ data: BaseModel | dict | str | int | float | list,
34
+ server_code: str | int = "",
35
+ server_msg: str = "",
36
+ status: int = 200,
37
+ headers: Header | dict[str, str] | None = None,
38
+ ) -> JSONResponse:
39
+ """
40
+ 自定义返回 JSONResponse 的方法
41
+ 如果使用了模板模式则返回模板模式的数据
42
+ 不使用模版情况下:如果data是dict、list直接返回,否则返回{"data": data}
43
+ Args:
44
+ data: 内容
45
+ server_code: 接口服务码
46
+ server_msg: 接口服务消息
47
+ status: 状态码
48
+ headers: 请求头
49
+
50
+ Returns:
51
+
52
+ """
53
+ data = data.model_dump(mode="json") if isinstance(data, BaseModel) else data
54
+ if self.json_resp_setting.use_tml:
55
+ data = {
56
+ self.json_resp_setting.code_field_name: server_code,
57
+ self.json_resp_setting.msg_field_name: server_msg,
58
+ self.json_resp_setting.data_field_name: data,
59
+ }
60
+ else:
61
+ data = {"data": data} if type(data) not in (dict, list) else data
62
+ return JSONResponse(data, status=status, headers=headers)
27
63
 
28
64
  async def receive_body(self):
29
65
  """
@@ -35,7 +71,6 @@ class Request(SanicRequest):
35
71
  await super().receive_body()
36
72
  self._get_data_type()
37
73
  self._load_data()
38
- self._set_openapi()
39
74
 
40
75
  def _get_type(self, name: str, base_type: type):
41
76
  """
sanic_api/app.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import sentry_sdk
2
2
  from sanic import Sanic, text
3
+ from sanic.errorpages import RENDERERS_BY_CONTENT_TYPE
3
4
  from sanic.log import logger
4
5
  from sanic.worker.loader import AppLoader
5
6
  from sanic_ext import Extend
@@ -7,6 +8,7 @@ from sentry_sdk.integrations.asyncio import AsyncioIntegration
7
8
 
8
9
  from sanic_api import LoggerExtend
9
10
  from sanic_api.api import Request
11
+ from sanic_api.api.error import ErrorJSONRenderer
10
12
  from sanic_api.config import DefaultSettings, RunModeEnum
11
13
 
12
14
 
@@ -55,11 +57,13 @@ class BaseApp:
55
57
  Returns:
56
58
 
57
59
  """
60
+ Request.json_resp_setting = self.settings.json_resp
58
61
  app = Sanic(self.name, configure_logging=False, request_class=Request)
59
62
 
60
63
  self._setup_logger(app)
61
64
  self._setup_config(app)
62
65
  self._setup_openai(app)
66
+ self._setup_error_handler(app)
63
67
 
64
68
  app.main_process_stop(self._main_process_stop)
65
69
  app.main_process_start(self._main_process_start)
@@ -153,11 +157,24 @@ class BaseApp:
153
157
  Returns:
154
158
 
155
159
  """
156
- app.config.LOGGING = True
160
+ # app.config.LOGGING = True
161
+ app.config.FALLBACK_ERROR_FORMAT = "json"
157
162
  self._setup_cors(app)
158
163
 
159
164
  def _setup_openai(self, app: Sanic): ...
160
165
 
166
+ def _setup_error_handler(self, _app: Sanic):
167
+ """
168
+ 设置错误处理器
169
+ Args:
170
+ _app: Sanic App
171
+
172
+ Returns:
173
+
174
+ """
175
+ ErrorJSONRenderer.json_resp_setting = self.settings.json_resp
176
+ RENDERERS_BY_CONTENT_TYPE["application/json"] = ErrorJSONRenderer
177
+
161
178
  def _setup_cors(self, app: Sanic):
162
179
  """
163
180
  设置跨域: 开发模式下允许所有跨域,生产模式下使用配置中的跨域列表
@@ -167,12 +184,13 @@ class BaseApp:
167
184
  Returns:
168
185
 
169
186
  """
170
- cors = ",".join(self.settings.cors_origins)
187
+ cors = ",".join(self.settings.cors.origins)
171
188
  if self.settings.mode == RunModeEnum.DEBNUG:
172
- app.config.CORS_ORIGINS = cors or "*"
173
189
  app.config.CORS_SEND_WILDCARD = True
190
+ app.config.CORS_SUPPORTS_CREDENTIALS = self.settings.cors.supports_credentials
191
+ app.config.CORS_ORIGINS = cors or "*"
174
192
  else:
175
- app.config.CORS_ORIGINS = ",".join(self.settings.cors_origins)
193
+ app.config.CORS_ORIGINS = cors
176
194
 
177
195
  async def _setup_route(self, app: Sanic):
178
196
  """
@@ -220,7 +238,7 @@ class BaseApp:
220
238
  return
221
239
 
222
240
  sentry_sdk.init(
223
- dsn=self.settings.sentry_dsn,
241
+ dsn=str(self.settings.sentry_dsn),
224
242
  # Set traces_sample_rate to 1.0 to capture 100%
225
243
  # of transactions for tracing.
226
244
  traces_sample_rate=1.0,
@@ -37,6 +37,40 @@ class LoggerSettings(BaseModel):
37
37
  loki_url: HttpUrl | None = Field(default=None)
38
38
 
39
39
 
40
+ class JsonRespSettings(BaseModel):
41
+ """
42
+ json 响应配置类
43
+ """
44
+
45
+ # 是否使用 {"code": "0", "msg": "success", "data": {}} 的格式
46
+ use_tml: bool = Field(default=True)
47
+
48
+ error_code: str | int = Field(default="ERROR-1")
49
+
50
+ # code 字段的名字
51
+ code_field_name: str = Field(default="code")
52
+
53
+ # msg 字段的名字
54
+ msg_field_name: str = Field(default="msg")
55
+
56
+ # data 字段的名字
57
+ data_field_name: str = Field(default="data")
58
+
59
+
60
+ class CrosSettings(BaseModel):
61
+ """
62
+ 跨域设置
63
+ """
64
+
65
+ # 地址、地址列表、*
66
+ # 当 credentials等于 'include' 时,origins必须是具体是地址不能是 “*”
67
+ origins: list[str] | None = Field(default_factory=list)
68
+
69
+ # 支持凭证。
70
+ # 当前端启用了withCredentials 后端需要设置这个值为True
71
+ supports_credentials: bool = Field(default=False)
72
+
73
+
40
74
  class DefaultSettings(SettingsBase):
41
75
  """
42
76
  配置类
@@ -62,10 +96,13 @@ class DefaultSettings(SettingsBase):
62
96
  access_log: bool = Field(default=True)
63
97
 
64
98
  # 跨域设置
65
- cors_origins: list[str] | None = Field(default_factory=list)
99
+ cors: CrosSettings = Field(default_factory=CrosSettings)
66
100
 
67
101
  # 哨兵连接dsn,如果存在则会把错误信息推送给哨兵
68
102
  sentry_dsn: HttpUrl | None = Field(default=None)
69
103
 
70
104
  # 日志配置
71
105
  logger: LoggerSettings = Field(default_factory=LoggerSettings)
106
+
107
+ # json 响应配置
108
+ json_resp: JsonRespSettings = Field(default_factory=JsonRespSettings)
@@ -0,0 +1,41 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from sanic import Blueprint
4
+ from sanic_ext.extensions.openapi.blueprint import blueprint_factory
5
+ from sanic_ext.extensions.openapi.builders import SpecificationBuilder
6
+ from sanic_ext.extensions.openapi.extension import OpenAPIExtension as BaseExtension
7
+
8
+ if TYPE_CHECKING:
9
+ from sanic_ext import Extend
10
+
11
+
12
+ class OpenAPIExtension(BaseExtension):
13
+ name = "openapi"
14
+ bp: Blueprint
15
+
16
+ def startup(self, bootstrap: Extend) -> None:
17
+ if self.app.config.OAS:
18
+ self.bp = blueprint_factory(self.app.config)
19
+ self.app.blueprint(self.bp)
20
+ bootstrap._openapi = SpecificationBuilder()
21
+
22
+
23
+ """
24
+ import inspect
25
+ import ast
26
+
27
+ def build_spec(app, loop):
28
+ ...
29
+
30
+ source_code = inspect.getsource(blueprint_factory)
31
+ ast_tree = ast.parse(source_code)
32
+
33
+ my_source_code = inspect.getsource(build_spec)
34
+ my_func_ast = ast.parse(my_source_code)
35
+
36
+ ast_tree.body[0].body[6] = my_func_ast
37
+ modified_code = compile(ast_tree, filename="<ast>", mode="exec")
38
+
39
+
40
+
41
+ """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sanic-api
3
- Version: 0.3.0a6
3
+ Version: 0.4.0
4
4
  Summary: Sanic 框架实用API工具集,拥有自动生成文档、参数校验、配置的导入、日志功能的优化等功能,更好的助力接口的开发
5
5
  Project-URL: homepage, https://github.com/x-haose/sanic-api
6
6
  Project-URL: repository, https://github.com/x-haose/sanic-api
@@ -23,12 +23,10 @@ Classifier: Topic :: Internet :: WWW/HTTP
23
23
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
24
24
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
25
  Requires-Python: >=3.10
26
- Requires-Dist: hs-config==0.1.3a5
26
+ Requires-Dist: hs-config==0.1.3
27
27
  Requires-Dist: loguru>=0.7.2
28
28
  Requires-Dist: loki-logger-handler>=0.1.4
29
29
  Requires-Dist: orjson>=3.8.6
30
- Requires-Dist: pydantic-settings[toml,yaml]>=2.5.2
31
- Requires-Dist: pydantic[dotenv]>=2.9.2
32
30
  Requires-Dist: requests>=2.32.0
33
31
  Requires-Dist: sanic-ext>=23.12.0
34
32
  Requires-Dist: sanic>=24.12.0
@@ -0,0 +1,19 @@
1
+ sanic_api/__init__.py,sha256=xqHsD9gP9fSfHc_67d7H4sqy6TGv9Ygpp6_QAc84OTA,75
2
+ sanic_api/app.py,sha256=TwizAXRAR4E6U6E-HbSPe-UQBjmJ7uzWygVwHG1JXOk,8513
3
+ sanic_api/api/__init__.py,sha256=MxQ7A-ALJOwznJviVt6zjhcIikhEJCKoOF-7c75qtpk,42
4
+ sanic_api/api/error.py,sha256=RikYhTFe_z0tv4nfksH4G6lzcwp5fWjNBC36xhFj-R4,661
5
+ sanic_api/api/model.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ sanic_api/api/request.py,sha256=CzltvJ8NE4UmnUTqGXJmJ7oRtBBT5GRcDADEnf7dkUE,7017
7
+ sanic_api/config/__init__.py,sha256=DVqXHMXANl_xUwBsLVYisr7ZPdX2ubA2_WH3GUZC-Bs,80
8
+ sanic_api/config/setting.py,sha256=K4Z9oUHe4BN0tzJltmlRF5myPDHcIq8fQ0xgK19s3iw,3156
9
+ sanic_api/logger/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ sanic_api/logger/config.py,sha256=5C-kYrE13_cgw7vNxBwBLi4F8BijHakOKs6YSzxsEMA,3031
11
+ sanic_api/logger/extension.py,sha256=xj8DjGW3jN2M7MTu4mk65H1Qs-j5VwvgO0LGahoeQRU,4108
12
+ sanic_api/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ sanic_api/openapi/extension.py,sha256=HCrcEgrj63FXOhx6TJyJDaee6mDuVnuSMA5EtdYUVjc,1009
14
+ sanic_api/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ sanic_api/utils/enum.py,sha256=JGFh8o8kovcR4LF3ekuWJIiZINQspfeYrvVhmFvYK-I,1240
16
+ sanic_api-0.4.0.dist-info/METADATA,sha256=gGiiZ-SQnWgc_LDRi00mwpsa67UJsCViGBLupON1ObQ,4285
17
+ sanic_api-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
+ sanic_api-0.4.0.dist-info/licenses/LICENSE.txt,sha256=T20w-F8AfuFO9CHy2mSk_An6T9eV4X4rGA01i-gp4M4,1090
19
+ sanic_api-0.4.0.dist-info/RECORD,,
sanic_api/api/response.py DELETED
@@ -1,34 +0,0 @@
1
- from typing import Any, ClassVar
2
-
3
- from pydantic import BaseModel, Field, PrivateAttr
4
- from sanic.compat import Header
5
- from sanic.response import JSONResponse
6
-
7
-
8
- class TempModel(BaseModel):
9
- _data_field: str = PrivateAttr(default="data")
10
- data: Any = Field(default=None, title="数据")
11
- code: str = Field(default_factory=str, title="业务状态码")
12
- msg: str = Field(default_factory=str, title="消息")
13
-
14
- def get_data_field_name(self) -> str:
15
- return self._data_field
16
-
17
-
18
- class BaseResp(BaseModel):
19
- temp_data: ClassVar = Ellipsis
20
-
21
- def resp(self, status: int = 200, headers: Header | dict[str, str] | None = None) -> JSONResponse:
22
- data = self.model_dump(mode="json")
23
- return JSONResponse(data, status=status, headers=headers)
24
-
25
-
26
- class BaseRespTml(BaseModel):
27
- temp_data: TempModel | None = Field(default_factory=TempModel)
28
-
29
- def resp(self, status: int = 200, headers: Header | dict[str, str] | None = None) -> JSONResponse:
30
- tmp_data_field_name = self.temp_data.get_data_field_name()
31
- self_data = self.model_dump(mode="json", exclude={"temp_data"})
32
- setattr(self.temp_data, tmp_data_field_name, self_data)
33
- tml_data = self.temp_data.model_dump(mode="json")
34
- return JSONResponse(tml_data, status=status, headers=headers)
@@ -1,19 +0,0 @@
1
- sanic_api/__init__.py,sha256=M5gj0GspFvurRNBBFwqA31zf1DUcjnHyfbSGQUHune4,77
2
- sanic_api/app.py,sha256=t54MJ6fFtvjStpRQZaiFiSJcE_KLQZnDe8k9BJjjGCs,7884
3
- sanic_api/api/__init__.py,sha256=tlfFmLNwI_Y5uQD4EN6h-XRr1YmoAktqEsjlo48kvX0,383
4
- sanic_api/api/model.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- sanic_api/api/request.py,sha256=PN6_PEQRfM_-SkndTmAqNHawzPph8uN3QpkM5Mdegvs,5735
6
- sanic_api/api/response.py,sha256=stQ6sVF_YiWM8cp4vr79w4HSD81zCHUxV8xAcx5Sk2o,1303
7
- sanic_api/config/__init__.py,sha256=DVqXHMXANl_xUwBsLVYisr7ZPdX2ubA2_WH3GUZC-Bs,80
8
- sanic_api/config/setting.py,sha256=qW8xp5alLmDcrZ3rXxQCVE8dsLFeLLfGvYJMM34uXiU,2183
9
- sanic_api/logger/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- sanic_api/logger/config.py,sha256=5C-kYrE13_cgw7vNxBwBLi4F8BijHakOKs6YSzxsEMA,3031
11
- sanic_api/logger/extension.py,sha256=xj8DjGW3jN2M7MTu4mk65H1Qs-j5VwvgO0LGahoeQRU,4108
12
- sanic_api/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- sanic_api/openapi/extension.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- sanic_api/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- sanic_api/utils/enum.py,sha256=JGFh8o8kovcR4LF3ekuWJIiZINQspfeYrvVhmFvYK-I,1240
16
- sanic_api-0.3.0a6.dist-info/METADATA,sha256=LacrqyuBdn3vdZBxqVhzNyFNLRjSCuGeF6JCisI26qc,4379
17
- sanic_api-0.3.0a6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
- sanic_api-0.3.0a6.dist-info/licenses/LICENSE.txt,sha256=T20w-F8AfuFO9CHy2mSk_An6T9eV4X4rGA01i-gp4M4,1090
19
- sanic_api-0.3.0a6.dist-info/RECORD,,