CheeseAPI 1.2.2__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
@@ -480,7 +480,7 @@ class Handle:
480
480
  try:
481
481
  Server, kwargs = self._app.routeBus._match(protocol.request.path, 'WEBSOCKET')
482
482
  protocol.kwargs.update(kwargs)
483
- except KeyError as e:
483
+ except Route_404_Exception as e:
484
484
  await self.websocket_afterRequest(protocol)
485
485
  if self._app.signal.websocket_afterRequest.receivers:
486
486
  await self._app.signal.websocket_afterRequest.async_send(**{
@@ -488,27 +488,30 @@ class Handle:
488
488
  **protocol.kwargs
489
489
  })
490
490
 
491
- if e.args[0] == 0:
492
- await self.websocket_404(protocol)
493
- if self._app.signal.websocket_404.receivers:
494
- await self._app.signal.websocket_404.async_send(**{
495
- 'request': protocol.request,
496
- 'response': protocol.response,
497
- **protocol.kwargs
498
- })
499
- return await self.websocket_response(protocol)
500
-
501
- elif e.args[0] == 1:
502
- await self.websocket_405(protocol)
503
- if self._app.signal.websocket_405.receivers:
504
- await self._app.signal.websocket_405.async_send(**{
505
- 'request': protocol.request,
506
- 'response': protocol.response,
507
- **protocol.kwargs
508
- })
509
- return await self.websocket_response(protocol)
491
+ await self.websocket_404(protocol)
492
+ if self._app.signal.websocket_404.receivers:
493
+ await self._app.signal.websocket_404.async_send(**{
494
+ 'request': protocol.request,
495
+ 'response': protocol.response,
496
+ **protocol.kwargs
497
+ })
498
+ return await self.websocket_response(protocol)
499
+ except Route_405_Exception as e:
500
+ await self.websocket_afterRequest(protocol)
501
+ if self._app.signal.websocket_afterRequest.receivers:
502
+ await self._app.signal.websocket_afterRequest.async_send(**{
503
+ 'request': protocol.request,
504
+ **protocol.kwargs
505
+ })
510
506
 
511
- raise e
507
+ await self.websocket_405(protocol)
508
+ if self._app.signal.websocket_405.receivers:
509
+ await self._app.signal.websocket_405.async_send(**{
510
+ 'request': protocol.request,
511
+ 'response': protocol.response,
512
+ **protocol.kwargs
513
+ })
514
+ return await self.websocket_response(protocol)
512
515
 
513
516
  protocol.server = Server()
514
517
 
@@ -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.2
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.2"
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,368 +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
- validatedForm[f'{self.scope}.{self.key}'] = self.default
127
-
128
- if validatedForm[f'{self.scope}.{self.key}'] is not None:
129
- if self.type is not None:
130
- try:
131
- if isinstance(self.type, list):
132
- for type in self.type:
133
- validatedForm[f'{self.scope}.{self.key}'] = type(validatedForm[f'{self.scope}.{self.key}'])
134
- else:
135
- validatedForm[f'{self.scope}.{self.key}'] = self.type(validatedForm[f'{self.scope}.{self.key}'])
136
- except:
137
- raise ValidateError(self.response or Response(app._text.validator_typeMessage(self.scope, self.key, self.expected_type), 400))
138
-
139
- if self.pattern and not re.match(self.pattern, validatedForm[f'{self.scope}.{self.key}']):
140
- raise ValidateError(self.response or Response(app._text.validator_patternMessage(self.scope, self.key), 400))
141
-
142
- if self.min and validatedForm[f'{self.scope}.{self.key}'] < self.min:
143
- raise ValidateError(self.response or Response(app._text.validator_minMessage(self.scope, self.key, self.min), 400))
144
-
145
- if self.max and validatedForm[f'{self.scope}.{self.key}'] > self.max:
146
- raise ValidateError(self.response or Response(app._text.validator_maxMessage(self.scope, self.key, self.max), 400))
147
-
148
- if self.enum and validatedForm[f'{self.scope}.{self.key}'] not in self.enum:
149
- raise ValidateError(self.response or Response(app._text.validator_enumMessage(self.scope, self.key, self.enum), 400))
150
-
151
- if self.fn:
152
- try:
153
- _value = await self.fn(*args, **{
154
- 'request': request,
155
- 'validatedForm': validatedForm,
156
- **kwargs
157
- })
158
- if _value is not None:
159
- validatedForm[f'{self.scope}.{self.key}'] = _value
160
- except ValidateError as e:
161
- raise ValidateError(e.response or self.response or Response(status = 500))
162
-
163
- @property
164
- def scope(self) -> Literal['form', 'headers', 'args', 'path', 'cookie']:
165
- '''
166
- 域范围。
167
-
168
- - form: request.form。
169
-
170
- - headers: request.headers。
171
-
172
- - args: request.args。
173
-
174
- - path: request.path中的动态路由。
175
-
176
- - cookie: request.cookie
177
- '''
178
-
179
- return self._scope
180
-
181
- @scope.setter
182
- def scope(self, value: Literal['form', 'headers', 'args', 'path', 'cookie']):
183
- self._scope = value
184
-
185
- @property
186
- def type(self) -> object | Callable | None:
187
- '''
188
- 遵循`type(value) -> object`的类或函数;支持list按顺序转换类型。
189
- '''
190
-
191
- return self._type
192
-
193
- @type.setter
194
- def type(self, value: object | Callable | None):
195
- self._type = value
196
-
197
- @property
198
- def expected_type(self) -> str:
199
- '''
200
- 期望的数据类型,不参与校验,仅供提示。
201
- '''
202
-
203
- return self._expected_type
204
-
205
- @expected_type.setter
206
- def expected_type(self, value: str | None):
207
- self._expected_type = self.type.__name__ if value is None and self.type is not None else value
208
-
209
- @property
210
- def default(self) -> Any:
211
- '''
212
- 默认值;该值仍会执行校验流程,而不是直接应用。
213
- '''
214
-
215
- return self._default
216
-
217
- @default.setter
218
- def default(self, value: Any):
219
- self._default = value
220
-
221
- @property
222
- def pattern(self) -> str | None:
223
- '''
224
- 对于`str`类型的数据,使用正则表达式进行验证。
225
- '''
226
-
227
- return self._pattern
228
-
229
- @pattern.setter
230
- def pattern(self, value: str | None):
231
- self._pattern = value
232
-
233
- @property
234
- def enum(self) -> List[Any]:
235
- '''
236
- 指定某些特定的值。
237
- '''
238
-
239
- return self._enum
240
-
241
- @enum.setter
242
- def enum(self, value: List[Any]):
243
- self._enum = value
244
-
245
- @property
246
- def fn(self) -> Callable | None:
247
- '''
248
- 自定义校验函数;该函数将在所有预设校验完成后执行。
249
-
250
- 该函数结构应为:
251
-
252
- ```python
253
- from typing import Dict, Any
254
-
255
- async def fn(*args, validatedForm: Dict[str, Any], **kwargs) -> Any:
256
- ...
257
- ```
258
-
259
- 参数:
260
-
261
- validatedForm: 之前完成校验的所有数据的字典。
262
-
263
- 返回:
264
-
265
- 若有返回值,则为该参数的最终值。
266
- '''
267
-
268
- return self._fn
269
-
270
- @fn.setter
271
- def fn(self, value: Callable | None):
272
- self._fn = value
273
-
274
- @property
275
- def response(self) -> Response | None:
276
- '''
277
- 校验失败返回的响应体;若为`None`,则使用系统默认值。
278
- '''
279
-
280
- return self._response
281
-
282
- @response.setter
283
- def response(self, value: Response | None):
284
- self._response = value
285
-
286
- def validator(validators: List[Validator]):
287
- '''
288
- 为路由函数添加校验装饰器。
289
-
290
- 校验通过后,路由函数会收到一个`validatedForm: Dict[str, Any]`参数,该参数为转换后的数据字典,key为`f'{validator.scope}.{validator.key}'`;在校验过程中,该参数也会在校验器中传递,其中包含了已经通过校验的数据。
291
-
292
- 下面是一个综合示例:
293
-
294
- ```python
295
- import datetime
296
- from typing import Any
297
-
298
- from CheeseAPI import app, validator, Validator, Mail
299
-
300
- async def birthDateValidator(*args, validatedForm, **kwargs):
301
- date = datetime.datetime.now()
302
- if validatedForm['form.birthDate'] > date:
303
- raise ValidateError(Response('出生日期异常', 400))
304
-
305
- @app.route.get('/')
306
- @validator([
307
- Validator('form', 'name', required = True),
308
- Validator('form', 'birthDate', type = [ float, datetime.datetime.fromtimestamp ], expected_type = 'timestamp', fn = birthDateValidator),
309
- Validator('form', 'gender', enum = [ 'man', 'woman', None ]),
310
- 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))
311
- ])
312
- async def test(*args, validatedForm: Dict[str, Any], **kwargs):
313
- ...
314
- ```
315
-
316
- 将一个校验器拆分为两个,可以分布执行校验,以实现优先统一校验某一部分;下面示例将展示如何将birthDate的自定义校验放到所有校验器都完成之后执行:
317
-
318
- ```python
319
- import datetime
320
- from typing import Any
321
-
322
- from CheeseAPI import app, validator, Validator, Mail
323
-
324
- async def birthDateValidator(*args, validatedForm, **kwargs):
325
- date = datetime.datetime.now()
326
- if validatedForm['form.birthDate'] > date:
327
- raise ValidateError(Response('出生日期异常', 400))
328
-
329
- @app.route.get('/')
330
- @validator([
331
- Validator('form', 'name', required = True),
332
- Validator('form', 'birthDate', type = [ float, datetime.datetime.fromtimestamp ], expected_type = 'timestamp'),
333
- Validator('form', 'gender', enum = [ 'man', 'woman', None ]),
334
- 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)),
335
- Validator('form', 'birthDate', fn = birthDateValidator)
336
- ])
337
- async def test(*args, validatedForm: Dict[str, Any], **kwargs):
338
- ...
339
- ```
340
-
341
- 由于在执行自定义校验前已经完成了类型校验,就不再需要重复填写,可直接获取`validatedForm['form.birthDate']`。
342
-
343
- - Args
344
-
345
- - validators: 校验器会按顺序执行。
346
- '''
347
-
348
- def wrapper(fn):
349
- @wraps(fn)
350
- async def decorator(*args, **kwargs):
351
- from CheeseAPI.app import app
352
-
353
- validatedForm: Dict[str, object] = {}
354
- for validator in validators:
355
- try:
356
- await validator._validate(app, *args, **{
357
- **kwargs,
358
- 'validatedForm': validatedForm
359
- })
360
- except ValidateError as e:
361
- return e.response
362
-
363
- return await fn(*args, **{
364
- **kwargs,
365
- 'validatedForm': validatedForm
366
- })
367
- return decorator
368
- 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