toms-fast 0.2.1__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 (60) hide show
  1. toms_fast-0.2.1.dist-info/METADATA +467 -0
  2. toms_fast-0.2.1.dist-info/RECORD +60 -0
  3. toms_fast-0.2.1.dist-info/WHEEL +4 -0
  4. toms_fast-0.2.1.dist-info/entry_points.txt +2 -0
  5. tomskit/__init__.py +0 -0
  6. tomskit/celery/README.md +693 -0
  7. tomskit/celery/__init__.py +4 -0
  8. tomskit/celery/celery.py +306 -0
  9. tomskit/celery/config.py +377 -0
  10. tomskit/cli/__init__.py +207 -0
  11. tomskit/cli/__main__.py +8 -0
  12. tomskit/cli/scaffold.py +123 -0
  13. tomskit/cli/templates/__init__.py +42 -0
  14. tomskit/cli/templates/base.py +348 -0
  15. tomskit/cli/templates/celery.py +101 -0
  16. tomskit/cli/templates/extensions.py +213 -0
  17. tomskit/cli/templates/fastapi.py +400 -0
  18. tomskit/cli/templates/migrations.py +281 -0
  19. tomskit/cli/templates_config.py +122 -0
  20. tomskit/logger/README.md +466 -0
  21. tomskit/logger/__init__.py +4 -0
  22. tomskit/logger/config.py +106 -0
  23. tomskit/logger/logger.py +290 -0
  24. tomskit/py.typed +0 -0
  25. tomskit/redis/README.md +462 -0
  26. tomskit/redis/__init__.py +6 -0
  27. tomskit/redis/config.py +85 -0
  28. tomskit/redis/redis_pool.py +87 -0
  29. tomskit/redis/redis_sync.py +66 -0
  30. tomskit/server/__init__.py +47 -0
  31. tomskit/server/config.py +117 -0
  32. tomskit/server/exceptions.py +412 -0
  33. tomskit/server/middleware.py +371 -0
  34. tomskit/server/parser.py +312 -0
  35. tomskit/server/resource.py +464 -0
  36. tomskit/server/server.py +276 -0
  37. tomskit/server/type.py +263 -0
  38. tomskit/sqlalchemy/README.md +590 -0
  39. tomskit/sqlalchemy/__init__.py +20 -0
  40. tomskit/sqlalchemy/config.py +125 -0
  41. tomskit/sqlalchemy/database.py +125 -0
  42. tomskit/sqlalchemy/pagination.py +359 -0
  43. tomskit/sqlalchemy/property.py +19 -0
  44. tomskit/sqlalchemy/sqlalchemy.py +131 -0
  45. tomskit/sqlalchemy/types.py +32 -0
  46. tomskit/task/README.md +67 -0
  47. tomskit/task/__init__.py +4 -0
  48. tomskit/task/task_manager.py +124 -0
  49. tomskit/tools/README.md +63 -0
  50. tomskit/tools/__init__.py +18 -0
  51. tomskit/tools/config.py +70 -0
  52. tomskit/tools/warnings.py +37 -0
  53. tomskit/tools/woker.py +81 -0
  54. tomskit/utils/README.md +666 -0
  55. tomskit/utils/README_SERIALIZER.md +644 -0
  56. tomskit/utils/__init__.py +35 -0
  57. tomskit/utils/fields.py +434 -0
  58. tomskit/utils/marshal_utils.py +137 -0
  59. tomskit/utils/response_utils.py +13 -0
  60. tomskit/utils/serializers.py +447 -0
@@ -0,0 +1,434 @@
1
+ """
2
+ 字段类,支持从异步数据源(如异步数据库查询)获取数据。
3
+
4
+ 基于 flask-restful 的 fields 实现,但所有方法都是异步的,以支持异步数据获取。
5
+ """
6
+ import inspect
7
+ from calendar import timegm
8
+ from decimal import Decimal as MyDecimal, ROUND_HALF_EVEN
9
+ from email.utils import formatdate
10
+
11
+ __all__ = [
12
+ "String",
13
+ "FormattedString",
14
+ "DateTime",
15
+ "Float",
16
+ "Integer",
17
+ "Arbitrary",
18
+ "Nested",
19
+ "List",
20
+ "Raw",
21
+ "Boolean",
22
+ "Fixed",
23
+ "Price",
24
+ "MarshallingException",
25
+ ]
26
+
27
+
28
+ class MarshallingException(Exception):
29
+ """
30
+ This is an encapsulating Exception in case of marshalling error.
31
+ """
32
+
33
+ def __init__(self, underlying_exception):
34
+ # just put the contextual representation of the error to hint on what
35
+ # went wrong without exposing internals
36
+ super().__init__(str(underlying_exception))
37
+
38
+
39
+ def is_indexable_but_not_string(obj):
40
+ """检查对象是否可索引但不是字符串"""
41
+ return not isinstance(obj, str) and hasattr(obj, "__iter__")
42
+
43
+
44
+ async def get_value(key, obj, default=None):
45
+ """Helper for pulling a keyed value off various types of objects.
46
+
47
+ 支持异步属性获取,如果属性是协程对象,会自动 await。
48
+ """
49
+ if isinstance(key, int):
50
+ return await _get_value_for_key(key, obj, default)
51
+ elif callable(key):
52
+ value = key(obj)
53
+ if inspect.isawaitable(value):
54
+ value = await value
55
+ # 只检查 None,不检查其他假值(如 0, False, '')
56
+ if value is None:
57
+ return default
58
+ return value
59
+ else:
60
+ return await _get_value_for_keys(key.split('.'), obj, default)
61
+
62
+
63
+ async def _get_value_for_keys(keys, obj, default):
64
+ """递归获取嵌套键的值"""
65
+ if len(keys) == 1:
66
+ return await _get_value_for_key(keys[0], obj, default)
67
+ else:
68
+ intermediate_obj = await _get_value_for_key(keys[0], obj, default)
69
+ if intermediate_obj is None:
70
+ return default
71
+ return await _get_value_for_keys(keys[1:], intermediate_obj, default)
72
+
73
+
74
+ async def _get_value_for_key(key, obj, default):
75
+ """从对象中获取单个键的值,支持异步属性"""
76
+ if is_indexable_but_not_string(obj):
77
+ try:
78
+ value = obj[key]
79
+ if inspect.isawaitable(value):
80
+ value = await value
81
+ # 只检查 None,不检查其他假值(如 0, False, '')
82
+ if value is None:
83
+ return default
84
+ return value
85
+ except (IndexError, TypeError, KeyError):
86
+ pass
87
+
88
+ # 尝试从对象属性获取值
89
+ if hasattr(obj, key):
90
+ value = getattr(obj, key)
91
+ if inspect.isawaitable(value):
92
+ value = await value
93
+ return value
94
+
95
+ # 如果属性不存在,返回 default
96
+ # 如果 default 是协程,也需要 await
97
+ if inspect.isawaitable(default):
98
+ return await default
99
+ return default
100
+
101
+
102
+ async def to_marshallable_type(obj):
103
+ """Helper for converting an object to a dictionary only if it is not
104
+ dictionary already or an indexable object nor a simple type.
105
+
106
+ 支持异步的 __marshallable__ 方法。
107
+ """
108
+ if obj is None:
109
+ return None # make it idempotent for None
110
+
111
+ if hasattr(obj, '__marshallable__'):
112
+ result = obj.__marshallable__()
113
+ # 如果 __marshallable__ 返回的是协程,需要 await
114
+ if inspect.isawaitable(result):
115
+ result = await result
116
+ return result
117
+
118
+ if hasattr(obj, '__getitem__'):
119
+ return obj # it is indexable it is ok
120
+
121
+ return dict(obj.__dict__)
122
+
123
+
124
+ class Raw:
125
+ """Raw provides a base field class from which others should extend. It
126
+ applies no formatting by default, and should only be used in cases where
127
+ data does not need to be formatted before being serialized. Fields should
128
+ throw a :class:`MarshallingException` in case of parsing problem.
129
+
130
+ :param default: The default value for the field, if no value is
131
+ specified.
132
+ :param attribute: If the public facing value differs from the internal
133
+ value, use this to retrieve a different attribute from the response
134
+ than the publicly named value.
135
+ """
136
+
137
+ def __init__(self, default=None, attribute=None):
138
+ self.attribute = attribute
139
+ self.default = default
140
+
141
+ async def format(self, value):
142
+ """Formats a field's value. No-op by default - field classes that
143
+ modify how the value of existing object keys should be presented should
144
+ override this and apply the appropriate formatting.
145
+
146
+ :param value: The value to format
147
+ :exception MarshallingException: In case of formatting problem
148
+
149
+ Ex::
150
+
151
+ class TitleCase(Raw):
152
+ async def format(self, value):
153
+ return str(value).title()
154
+ """
155
+ return value
156
+
157
+ async def output(self, key, obj):
158
+ """Pulls the value for the given key from the object, applies the
159
+ field's formatting and returns the result. If the key is not found
160
+ in the object, returns the default value. Field classes that create
161
+ values which do not require the existence of the key in the object
162
+ should override this and return the desired value.
163
+
164
+ :exception MarshallingException: In case of formatting problem
165
+ """
166
+ value = await get_value(key if self.attribute is None else self.attribute, obj)
167
+
168
+ if value is None:
169
+ return self.default
170
+
171
+ return await self.format(value)
172
+
173
+
174
+ class Nested(Raw):
175
+ """Allows you to nest one set of fields inside another.
176
+ See :ref:`nested-field` for more information
177
+
178
+ :param dict nested: The dictionary to nest
179
+ :param bool allow_null: Whether to return None instead of a dictionary
180
+ with null keys, if a nested dictionary has all-null keys
181
+ :param kwargs: If ``default`` keyword argument is present, a nested
182
+ dictionary will be marshaled as its value if nested dictionary is
183
+ all-null keys (e.g. lets you return an empty JSON object instead of
184
+ null)
185
+ """
186
+
187
+ def __init__(self, nested, allow_null=False, **kwargs):
188
+ self.nested = nested
189
+ self.allow_null = allow_null
190
+ super().__init__(**kwargs)
191
+
192
+ async def output(self, key, obj):
193
+ value = await get_value(key if self.attribute is None else self.attribute, obj)
194
+ if value is None:
195
+ if self.allow_null:
196
+ return None
197
+ elif self.default is not None:
198
+ return self.default
199
+
200
+ # 延迟导入以避免循环导入
201
+ from tomskit.utils.marshal_utils import marshal
202
+ return await marshal(value, self.nested)
203
+
204
+
205
+ class List(Raw):
206
+ """
207
+ Field for marshalling lists of other fields.
208
+
209
+ See :ref:`list-field` for more information.
210
+
211
+ :param cls_or_instance: The field type the list will contain.
212
+ """
213
+
214
+ def __init__(self, cls_or_instance, **kwargs):
215
+ super().__init__(**kwargs)
216
+ error_msg = ("The type of the list elements must be a subclass of "
217
+ "tomskit.utils.fields.Raw")
218
+ if isinstance(cls_or_instance, type):
219
+ if not issubclass(cls_or_instance, Raw):
220
+ raise MarshallingException(error_msg)
221
+ self.container = cls_or_instance()
222
+ else:
223
+ if not isinstance(cls_or_instance, Raw):
224
+ raise MarshallingException(error_msg)
225
+ self.container = cls_or_instance
226
+
227
+ async def format(self, value):
228
+ # Convert all instances in typed list to container type
229
+ if isinstance(value, set):
230
+ value = list(value)
231
+
232
+ results = []
233
+ for idx, val in enumerate(value):
234
+ item = val if (isinstance(val, dict)
235
+ or (self.container.attribute
236
+ and hasattr(val, self.container.attribute))
237
+ ) and not isinstance(self.container, Nested) and type(self.container) is not Raw else value
238
+ output = await self.container.output(idx, item)
239
+ results.append(output)
240
+ return results
241
+
242
+ async def output(self, key, data):
243
+ value = await get_value(key if self.attribute is None else self.attribute, data)
244
+ # we cannot really test for external dict behavior
245
+ if is_indexable_but_not_string(value) and not isinstance(value, dict):
246
+ return await self.format(value)
247
+
248
+ if value is None:
249
+ return self.default
250
+
251
+ # 延迟导入以避免循环导入
252
+ from tomskit.utils.marshal_utils import marshal
253
+ return [await marshal(value, self.container.nested)]
254
+
255
+
256
+ class String(Raw):
257
+ """
258
+ Marshal a value as a string. Uses ``str`` so values will
259
+ be converted to string.
260
+ """
261
+ async def format(self, value):
262
+ try:
263
+ return str(value)
264
+ except ValueError as ve:
265
+ raise MarshallingException(ve)
266
+
267
+
268
+ class Integer(Raw):
269
+ """ Field for outputting an integer value.
270
+
271
+ :param int default: The default value for the field, if no value is
272
+ specified.
273
+ """
274
+ def __init__(self, default=0, **kwargs):
275
+ super().__init__(default=default, **kwargs)
276
+
277
+ async def format(self, value):
278
+ try:
279
+ if value is None:
280
+ return self.default
281
+ return int(value)
282
+ except ValueError as ve:
283
+ raise MarshallingException(ve)
284
+
285
+
286
+ class Boolean(Raw):
287
+ """
288
+ Field for outputting a boolean value.
289
+
290
+ Empty collections such as ``""``, ``{}``, ``[]``, etc. will be converted to
291
+ ``False``.
292
+ """
293
+ async def format(self, value):
294
+ return bool(value)
295
+
296
+
297
+ class FormattedString(Raw):
298
+ """
299
+ FormattedString is used to interpolate other values from
300
+ the response into this field. The syntax for the source string is
301
+ the same as the string :meth:`~str.format` method from the python
302
+ stdlib.
303
+
304
+ Ex::
305
+
306
+ fields = {
307
+ 'name': fields.String(),
308
+ 'greeting': fields.FormattedString("Hello {name}")
309
+ }
310
+ data = {
311
+ 'name': 'Doug',
312
+ }
313
+ result = await marshal(data, fields)
314
+ """
315
+ def __init__(self, src_str):
316
+ """
317
+ :param string src_str: the string to format with the other
318
+ values from the response.
319
+ """
320
+ super().__init__()
321
+ self.src_str = str(src_str)
322
+
323
+ async def output(self, key, obj):
324
+ try:
325
+ data = await to_marshallable_type(obj)
326
+ if data is None:
327
+ data = {}
328
+ return self.src_str.format(**data)
329
+ except (TypeError, IndexError) as error:
330
+ raise MarshallingException(error)
331
+
332
+
333
+ class Float(Raw):
334
+ """
335
+ A double as IEEE-754 double precision.
336
+ ex : 3.141592653589793 3.1415926535897933e-06 3.141592653589793e+24 nan inf
337
+ -inf
338
+ """
339
+
340
+ async def format(self, value):
341
+ try:
342
+ return float(value)
343
+ except ValueError as ve:
344
+ raise MarshallingException(ve)
345
+
346
+
347
+ class Arbitrary(Raw):
348
+ """
349
+ A floating point number with an arbitrary precision
350
+ ex: 634271127864378216478362784632784678324.23432
351
+ """
352
+
353
+ async def format(self, value):
354
+ return str(MyDecimal(value))
355
+
356
+
357
+ class DateTime(Raw):
358
+ """
359
+ Return a formatted datetime string in UTC. Supported formats are RFC 822
360
+ and ISO 8601.
361
+
362
+ See :func:`email.utils.formatdate` for more info on the RFC 822 format.
363
+
364
+ See :meth:`datetime.datetime.isoformat` for more info on the ISO 8601
365
+ format.
366
+
367
+ :param dt_format: ``'rfc822'`` or ``'iso8601'``
368
+ :type dt_format: str
369
+ """
370
+ def __init__(self, dt_format='rfc822', **kwargs):
371
+ super().__init__(**kwargs)
372
+ self.dt_format = dt_format
373
+
374
+ async def format(self, value):
375
+ try:
376
+ if self.dt_format == 'rfc822':
377
+ return _rfc822(value)
378
+ elif self.dt_format == 'iso8601':
379
+ return _iso8601(value)
380
+ else:
381
+ raise MarshallingException(
382
+ f'Unsupported date format {self.dt_format}'
383
+ )
384
+ except AttributeError as ae:
385
+ raise MarshallingException(ae)
386
+
387
+ ZERO = MyDecimal()
388
+
389
+
390
+ class Fixed(Raw):
391
+ """
392
+ A decimal number with a fixed precision.
393
+ """
394
+ def __init__(self, decimals=5, **kwargs):
395
+ super().__init__(**kwargs)
396
+ self.precision = MyDecimal('0.' + '0' * (decimals - 1) + '1')
397
+
398
+ async def format(self, value):
399
+ dvalue = MyDecimal(value)
400
+ if not dvalue.is_normal() and dvalue != ZERO:
401
+ raise MarshallingException('Invalid Fixed precision number.')
402
+ return str(dvalue.quantize(self.precision, rounding=ROUND_HALF_EVEN))
403
+
404
+
405
+ """Alias for :class:`~fields.Fixed`"""
406
+ Price = Fixed
407
+
408
+
409
+ def _rfc822(dt):
410
+ """Turn a datetime object into a formatted date.
411
+
412
+ Example::
413
+
414
+ fields._rfc822(datetime(2011, 1, 1)) => "Sat, 01 Jan 2011 00:00:00 -0000"
415
+
416
+ :param dt: The datetime to transform
417
+ :type dt: datetime
418
+ :return: A RFC 822 formatted date string
419
+ """
420
+ return formatdate(timegm(dt.utctimetuple()))
421
+
422
+
423
+ def _iso8601(dt):
424
+ """Turn a datetime object into an ISO8601 formatted date.
425
+
426
+ Example::
427
+
428
+ fields._iso8601(datetime(2012, 1, 1, 0, 0)) => "2012-01-01T00:00:00"
429
+
430
+ :param dt: The datetime to transform
431
+ :type dt: datetime
432
+ :return: A ISO 8601 formatted date string
433
+ """
434
+ return dt.isoformat()
@@ -0,0 +1,137 @@
1
+ """
2
+ marshal 函数,支持异步数据序列化。
3
+
4
+ 基于 flask-restful 的 marshal 实现,但所有操作都是异步的,以支持从异步数据源(如异步数据库查询)获取数据。
5
+ """
6
+ import inspect
7
+ from collections import OrderedDict
8
+ from collections.abc import AsyncIterable
9
+ from functools import wraps
10
+
11
+ from fastapi.responses import JSONResponse
12
+
13
+ from tomskit.utils.response_utils import unpack
14
+
15
+
16
+ async def marshal(data, fields, envelope=None):
17
+ """接受原始数据(以字典、列表、对象的形式)和输出字段的字典,
18
+ 并根据这些字段过滤数据。支持异步数据获取。
19
+
20
+ :param data: 实际的数据对象(可以是单个对象或对象列表)
21
+ :param fields: 包含输出字段的字典
22
+ :param envelope: 可选的键,用于包裹序列化的响应
23
+ """
24
+ def make(cls):
25
+ if isinstance(cls, type):
26
+ return cls()
27
+ return cls
28
+
29
+ if data is None:
30
+ return OrderedDict() if envelope is None else OrderedDict([(envelope, None)])
31
+
32
+ # 处理列表和元组
33
+ if isinstance(data, (list, tuple)):
34
+ results = []
35
+ for item in data:
36
+ result = await marshal(item, fields)
37
+ results.append(result)
38
+ return OrderedDict([(envelope, results)]) if envelope else results
39
+
40
+ # 处理异步可迭代对象
41
+ elif isinstance(data, AsyncIterable):
42
+ results = []
43
+ async for item in data:
44
+ result = await marshal(item, fields)
45
+ results.append(result)
46
+ return OrderedDict([(envelope, results)]) if envelope else results
47
+
48
+ # 处理单个对象
49
+ items = OrderedDict()
50
+ for k, v in fields.items():
51
+ if isinstance(v, dict):
52
+ value = await marshal(data, v)
53
+ else:
54
+ field_instance = make(v)
55
+ if hasattr(field_instance, 'output'):
56
+ output = field_instance.output(k, data)
57
+ if inspect.isawaitable(output):
58
+ value = await output
59
+ else:
60
+ value = output
61
+ else:
62
+ raise ValueError(f"字段 '{k}' 不是有效的 Field 实例。")
63
+ items[k] = value
64
+
65
+ return OrderedDict([(envelope, items)]) if envelope else items
66
+
67
+
68
+ class marshal_with:
69
+ """A decorator that apply marshalling to the return values of your methods.
70
+
71
+ see :meth:`marshal_utils.marshal`
72
+ """
73
+ def __init__(self, fields, envelope=None):
74
+ """
75
+ :param fields: a dict of whose keys will make up the final
76
+ serialized response output
77
+ :param envelope: optional key that will be used to envelop the serialized
78
+ response
79
+ """
80
+ self.fields = fields
81
+ self.envelope = envelope
82
+
83
+ def __call__(self, f):
84
+ @wraps(f)
85
+ async def wrapper(*args, **kwargs):
86
+ resp = await f(*args, **kwargs)
87
+ if isinstance(resp, tuple):
88
+ data, code, headers = unpack(resp)
89
+ content = await marshal(data, self.fields, self.envelope)
90
+ return JSONResponse(content=content, status_code=code, headers=headers)
91
+ elif isinstance(resp, JSONResponse):
92
+ return resp
93
+ else:
94
+ content = await marshal(resp, self.fields, self.envelope)
95
+ return JSONResponse(content=content)
96
+ return wrapper
97
+
98
+
99
+ class marshal_with_field:
100
+ """
101
+ A decorator that formats the return values of your methods with a single field.
102
+
103
+ >>> from tomskit.utils import marshal_utils, fields
104
+ >>> @marshal_with_field(fields.List(fields.Integer))
105
+ ... async def get():
106
+ ... return ['1', 2, 3.0]
107
+ ...
108
+ >>> await get()
109
+ [1, 2, 3]
110
+
111
+ see :meth:`marshal_utils.marshal_with`
112
+ """
113
+ def __init__(self, field):
114
+ """
115
+ :param field: a single field with which to marshal the output.
116
+ """
117
+ if isinstance(field, type):
118
+ self.field = field()
119
+ else:
120
+ self.field = field
121
+
122
+ def __call__(self, f):
123
+ @wraps(f)
124
+ async def wrapper(*args, **kwargs):
125
+ resp = await f(*args, **kwargs)
126
+
127
+ if isinstance(resp, tuple):
128
+ data, code, headers = unpack(resp)
129
+ content = await self.field.format(data)
130
+ return JSONResponse(content=content, status_code=code, headers=headers)
131
+ elif isinstance(resp, JSONResponse):
132
+ return resp
133
+ else:
134
+ content = await self.field.format(resp)
135
+ return JSONResponse(content=content)
136
+
137
+ return wrapper
@@ -0,0 +1,13 @@
1
+
2
+ def unpack(value):
3
+ """Return a three-tuple of (data, code, headers)"""
4
+ if not isinstance(value, tuple):
5
+ return value, 200, {}
6
+
7
+ if len(value) == 3:
8
+ return value
9
+ elif len(value) == 2:
10
+ data, code = value
11
+ return data, code, {}
12
+ else:
13
+ return value, 200, {}