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 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
- 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):
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(
@@ -125,13 +113,19 @@ def iter_endpoints(
125
113
  paths = [prefix]
126
114
 
127
115
  methods: list[_MethodInfo] = []
128
- if method := get_method(name):
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
- _CBVEndpointParamName = Literal[
165
- "path",
166
- "tags",
167
- "dependencies",
168
- "responses",
169
- "response_model",
170
- "status",
171
- "deprecated",
172
- "methods",
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
- type[EndpointsClassInterface],
182
- dict[_FnName, dict[_CBVEndpointParamName, Any]],
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[EndpointsClassInterfaceT],
185
+ cls: type[_T],
193
186
  path: str,
194
187
  method: RequestMethod,
195
188
  method_name: str,
196
189
  ):
197
- 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 []
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 = list(cls.dependencies) if cls.dependencies else []
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 = cls.responses or []
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", cls.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 = self._state[cls][method_name].get("methods") or [
224
- method
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: Any | None = None,
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: dict[_CBVEndpointParamName, Any] = {
255
- "path": path,
256
- "methods": methods,
257
- "tags": tags,
258
- "dependencies": dependencies,
259
- "responses": responses,
260
- "response_model": response_model,
261
- "status": status,
262
- "deprecated": deprecated,
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]) -> EndpointsClassInterface:
275
- if result := self._initialed_state.get(cls): # type: ignore
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(n_cls):
275
+ for endpoint in iter_endpoints(cls):
283
276
  default_data[endpoint.original_handle_name] = {}
284
277
 
285
- self._state.setdefault(n_cls, default_data)
286
- result = self._build_cls(n_cls)
287
- 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
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 inspect.isfunction(cls.__init__) and hasattr(cls, "build_self"):
299
- 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
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
- def __call__(self, cls: type[_T]) -> type[_T]:
378
- instance = self._initial_state(cls)
379
- 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)
380
411
 
381
- decorator = self.__create_class_dependencies_injector(cls_)
412
+ return cls
382
413
 
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=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
- 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.11
3
+ Version: 0.1.13
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=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=oxQFHWa48kZxxhL7UFeq9mUVqo9Q0QgQK_IllkyOFUk,11937
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.11.dist-info/METADATA,sha256=XqvuAjDSFm1JLYjz-ag0CVE3gmJ8EKs8BbGRk92yb6o,366
34
- u_toolkit-0.1.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
35
- u_toolkit-0.1.11.dist-info/entry_points.txt,sha256=hTfAYCd5vvRiqgnJk2eBsoRIiIVB9pK8WZm3Q3jjKFU,45
36
- u_toolkit-0.1.11.dist-info/RECORD,,
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,,