u-toolkit 0.1.11__py3-none-any.whl → 0.1.13__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 +8 -4
- u_toolkit/fastapi/cbv.py +132 -123
- {u_toolkit-0.1.11.dist-info → u_toolkit-0.1.13.dist-info}/METADATA +1 -1
- {u_toolkit-0.1.11.dist-info → u_toolkit-0.1.13.dist-info}/RECORD +6 -6
- {u_toolkit-0.1.11.dist-info → u_toolkit-0.1.13.dist-info}/WHEEL +0 -0
- {u_toolkit-0.1.11.dist-info → u_toolkit-0.1.13.dist-info}/entry_points.txt +0 -0
u_toolkit/decorators.py
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
+
import inspect
|
1
2
|
from collections.abc import Callable
|
2
3
|
from functools import wraps
|
3
4
|
from typing import Generic, NamedTuple, TypeVar
|
4
5
|
|
5
|
-
from u_toolkit.signature import list_parameters, update_parameters
|
6
|
-
|
7
6
|
|
8
7
|
_FnT = TypeVar("_FnT", bound=Callable)
|
9
8
|
|
@@ -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):
|
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(
|
@@ -125,13 +113,19 @@ def iter_endpoints(
|
|
125
113
|
paths = [prefix]
|
126
114
|
|
127
115
|
methods: list[_MethodInfo] = []
|
128
|
-
|
116
|
+
from_valid_method = False
|
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)):
|
129
125
|
methods.append(method)
|
130
|
-
elif valid_method:
|
131
|
-
methods.extend(valid_method(name, handle) or [])
|
132
126
|
|
133
127
|
for method, pattern in methods:
|
134
|
-
handle_name = pattern.sub("", name)
|
128
|
+
handle_name = name if from_valid_method else pattern.sub("", name)
|
135
129
|
path = handle_name.replace("__", "/")
|
136
130
|
if path:
|
137
131
|
paths.append(path)
|
@@ -161,52 +155,53 @@ def iter_dependencies(cls: type[_T]):
|
|
161
155
|
yield token, dep
|
162
156
|
|
163
157
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
]
|
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]
|
174
170
|
|
175
171
|
|
176
172
|
class CBV:
|
177
173
|
def __init__(self, router: APIRouter | None = None) -> None:
|
178
174
|
self.router = router or APIRouter()
|
179
175
|
|
180
|
-
self._state: dict[
|
181
|
-
|
182
|
-
|
183
|
-
] = {}
|
184
|
-
|
185
|
-
self._initialed_state: dict[
|
186
|
-
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
|
187
179
|
] = {}
|
180
|
+
self._initialed_state: dict[type[_T], _T] = {} # type: ignore
|
188
181
|
|
189
182
|
def create_route(
|
190
183
|
self,
|
191
184
|
*,
|
192
|
-
cls: type[
|
185
|
+
cls: type[_T],
|
193
186
|
path: str,
|
194
187
|
method: RequestMethod,
|
195
188
|
method_name: str,
|
196
189
|
):
|
197
|
-
|
190
|
+
class_routes_info = self._cls_routes_extra[cls][0]
|
191
|
+
|
192
|
+
class_tags = class_routes_info.get("tags") or []
|
198
193
|
endpoint_tags: list[str | Enum] = (
|
199
194
|
self._state[cls][method_name].get("tags") or []
|
200
195
|
)
|
201
196
|
tags = class_tags + endpoint_tags
|
202
197
|
|
203
|
-
class_dependencies =
|
198
|
+
class_dependencies = class_routes_info.get("dependencies") or []
|
204
199
|
endpoint_dependencies = (
|
205
200
|
self._state[cls][method_name].get("dependencies") or []
|
206
201
|
)
|
207
202
|
dependencies = class_dependencies + endpoint_dependencies
|
208
203
|
|
209
|
-
class_responses =
|
204
|
+
class_responses = class_routes_info.get("responses") or []
|
210
205
|
endpoint_responses = (
|
211
206
|
self._state[cls][method_name].get("responses") or []
|
212
207
|
)
|
@@ -215,13 +210,14 @@ class CBV:
|
|
215
210
|
status_code = self._state[cls][method_name].get("status")
|
216
211
|
|
217
212
|
deprecated = self._state[cls][method_name].get(
|
218
|
-
"deprecated",
|
213
|
+
"deprecated", class_routes_info.get("deprecated")
|
219
214
|
)
|
220
215
|
|
221
216
|
response_model = self._state[cls][method_name].get("response_model")
|
222
217
|
|
223
|
-
endpoint_methods =
|
224
|
-
|
218
|
+
endpoint_methods = [
|
219
|
+
i.upper()
|
220
|
+
for i in (self._state[cls][method_name].get("methods") or [method])
|
225
221
|
]
|
226
222
|
|
227
223
|
path = self._state[cls][method_name].get("path") or path
|
@@ -245,22 +241,22 @@ class CBV:
|
|
245
241
|
tags: list[str | Enum] | None = None,
|
246
242
|
dependencies: list | None = None,
|
247
243
|
responses: list[Response] | None = None,
|
248
|
-
response_model:
|
244
|
+
response_model: type[_T] | None = None,
|
249
245
|
status: int | None = None,
|
250
246
|
deprecated: bool | None = None,
|
251
247
|
):
|
252
248
|
state = self._state
|
253
249
|
initial_state = self._initial_state
|
254
|
-
data
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
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
|
+
)
|
264
260
|
|
265
261
|
def handle(params: DefineMethodParams):
|
266
262
|
initial_state(params.method_class)
|
@@ -271,37 +267,27 @@ class CBV:
|
|
271
267
|
|
272
268
|
return define_method_handler(handle)
|
273
269
|
|
274
|
-
def _initial_state(self, cls: type[_T]) ->
|
275
|
-
if result := self._initialed_state.get(cls):
|
276
|
-
return result
|
277
|
-
|
278
|
-
self._update_cls(cls)
|
279
|
-
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)
|
280
273
|
|
281
274
|
default_data = {}
|
282
|
-
for endpoint in iter_endpoints(
|
275
|
+
for endpoint in iter_endpoints(cls):
|
283
276
|
default_data[endpoint.original_handle_name] = {}
|
284
277
|
|
285
|
-
self._state.setdefault(
|
286
|
-
result = self._build_cls(
|
287
|
-
self._initialed_state[
|
278
|
+
self._state.setdefault(cls, default_data)
|
279
|
+
result = self._build_cls(cls)
|
280
|
+
self._initialed_state[cls] = result
|
288
281
|
return result
|
289
282
|
|
290
|
-
def _update_cls(self, cls: type[_T]):
|
291
|
-
for extra_name in EndpointsClassInterface.__annotations__:
|
292
|
-
if not hasattr(cls, extra_name):
|
293
|
-
setattr(cls, extra_name, None)
|
294
|
-
|
295
|
-
# TODO: 加个如果存在属性, 校验属性类型是否是预期的
|
296
|
-
|
297
283
|
def _build_cls(self, cls: type[_T]) -> _T:
|
298
|
-
if
|
299
|
-
|
284
|
+
if cls in self._cls_routes_extra and (
|
285
|
+
build := self._cls_routes_extra[cls][1]
|
286
|
+
):
|
287
|
+
return build(cls) # type: ignore
|
300
288
|
return cls()
|
301
289
|
|
302
|
-
def __create_class_dependencies_injector(
|
303
|
-
self, cls: type[EndpointsClassInterfaceT]
|
304
|
-
):
|
290
|
+
def __create_class_dependencies_injector(self, cls: type[_T]):
|
305
291
|
"""将类的依赖添加到函数实例上
|
306
292
|
|
307
293
|
```python
|
@@ -333,7 +319,7 @@ class CBV:
|
|
333
319
|
|
334
320
|
def new_fn(method_name, kwargs):
|
335
321
|
instance = self._build_cls(cls)
|
336
|
-
dependencies = kwargs.pop(collect_cls_dependencies.__name__)
|
322
|
+
dependencies = kwargs.pop(collect_cls_dependencies.__name__, {})
|
337
323
|
for dep_name, dep_value in dependencies.items():
|
338
324
|
setattr(instance, dep_name, dep_value)
|
339
325
|
return getattr(instance, method_name)
|
@@ -374,38 +360,61 @@ class CBV:
|
|
374
360
|
|
375
361
|
return decorator
|
376
362
|
|
377
|
-
|
378
|
-
|
379
|
-
|
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)
|
380
411
|
|
381
|
-
|
412
|
+
return cls
|
382
413
|
|
383
|
-
|
384
|
-
|
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=cast(type[EndpointsClassInterface], 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
|
-
route(endpoint)
|
414
|
+
if args:
|
415
|
+
return decorator(args[0])
|
410
416
|
|
411
|
-
|
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=1drP4aNDpGxoEGDdnJyWP3lIeMBPPfeWEUrG0J8CpzE,1330
|
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.13.dist-info/METADATA,sha256=CumVE1W_bIzTbM-fYPfOcaBFmEcT1rgWVwXF288NuGQ,366
|
34
|
+
u_toolkit-0.1.13.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
35
|
+
u_toolkit-0.1.13.dist-info/entry_points.txt,sha256=hTfAYCd5vvRiqgnJk2eBsoRIiIVB9pK8WZm3Q3jjKFU,45
|
36
|
+
u_toolkit-0.1.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|