u-toolkit 0.1.12__py3-none-any.whl → 0.1.14__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.
- u_toolkit/decorators.py +39 -5
- u_toolkit/fastapi/cbv.py +130 -124
- {u_toolkit-0.1.12.dist-info → u_toolkit-0.1.14.dist-info}/METADATA +1 -1
- {u_toolkit-0.1.12.dist-info → u_toolkit-0.1.14.dist-info}/RECORD +6 -6
- {u_toolkit-0.1.12.dist-info → u_toolkit-0.1.14.dist-info}/WHEEL +0 -0
- {u_toolkit-0.1.12.dist-info → u_toolkit-0.1.14.dist-info}/entry_points.txt +0 -0
u_toolkit/decorators.py
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
+
import inspect
|
1
2
|
from collections.abc import Callable
|
2
3
|
from functools import wraps
|
3
|
-
from typing import Generic, NamedTuple, TypeVar
|
4
|
-
|
5
|
-
from u_toolkit.signature import list_parameters, update_parameters
|
4
|
+
from typing import Generic, NamedTuple, ParamSpec, TypeVar
|
6
5
|
|
7
6
|
|
8
7
|
_FnT = TypeVar("_FnT", bound=Callable)
|
@@ -27,8 +26,13 @@ class DefineMethodDecorator(Generic[_T, _FnT]):
|
|
27
26
|
self.register_method(DefineMethodParams(owner_class, name, self.fn))
|
28
27
|
|
29
28
|
def __get__(self, instance: _T, owner_class: type[_T]):
|
30
|
-
|
31
|
-
|
29
|
+
if inspect.iscoroutinefunction(self.fn):
|
30
|
+
|
31
|
+
@wraps(self.fn)
|
32
|
+
async def awrapper(*args, **kwargs):
|
33
|
+
return await self.fn(instance, *args, **kwargs)
|
34
|
+
|
35
|
+
return awrapper
|
32
36
|
|
33
37
|
@wraps(self.fn)
|
34
38
|
def wrapper(*args, **kwargs):
|
@@ -45,3 +49,33 @@ def define_method_handler(
|
|
45
49
|
handle(params)
|
46
50
|
|
47
51
|
return Decorator
|
52
|
+
|
53
|
+
|
54
|
+
_P = ParamSpec("_P")
|
55
|
+
|
56
|
+
|
57
|
+
def create_decorator(
|
58
|
+
handle: Callable[_P] | None = None,
|
59
|
+
):
|
60
|
+
def decorator(fn: Callable[_P, _T]):
|
61
|
+
if inspect.iscoroutinefunction(fn):
|
62
|
+
|
63
|
+
@wraps(fn)
|
64
|
+
async def async_wrapper(*args, **kwargs):
|
65
|
+
if inspect.iscoroutinefunction(handle):
|
66
|
+
await handle(*args, **kwargs)
|
67
|
+
elif handle:
|
68
|
+
handle(*args, **kwargs)
|
69
|
+
return await fn(*args, **kwargs)
|
70
|
+
|
71
|
+
return async_wrapper
|
72
|
+
|
73
|
+
@wraps(fn)
|
74
|
+
def wrapper(*args: _P.args, **kwargs: _P.kwargs):
|
75
|
+
if handle:
|
76
|
+
handle(*args, **kwargs)
|
77
|
+
return fn(*args, **kwargs)
|
78
|
+
|
79
|
+
return wrapper
|
80
|
+
|
81
|
+
return decorator
|
u_toolkit/fastapi/cbv.py
CHANGED
@@ -3,7 +3,16 @@ import re
|
|
3
3
|
from collections.abc import Callable
|
4
4
|
from enum import Enum, StrEnum, auto
|
5
5
|
from functools import partial, update_wrapper, wraps
|
6
|
-
from typing import
|
6
|
+
from typing import (
|
7
|
+
Generic,
|
8
|
+
Literal,
|
9
|
+
NamedTuple,
|
10
|
+
NotRequired,
|
11
|
+
TypeVar,
|
12
|
+
TypedDict,
|
13
|
+
cast,
|
14
|
+
overload,
|
15
|
+
)
|
7
16
|
|
8
17
|
from fastapi import APIRouter, Depends
|
9
18
|
from pydantic.alias_generators import to_snake
|
@@ -20,23 +29,7 @@ from u_toolkit.signature import (
|
|
20
29
|
)
|
21
30
|
|
22
31
|
|
23
|
-
class EndpointsClassInterface(Protocol):
|
24
|
-
dependencies: tuple | None = None
|
25
|
-
responses: tuple[Response, ...] | None = None
|
26
|
-
prefix: str | None = None
|
27
|
-
tags: tuple[str | Enum, ...] | None = None
|
28
|
-
deprecated: bool | None = None
|
29
|
-
|
30
|
-
@classmethod
|
31
|
-
def build_self(cls) -> Self:
|
32
|
-
return cls()
|
33
|
-
|
34
|
-
|
35
32
|
_T = TypeVar("_T")
|
36
|
-
EndpointsClassInterfaceT = TypeVar(
|
37
|
-
"EndpointsClassInterfaceT",
|
38
|
-
bound=EndpointsClassInterface,
|
39
|
-
)
|
40
33
|
|
41
34
|
|
42
35
|
LiteralUpperMethod = Literal[
|
@@ -84,15 +77,6 @@ METHOD_PATTERNS = {
|
|
84
77
|
_FnName = str
|
85
78
|
|
86
79
|
|
87
|
-
class EndpointInfo(NamedTuple):
|
88
|
-
handle: Callable
|
89
|
-
original_handle_name: str
|
90
|
-
handle_name: str
|
91
|
-
method: RequestMethod
|
92
|
-
method_pattern: re.Pattern
|
93
|
-
path: str
|
94
|
-
|
95
|
-
|
96
80
|
_MethodInfo = tuple[RequestMethod, re.Pattern[str]]
|
97
81
|
|
98
82
|
|
@@ -103,9 +87,13 @@ def get_method(name: str) -> _MethodInfo | None:
|
|
103
87
|
return None
|
104
88
|
|
105
89
|
|
106
|
-
|
107
|
-
|
108
|
-
|
90
|
+
class EndpointInfo(NamedTuple):
|
91
|
+
handle: Callable
|
92
|
+
original_handle_name: str
|
93
|
+
handle_name: str
|
94
|
+
method: RequestMethod
|
95
|
+
method_pattern: re.Pattern
|
96
|
+
path: str
|
109
97
|
|
110
98
|
|
111
99
|
def iter_endpoints(
|
@@ -126,11 +114,15 @@ def iter_endpoints(
|
|
126
114
|
|
127
115
|
methods: list[_MethodInfo] = []
|
128
116
|
from_valid_method = False
|
129
|
-
|
117
|
+
|
118
|
+
if valid_method:
|
119
|
+
if results := valid_method(name, handle):
|
120
|
+
methods.extend(results)
|
121
|
+
if methods:
|
122
|
+
from_valid_method = True
|
123
|
+
|
124
|
+
if not methods and (method := get_method(name)):
|
130
125
|
methods.append(method)
|
131
|
-
elif valid_method:
|
132
|
-
methods.extend(valid_method(name, handle) or [])
|
133
|
-
from_valid_method = True
|
134
126
|
|
135
127
|
for method, pattern in methods:
|
136
128
|
handle_name = name if from_valid_method else pattern.sub("", name)
|
@@ -163,52 +155,53 @@ def iter_dependencies(cls: type[_T]):
|
|
163
155
|
yield token, dep
|
164
156
|
|
165
157
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
]
|
158
|
+
class CBVRoutesInfo(TypedDict):
|
159
|
+
path: NotRequired[str | None]
|
160
|
+
tags: NotRequired[list[str | Enum] | None]
|
161
|
+
dependencies: NotRequired[list | None]
|
162
|
+
responses: NotRequired[list[Response] | None]
|
163
|
+
deprecated: NotRequired[bool | None]
|
164
|
+
|
165
|
+
|
166
|
+
class CBVRouteInfo(CBVRoutesInfo, Generic[_T]):
|
167
|
+
methods: NotRequired[list[RequestMethod] | None]
|
168
|
+
response_model: NotRequired[type[_T] | None]
|
169
|
+
status: NotRequired[int | None]
|
176
170
|
|
177
171
|
|
178
172
|
class CBV:
|
179
173
|
def __init__(self, router: APIRouter | None = None) -> None:
|
180
174
|
self.router = router or APIRouter()
|
181
175
|
|
182
|
-
self._state: dict[
|
183
|
-
|
184
|
-
|
185
|
-
] = {}
|
186
|
-
|
187
|
-
self._initialed_state: dict[
|
188
|
-
type[EndpointsClassInterface], EndpointsClassInterface
|
176
|
+
self._state: dict[type, dict[_FnName, CBVRouteInfo]] = {}
|
177
|
+
self._cls_routes_extra: dict[
|
178
|
+
type, tuple[CBVRoutesInfo, Callable[[type[_T]], _T] | None] # type: ignore
|
189
179
|
] = {}
|
180
|
+
self._initialed_state: dict[type[_T], _T] = {} # type: ignore
|
190
181
|
|
191
182
|
def create_route(
|
192
183
|
self,
|
193
184
|
*,
|
194
|
-
cls: type[
|
185
|
+
cls: type[_T],
|
195
186
|
path: str,
|
196
187
|
method: RequestMethod,
|
197
188
|
method_name: str,
|
198
189
|
):
|
199
|
-
|
190
|
+
class_routes_info = self._cls_routes_extra[cls][0]
|
191
|
+
|
192
|
+
class_tags = class_routes_info.get("tags") or []
|
200
193
|
endpoint_tags: list[str | Enum] = (
|
201
194
|
self._state[cls][method_name].get("tags") or []
|
202
195
|
)
|
203
196
|
tags = class_tags + endpoint_tags
|
204
197
|
|
205
|
-
class_dependencies =
|
198
|
+
class_dependencies = class_routes_info.get("dependencies") or []
|
206
199
|
endpoint_dependencies = (
|
207
200
|
self._state[cls][method_name].get("dependencies") or []
|
208
201
|
)
|
209
202
|
dependencies = class_dependencies + endpoint_dependencies
|
210
203
|
|
211
|
-
class_responses =
|
204
|
+
class_responses = class_routes_info.get("responses") or []
|
212
205
|
endpoint_responses = (
|
213
206
|
self._state[cls][method_name].get("responses") or []
|
214
207
|
)
|
@@ -217,13 +210,14 @@ class CBV:
|
|
217
210
|
status_code = self._state[cls][method_name].get("status")
|
218
211
|
|
219
212
|
deprecated = self._state[cls][method_name].get(
|
220
|
-
"deprecated",
|
213
|
+
"deprecated", class_routes_info.get("deprecated")
|
221
214
|
)
|
222
215
|
|
223
216
|
response_model = self._state[cls][method_name].get("response_model")
|
224
217
|
|
225
|
-
endpoint_methods =
|
226
|
-
|
218
|
+
endpoint_methods = [
|
219
|
+
i.upper()
|
220
|
+
for i in (self._state[cls][method_name].get("methods") or [method])
|
227
221
|
]
|
228
222
|
|
229
223
|
path = self._state[cls][method_name].get("path") or path
|
@@ -247,22 +241,22 @@ class CBV:
|
|
247
241
|
tags: list[str | Enum] | None = None,
|
248
242
|
dependencies: list | None = None,
|
249
243
|
responses: list[Response] | None = None,
|
250
|
-
response_model:
|
244
|
+
response_model: type[_T] | None = None,
|
251
245
|
status: int | None = None,
|
252
246
|
deprecated: bool | None = None,
|
253
247
|
):
|
254
248
|
state = self._state
|
255
249
|
initial_state = self._initial_state
|
256
|
-
data
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
250
|
+
data = CBVRouteInfo(
|
251
|
+
path=path,
|
252
|
+
methods=methods,
|
253
|
+
tags=tags,
|
254
|
+
dependencies=dependencies,
|
255
|
+
responses=responses,
|
256
|
+
response_model=response_model,
|
257
|
+
status=status,
|
258
|
+
deprecated=deprecated,
|
259
|
+
)
|
266
260
|
|
267
261
|
def handle(params: DefineMethodParams):
|
268
262
|
initial_state(params.method_class)
|
@@ -273,37 +267,27 @@ class CBV:
|
|
273
267
|
|
274
268
|
return define_method_handler(handle)
|
275
269
|
|
276
|
-
def _initial_state(self, cls: type[_T]) ->
|
277
|
-
if result := self._initialed_state.get(cls):
|
278
|
-
return result
|
279
|
-
|
280
|
-
self._update_cls(cls)
|
281
|
-
n_cls = cast(type[EndpointsClassInterface], cls)
|
270
|
+
def _initial_state(self, cls: type[_T]) -> _T:
|
271
|
+
if result := self._initialed_state.get(cls):
|
272
|
+
return cast(_T, result)
|
282
273
|
|
283
274
|
default_data = {}
|
284
|
-
for endpoint in iter_endpoints(
|
275
|
+
for endpoint in iter_endpoints(cls):
|
285
276
|
default_data[endpoint.original_handle_name] = {}
|
286
277
|
|
287
|
-
self._state.setdefault(
|
288
|
-
result = self._build_cls(
|
289
|
-
self._initialed_state[
|
278
|
+
self._state.setdefault(cls, default_data)
|
279
|
+
result = self._build_cls(cls)
|
280
|
+
self._initialed_state[cls] = result
|
290
281
|
return result
|
291
282
|
|
292
|
-
def _update_cls(self, cls: type[_T]):
|
293
|
-
for extra_name in EndpointsClassInterface.__annotations__:
|
294
|
-
if not hasattr(cls, extra_name):
|
295
|
-
setattr(cls, extra_name, None)
|
296
|
-
|
297
|
-
# TODO: 加个如果存在属性, 校验属性类型是否是预期的
|
298
|
-
|
299
283
|
def _build_cls(self, cls: type[_T]) -> _T:
|
300
|
-
if
|
301
|
-
|
284
|
+
if cls in self._cls_routes_extra and (
|
285
|
+
build := self._cls_routes_extra[cls][1]
|
286
|
+
):
|
287
|
+
return build(cls) # type: ignore
|
302
288
|
return cls()
|
303
289
|
|
304
|
-
def __create_class_dependencies_injector(
|
305
|
-
self, cls: type[EndpointsClassInterfaceT]
|
306
|
-
):
|
290
|
+
def __create_class_dependencies_injector(self, cls: type[_T]):
|
307
291
|
"""将类的依赖添加到函数实例上
|
308
292
|
|
309
293
|
```python
|
@@ -335,7 +319,7 @@ class CBV:
|
|
335
319
|
|
336
320
|
def new_fn(method_name, kwargs):
|
337
321
|
instance = self._build_cls(cls)
|
338
|
-
dependencies = kwargs.pop(collect_cls_dependencies.__name__)
|
322
|
+
dependencies = kwargs.pop(collect_cls_dependencies.__name__, {})
|
339
323
|
for dep_name, dep_value in dependencies.items():
|
340
324
|
setattr(instance, dep_name, dep_value)
|
341
325
|
return getattr(instance, method_name)
|
@@ -376,39 +360,61 @@ class CBV:
|
|
376
360
|
|
377
361
|
return decorator
|
378
362
|
|
379
|
-
|
380
|
-
|
381
|
-
|
363
|
+
@overload
|
364
|
+
def __call__(self, cls: type[_T], /) -> type[_T]: ...
|
365
|
+
@overload
|
366
|
+
def __call__(
|
367
|
+
self,
|
368
|
+
*,
|
369
|
+
info: CBVRoutesInfo | None = None,
|
370
|
+
build: Callable[[type[_T]], _T] | None = None,
|
371
|
+
) -> Callable[[type[_T]], type[_T]]: ...
|
372
|
+
|
373
|
+
def __call__(self, *args, **kwargs):
|
374
|
+
info: CBVRoutesInfo = {}
|
375
|
+
build: Callable | None = None
|
376
|
+
|
377
|
+
def decorator(cls: type[_T]) -> type[_T]:
|
378
|
+
instance = self._initial_state(cls)
|
379
|
+
self._cls_routes_extra[cls] = info, build
|
380
|
+
|
381
|
+
decorator = self.__create_class_dependencies_injector(cls)
|
382
|
+
|
383
|
+
def valid_method(
|
384
|
+
name: str, _handle: Callable
|
385
|
+
) -> None | list[_MethodInfo]:
|
386
|
+
if (cls_state := self._state.get(cls)) and (
|
387
|
+
method_state := cls_state.get(name)
|
388
|
+
):
|
389
|
+
methods: list[RequestMethod] = (
|
390
|
+
method_state.get("methods") or []
|
391
|
+
)
|
392
|
+
result: list[_MethodInfo] = []
|
393
|
+
for i in methods:
|
394
|
+
method = Methods(i.lower())
|
395
|
+
result.append((method, METHOD_PATTERNS[method]))
|
396
|
+
return result
|
397
|
+
|
398
|
+
return None
|
399
|
+
|
400
|
+
for endpoint_info in iter_endpoints(cls, valid_method):
|
401
|
+
route = self.create_route(
|
402
|
+
cls=cls,
|
403
|
+
path=endpoint_info.path,
|
404
|
+
method=endpoint_info.method,
|
405
|
+
method_name=endpoint_info.original_handle_name,
|
406
|
+
)
|
407
|
+
method = getattr(instance, endpoint_info.original_handle_name)
|
408
|
+
endpoint = decorator(method)
|
409
|
+
endpoint.__name__ = endpoint_info.handle_name
|
410
|
+
route(endpoint)
|
382
411
|
|
383
|
-
|
412
|
+
return cls
|
384
413
|
|
385
|
-
|
386
|
-
|
387
|
-
) -> None | list[_MethodInfo]:
|
388
|
-
if (cls_state := self._state.get(cls_)) and (
|
389
|
-
method_state := cls_state.get(name)
|
390
|
-
):
|
391
|
-
methods: list[RequestMethod] = (
|
392
|
-
method_state.get("methods") or []
|
393
|
-
)
|
394
|
-
result: list[_MethodInfo] = []
|
395
|
-
for i in methods:
|
396
|
-
method = Methods(i.lower())
|
397
|
-
result.append((method, METHOD_PATTERNS[method]))
|
398
|
-
return result
|
399
|
-
|
400
|
-
return None
|
401
|
-
|
402
|
-
for endpoint_info in iter_endpoints(cls, valid_method):
|
403
|
-
route = self.create_route(
|
404
|
-
cls=cast(type[EndpointsClassInterface], cls),
|
405
|
-
path=endpoint_info.path,
|
406
|
-
method=endpoint_info.method,
|
407
|
-
method_name=endpoint_info.original_handle_name,
|
408
|
-
)
|
409
|
-
method = getattr(instance, endpoint_info.original_handle_name)
|
410
|
-
endpoint = decorator(method)
|
411
|
-
endpoint.__name__ = endpoint_info.handle_name
|
412
|
-
route(endpoint)
|
414
|
+
if args:
|
415
|
+
return decorator(args[0])
|
413
416
|
|
414
|
-
|
417
|
+
info.update(kwargs.get("info") or {})
|
418
|
+
build = kwargs.get("build")
|
419
|
+
|
420
|
+
return decorator
|
@@ -1,7 +1,7 @@
|
|
1
1
|
u_toolkit/__init__.py,sha256=7f_bFg1UERA4gyh3HjCAjOZo2cBUgElpwgX5x-Xk2WA,238
|
2
2
|
u_toolkit/alias_generators.py,sha256=KmPL1ViGJjptONffZUAX4ENvkldrwDylm_Ly5RYE8b4,963
|
3
3
|
u_toolkit/datetime.py,sha256=GOG0xa6yKeqvFXJkImK5Pt7wsPXnHMlNKju8deniGJ4,644
|
4
|
-
u_toolkit/decorators.py,sha256=
|
4
|
+
u_toolkit/decorators.py,sha256=3AQ_NMT7YfKDNFBX07chghktwhTBlIcTjAzjgVMmfNI,2082
|
5
5
|
u_toolkit/enum.py,sha256=2yWmK8V1q0P5S3ltBED-TF2H09BmRbsBuqu3Th3G1NE,862
|
6
6
|
u_toolkit/function.py,sha256=GAE6TIm5jpemUiNUPjzk7pIHrxwSSX7sHl1V5E735dE,252
|
7
7
|
u_toolkit/helpers.py,sha256=K3FCz93K1nT4o7gWKVpMKy3k3BnirTPCSb8F8EFUrWk,112
|
@@ -11,7 +11,7 @@ u_toolkit/object.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
u_toolkit/path.py,sha256=IkyIHcU9hKBCOZfF30FrKf4CfL-MH91fjeYF9EY7eos,128
|
12
12
|
u_toolkit/signature.py,sha256=-Q6n28PYBYYdd2OXBKESeVkL2rYpV6EaY3IVwQmzezQ,2161
|
13
13
|
u_toolkit/fastapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
-
u_toolkit/fastapi/cbv.py,sha256=
|
14
|
+
u_toolkit/fastapi/cbv.py,sha256=q9A4HKguOVBfLeZmhnY9kfiP6FfAViZUo52m-5aKZhU,12219
|
15
15
|
u_toolkit/fastapi/config.py,sha256=kGpokR9XXr1KxMA1GVKYkCdKwqIQAIwOJ-v6sGbqzAQ,267
|
16
16
|
u_toolkit/fastapi/exception.py,sha256=P9apEEQvbBMmffiHX57768gmemMeAy69-nb-1JQZAZE,4624
|
17
17
|
u_toolkit/fastapi/helpers.py,sha256=BCMMLxa1c6BMA_rKq-hCi0iyEjrR3Z5rPMeTvgaVJB0,447
|
@@ -30,7 +30,7 @@ u_toolkit/sqlalchemy/type_vars.py,sha256=m2VeV41CBIK_1QX3w2kgz-n556sILAGZ-Kaz3TD
|
|
30
30
|
u_toolkit/sqlalchemy/orm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
31
|
u_toolkit/sqlalchemy/orm/fields.py,sha256=3zoYil23I6YLtc_59aHDt9w5l1NBTkePT9AfXI3DMiY,593
|
32
32
|
u_toolkit/sqlalchemy/orm/models.py,sha256=V8vf4ps3phAmwxyaFYK7pw8Igz7h097o4QBjKB0gwC8,705
|
33
|
-
u_toolkit-0.1.
|
34
|
-
u_toolkit-0.1.
|
35
|
-
u_toolkit-0.1.
|
36
|
-
u_toolkit-0.1.
|
33
|
+
u_toolkit-0.1.14.dist-info/METADATA,sha256=-mn37kFXx78jKuagV5ZFLhtjjvIV2wx3dfB3_EhWpjU,366
|
34
|
+
u_toolkit-0.1.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
35
|
+
u_toolkit-0.1.14.dist-info/entry_points.txt,sha256=hTfAYCd5vvRiqgnJk2eBsoRIiIVB9pK8WZm3Q3jjKFU,45
|
36
|
+
u_toolkit-0.1.14.dist-info/RECORD,,
|
File without changes
|
File without changes
|