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 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
- parameters = list_parameters(self.fn)[1:]
31
- update_parameters(self.fn, *parameters)
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 Any, Literal, NamedTuple, Protocol, Self, TypeVar, cast
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
- def valid_endpoint(name: str):
107
- if get_method(name) is None:
108
- raise ValueError("Invalid endpoint function.")
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
- if method := get_method(name):
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
- _CBVEndpointParamName = Literal[
167
- "path",
168
- "tags",
169
- "dependencies",
170
- "responses",
171
- "response_model",
172
- "status",
173
- "deprecated",
174
- "methods",
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
- type[EndpointsClassInterface],
184
- dict[_FnName, dict[_CBVEndpointParamName, Any]],
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[EndpointsClassInterfaceT],
185
+ cls: type[_T],
195
186
  path: str,
196
187
  method: RequestMethod,
197
188
  method_name: str,
198
189
  ):
199
- class_tags = list(cls.tags) if cls.tags else []
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 = list(cls.dependencies) if cls.dependencies else []
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 = cls.responses or []
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", cls.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 = self._state[cls][method_name].get("methods") or [
226
- method
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: Any | None = None,
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: dict[_CBVEndpointParamName, Any] = {
257
- "path": path,
258
- "methods": methods,
259
- "tags": tags,
260
- "dependencies": dependencies,
261
- "responses": responses,
262
- "response_model": response_model,
263
- "status": status,
264
- "deprecated": deprecated,
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]) -> EndpointsClassInterface:
277
- if result := self._initialed_state.get(cls): # type: ignore
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(n_cls):
275
+ for endpoint in iter_endpoints(cls):
285
276
  default_data[endpoint.original_handle_name] = {}
286
277
 
287
- self._state.setdefault(n_cls, default_data)
288
- result = self._build_cls(n_cls)
289
- self._initialed_state[n_cls] = result
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 inspect.isfunction(cls.__init__) and hasattr(cls, "build_self"):
301
- return cast(type[EndpointsClassInterface], cls).build_self() # type: ignore
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
- def __call__(self, cls: type[_T]) -> type[_T]:
380
- instance = self._initial_state(cls)
381
- cls_ = cast(type[EndpointsClassInterface], cls)
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
- decorator = self.__create_class_dependencies_injector(cls_)
412
+ return cls
384
413
 
385
- def valid_method(
386
- name: str, _handle: Callable
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
- return cls
417
+ info.update(kwargs.get("info") or {})
418
+ build = kwargs.get("build")
419
+
420
+ return decorator
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: u-toolkit
3
- Version: 0.1.12
3
+ Version: 0.1.14
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: pydantic>=2.11.3
@@ -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=NVQPvl8nByo11IRVtAi_kRRjhGvuHPlEme4dYTGZU0s,1261
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=s8si0Oc7pE_4M8wX-911aEZw5TJ-4-MtTLQHN71htcU,12097
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.12.dist-info/METADATA,sha256=Vtf8jjzNczvmnfEM3I0ZRPW6EPYl9W7x5JbGERtj5Yg,366
34
- u_toolkit-0.1.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
35
- u_toolkit-0.1.12.dist-info/entry_points.txt,sha256=hTfAYCd5vvRiqgnJk2eBsoRIiIVB9pK8WZm3Q3jjKFU,45
36
- u_toolkit-0.1.12.dist-info/RECORD,,
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,,