CheeseAPI 1.2.3__tar.gz → 1.3.0__tar.gz

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.
@@ -5,4 +5,4 @@ from CheeseAPI.request import Request
5
5
  from CheeseAPI.response import Response, JsonResponse, FileResponse, BaseResponse, RedirectResponse
6
6
  from CheeseAPI.route import Route
7
7
  from CheeseAPI.websocket import WebsocketServer
8
- from CheeseAPI.validator import Validator, ValidateError, validator, Bool
8
+ from CheeseAPI.validator import validator, ValidateError
@@ -0,0 +1,91 @@
1
+ import json
2
+ from typing import TYPE_CHECKING
3
+ from functools import wraps
4
+
5
+ from pydantic import BaseModel, ValidationError
6
+
7
+ from CheeseAPI.response import JsonResponse
8
+
9
+ if TYPE_CHECKING:
10
+ from CheeseAPI.request import Request
11
+ from CheeseAPI.response import BaseResponse
12
+
13
+ class ValidateError(Exception):
14
+ def __init__(self, response: 'BaseResponse'):
15
+ self.response: 'BaseResponse' = response
16
+
17
+ def validator(validator: BaseModel):
18
+ '''
19
+ 为路由函数添加校验装饰器。
20
+
21
+ 校验参数以类的校验属性为key,从路径变量、args、form、cookie、headers按顺序尝试匹配,若全部匹配失败,则会默认为None。
22
+
23
+ 校验通过后,路由函数会收到一个`validator: BaseModel`已校验参数。
24
+
25
+ ```python
26
+ from CheeseAPI import app, validator
27
+ from pydantic import BaseModel, EmailStr, PastDatetime
28
+
29
+ class User(BaseModel):
30
+ mail: EmailStr
31
+ name: str
32
+ birthDate: PastDatetime
33
+
34
+ @app.route.get('/')
35
+ @validator(Form)
36
+ async def test(*, validator: User, **kwargs):
37
+ ...
38
+ ```
39
+
40
+ 在自定义校验中,可以自定义校验失败后返回的Response:
41
+
42
+ ```python
43
+ from CheeseAPI import ValidateError, Response
44
+ from pydantic import BaseModel, field_validator
45
+
46
+ class Form(BaseModel):
47
+ value: str
48
+
49
+ @field_validator('value')
50
+ def value(cls, value: str) -> str:
51
+ if ...:
52
+ raise ValidateError(Response('My Response', 400))
53
+
54
+ ...
55
+ ```
56
+ '''
57
+
58
+ def wrapper(fn):
59
+ @wraps(fn)
60
+ async def decorator(*args, **kwargs):
61
+ request: 'Request' = kwargs['request']
62
+
63
+ _kwargs = {}
64
+ for key in validator.model_fields.keys():
65
+ _kwargs[key] = None
66
+
67
+ for scope in [ 'path', 'args', 'form', 'cookie', 'headers' ]:
68
+ try:
69
+ if scope == 'path':
70
+ _kwargs[key] = kwargs.get(key)
71
+ else:
72
+ _kwargs[key] = getattr(request, scope).get(key)
73
+
74
+ if _kwargs.get(key):
75
+ break
76
+ except:
77
+ ...
78
+
79
+ try:
80
+ _validator = validator(**_kwargs)
81
+ except ValidationError as e:
82
+ return JsonResponse(json.loads(e.json()), 400)
83
+ except ValidateError as e:
84
+ return e.response
85
+
86
+ return await fn(*args, **{
87
+ **kwargs,
88
+ 'validator': _validator
89
+ })
90
+ return decorator
91
+ return wrapper
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: CheeseAPI
3
- Version: 1.2.3
3
+ Version: 1.3.0
4
4
  Summary: 一款web协程框架。
5
5
  Project-URL: Source, https://github.com/CheeseUnknown/CheeseAPI
6
6
  Author-email: Cheese Unknown <cheese@cheese.ren>
@@ -9,7 +9,9 @@ Keywords: API,BackEnd
9
9
  Requires-Dist: cheeselog==1.0.*
10
10
  Requires-Dist: cheesesignal==1.1.*
11
11
  Requires-Dist: dill
12
+ Requires-Dist: email-validator
12
13
  Requires-Dist: httptools
14
+ Requires-Dist: pydantic
13
15
  Requires-Dist: uvloop
14
16
  Requires-Dist: websockets
15
17
  Requires-Dist: xmltodict
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "CheeseAPI"
7
- version = "1.2.3"
7
+ version = "1.3.0"
8
8
  description = "一款web协程框架。"
9
9
  readme = "README.md"
10
10
  license-files = { paths = [ "LICENSE" ] }
@@ -20,7 +20,9 @@ dependencies = [
20
20
  "uvloop",
21
21
  "httptools",
22
22
  "CheeseSignal==1.1.*",
23
- "dill"
23
+ "dill",
24
+ "pydantic",
25
+ "email-validator"
24
26
  ]
25
27
 
26
28
  [project.urls]
@@ -1,371 +0,0 @@
1
- import re
2
- from typing import List, Literal, Any, Callable, Dict, TYPE_CHECKING
3
- from functools import wraps
4
-
5
- from CheeseAPI.response import Response, BaseResponse
6
- from CheeseAPI.request import Request
7
-
8
- if TYPE_CHECKING:
9
- from CheeseAPI.app import App
10
-
11
- class ValidateError(Exception):
12
- def __init__(self, response: BaseResponse | None = None):
13
- '''
14
- 在自定义校验函数中抛出此错误,可结束校验并直接返回响应体。
15
- '''
16
-
17
- self.response: BaseResponse | None = response
18
-
19
- class Bool:
20
- '''
21
- 将满足小写后等于true或false的字符串转为`bool`。
22
- '''
23
-
24
- def __new__(cls, value: str) -> bool:
25
- if not isinstance(value, str):
26
- raise ValueError('参数类型错误')
27
-
28
- if value.lower() == 'true':
29
- return True
30
-
31
- if value.lower() == 'false':
32
- return False
33
-
34
- raise ValueError('格式错误')
35
-
36
- class Validator:
37
- def __init__(self,
38
- scope: Literal['form', 'headers', 'args', 'path', 'cookie'],
39
- key: str,
40
- *,
41
- required: bool = False,
42
- default: Any = None,
43
- type: List[object | Callable] | object | Callable = None,
44
- expected_type: str | None = None,
45
- pattern: str | None = None,
46
- min: object | None = None,
47
- max: object | None = None,
48
- enum: List[Any] = [],
49
- fn: Callable | None = None,
50
- response: Response | None = None
51
- ):
52
- '''
53
- 可能的示例:
54
-
55
- ```python
56
- import datetime
57
-
58
- from CheeseAPI import Validator
59
-
60
- Validator('form', 'date', type = [ float, datetime.datetime.fromtimestamp ], expected_type = 'timestamp')
61
-
62
- Validator('form', 'gender', enum = [ 'man', 'woman', None ])
63
-
64
- Validator('form', 'idCard', pattern = r'^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[1-2]\\d|3[0-1])\\d{3}[\\dXx]$', response = Response('身份证格式错误', 400))
65
- ```
66
-
67
- - Args
68
-
69
- - scope: 域范围。form: request.form;headers: request.headers;args: request.args;path: request.path中的动态路由;cookie: request.cookie。
70
-
71
- - type: 遵循`self.type(value) -> object`的类或函数;支持list按顺序转换类型。
72
-
73
- - expected_type: 期望的数据类型,不参与校验,仅供提示。
74
-
75
- - default: 默认值;该值仍会执行校验流程,而不是直接应用。
76
-
77
- - pattern: 对于`str`类型的数据,使用正则表达式进行验证。
78
-
79
- - enum: 指定某些特定的值。
80
-
81
- - fn: 【更多内容查看完整注释】 自定义校验函数;该函数将在所有预设校验完成后执行。
82
-
83
- 该函数结构应为:
84
-
85
- ```python
86
- from typing import Dict, Any
87
-
88
- async def fn(*args, validatedForm: Dict[str, Any], **kwargs) -> Any:
89
- ...
90
- ```
91
-
92
- 参数:
93
-
94
- validatedForm: 之前完成校验的所有数据的字典。
95
-
96
- 返回:
97
-
98
- 若有返回值,则为该参数的最终值。
99
- '''
100
-
101
- self._scope: Literal['form', 'headers', 'args', 'path', 'cookie'] = scope
102
- self.key: str = key
103
- self._type: List[object | Callable] | object | Callable = type
104
- self._expected_type: str | None = type.__name__ if expected_type is None and self._type is not None else expected_type
105
- self.required: bool = required
106
- self._default: Any = default
107
- self._pattern: str | None = pattern
108
- self.min: object | None = min
109
- self.max: object | None = max
110
- if self.min is not None and self.max is not None and self.min > self.max:
111
- raise ValueError('最小范围不能大于最大范围')
112
- self._enum: List[Any] = enum
113
- self._fn: Callable | None = fn
114
- self._response: Response | None = response
115
-
116
- async def _validate(self, app: 'App', *args, request: Request, validatedForm: Dict[str, object], **kwargs):
117
- if f'{self.scope}.{self.key}' not in validatedForm:
118
- if self.scope == 'path':
119
- validatedForm[f'{self.scope}.{self.key}'] = kwargs[self.key]
120
- else:
121
- validatedForm[f'{self.scope}.{self.key}'] = (getattr(request, self.scope) or {}).get(self.key)
122
-
123
- if validatedForm[f'{self.scope}.{self.key}'] is None:
124
- if self.required:
125
- raise ValidateError(self.response or Response(app._text.validator_requiredMessage(self.scope, self.key), 400))
126
- if callable(self.default):
127
- validatedForm[f'{self.scope}.{self.key}'] = self.default()
128
- else:
129
- validatedForm[f'{self.scope}.{self.key}'] = self.default
130
-
131
- if validatedForm[f'{self.scope}.{self.key}'] is not None:
132
- if self.type is not None:
133
- try:
134
- if isinstance(self.type, list):
135
- for type in self.type:
136
- validatedForm[f'{self.scope}.{self.key}'] = type(validatedForm[f'{self.scope}.{self.key}'])
137
- else:
138
- validatedForm[f'{self.scope}.{self.key}'] = self.type(validatedForm[f'{self.scope}.{self.key}'])
139
- except:
140
- raise ValidateError(self.response or Response(app._text.validator_typeMessage(self.scope, self.key, self.expected_type), 400))
141
-
142
- if self.pattern and not re.match(self.pattern, validatedForm[f'{self.scope}.{self.key}']):
143
- raise ValidateError(self.response or Response(app._text.validator_patternMessage(self.scope, self.key), 400))
144
-
145
- if self.min and validatedForm[f'{self.scope}.{self.key}'] < self.min:
146
- raise ValidateError(self.response or Response(app._text.validator_minMessage(self.scope, self.key, self.min), 400))
147
-
148
- if self.max and validatedForm[f'{self.scope}.{self.key}'] > self.max:
149
- raise ValidateError(self.response or Response(app._text.validator_maxMessage(self.scope, self.key, self.max), 400))
150
-
151
- if self.enum and validatedForm[f'{self.scope}.{self.key}'] not in self.enum:
152
- raise ValidateError(self.response or Response(app._text.validator_enumMessage(self.scope, self.key, self.enum), 400))
153
-
154
- if self.fn:
155
- try:
156
- _value = await self.fn(*args, **{
157
- 'request': request,
158
- 'validatedForm': validatedForm,
159
- **kwargs
160
- })
161
- if _value is not None:
162
- validatedForm[f'{self.scope}.{self.key}'] = _value
163
- except ValidateError as e:
164
- raise ValidateError(e.response or self.response or Response(status = 500))
165
-
166
- @property
167
- def scope(self) -> Literal['form', 'headers', 'args', 'path', 'cookie']:
168
- '''
169
- 域范围。
170
-
171
- - form: request.form。
172
-
173
- - headers: request.headers。
174
-
175
- - args: request.args。
176
-
177
- - path: request.path中的动态路由。
178
-
179
- - cookie: request.cookie
180
- '''
181
-
182
- return self._scope
183
-
184
- @scope.setter
185
- def scope(self, value: Literal['form', 'headers', 'args', 'path', 'cookie']):
186
- self._scope = value
187
-
188
- @property
189
- def type(self) -> object | Callable | None:
190
- '''
191
- 遵循`type(value) -> object`的类或函数;支持list按顺序转换类型。
192
- '''
193
-
194
- return self._type
195
-
196
- @type.setter
197
- def type(self, value: object | Callable | None):
198
- self._type = value
199
-
200
- @property
201
- def expected_type(self) -> str:
202
- '''
203
- 期望的数据类型,不参与校验,仅供提示。
204
- '''
205
-
206
- return self._expected_type
207
-
208
- @expected_type.setter
209
- def expected_type(self, value: str | None):
210
- self._expected_type = self.type.__name__ if value is None and self.type is not None else value
211
-
212
- @property
213
- def default(self) -> Any:
214
- '''
215
- 默认值;该值仍会执行校验流程,而不是直接应用。
216
- '''
217
-
218
- return self._default
219
-
220
- @default.setter
221
- def default(self, value: Any):
222
- self._default = value
223
-
224
- @property
225
- def pattern(self) -> str | None:
226
- '''
227
- 对于`str`类型的数据,使用正则表达式进行验证。
228
- '''
229
-
230
- return self._pattern
231
-
232
- @pattern.setter
233
- def pattern(self, value: str | None):
234
- self._pattern = value
235
-
236
- @property
237
- def enum(self) -> List[Any]:
238
- '''
239
- 指定某些特定的值。
240
- '''
241
-
242
- return self._enum
243
-
244
- @enum.setter
245
- def enum(self, value: List[Any]):
246
- self._enum = value
247
-
248
- @property
249
- def fn(self) -> Callable | None:
250
- '''
251
- 自定义校验函数;该函数将在所有预设校验完成后执行。
252
-
253
- 该函数结构应为:
254
-
255
- ```python
256
- from typing import Dict, Any
257
-
258
- async def fn(*args, validatedForm: Dict[str, Any], **kwargs) -> Any:
259
- ...
260
- ```
261
-
262
- 参数:
263
-
264
- validatedForm: 之前完成校验的所有数据的字典。
265
-
266
- 返回:
267
-
268
- 若有返回值,则为该参数的最终值。
269
- '''
270
-
271
- return self._fn
272
-
273
- @fn.setter
274
- def fn(self, value: Callable | None):
275
- self._fn = value
276
-
277
- @property
278
- def response(self) -> Response | None:
279
- '''
280
- 校验失败返回的响应体;若为`None`,则使用系统默认值。
281
- '''
282
-
283
- return self._response
284
-
285
- @response.setter
286
- def response(self, value: Response | None):
287
- self._response = value
288
-
289
- def validator(validators: List[Validator]):
290
- '''
291
- 为路由函数添加校验装饰器。
292
-
293
- 校验通过后,路由函数会收到一个`validatedForm: Dict[str, Any]`参数,该参数为转换后的数据字典,key为`f'{validator.scope}.{validator.key}'`;在校验过程中,该参数也会在校验器中传递,其中包含了已经通过校验的数据。
294
-
295
- 下面是一个综合示例:
296
-
297
- ```python
298
- import datetime
299
- from typing import Any
300
-
301
- from CheeseAPI import app, validator, Validator, Mail
302
-
303
- async def birthDateValidator(*args, validatedForm, **kwargs):
304
- date = datetime.datetime.now()
305
- if validatedForm['form.birthDate'] > date:
306
- raise ValidateError(Response('出生日期异常', 400))
307
-
308
- @app.route.get('/')
309
- @validator([
310
- Validator('form', 'name', required = True),
311
- Validator('form', 'birthDate', type = [ float, datetime.datetime.fromtimestamp ], expected_type = 'timestamp', fn = birthDateValidator),
312
- Validator('form', 'gender', enum = [ 'man', 'woman', None ]),
313
- Validator('form', 'idCard', pattern = r'^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[1-2]\\d|3[0-1])\\d{3}[\\dXx]$', response = Response('身份证格式错误', 400))
314
- ])
315
- async def test(*args, validatedForm: Dict[str, Any], **kwargs):
316
- ...
317
- ```
318
-
319
- 将一个校验器拆分为两个,可以分布执行校验,以实现优先统一校验某一部分;下面示例将展示如何将birthDate的自定义校验放到所有校验器都完成之后执行:
320
-
321
- ```python
322
- import datetime
323
- from typing import Any
324
-
325
- from CheeseAPI import app, validator, Validator, Mail
326
-
327
- async def birthDateValidator(*args, validatedForm, **kwargs):
328
- date = datetime.datetime.now()
329
- if validatedForm['form.birthDate'] > date:
330
- raise ValidateError(Response('出生日期异常', 400))
331
-
332
- @app.route.get('/')
333
- @validator([
334
- Validator('form', 'name', required = True),
335
- Validator('form', 'birthDate', type = [ float, datetime.datetime.fromtimestamp ], expected_type = 'timestamp'),
336
- Validator('form', 'gender', enum = [ 'man', 'woman', None ]),
337
- Validator('form', 'idCard', pattern = r'^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[1-2]\\d|3[0-1])\\d{3}[\\dXx]$', response = Response('身份证格式错误', 400)),
338
- Validator('form', 'birthDate', fn = birthDateValidator)
339
- ])
340
- async def test(*args, validatedForm: Dict[str, Any], **kwargs):
341
- ...
342
- ```
343
-
344
- 由于在执行自定义校验前已经完成了类型校验,就不再需要重复填写,可直接获取`validatedForm['form.birthDate']`。
345
-
346
- - Args
347
-
348
- - validators: 校验器会按顺序执行。
349
- '''
350
-
351
- def wrapper(fn):
352
- @wraps(fn)
353
- async def decorator(*args, **kwargs):
354
- from CheeseAPI.app import app
355
-
356
- validatedForm: Dict[str, object] = {}
357
- for validator in validators:
358
- try:
359
- await validator._validate(app, *args, **{
360
- **kwargs,
361
- 'validatedForm': validatedForm
362
- })
363
- except ValidateError as e:
364
- return e.response
365
-
366
- return await fn(*args, **{
367
- **kwargs,
368
- 'validatedForm': validatedForm
369
- })
370
- return decorator
371
- return wrapper
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes