u-toolkit 0.1.19__py3-none-any.whl → 0.1.20__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/__init__.py +0 -9
- u_toolkit/enum.py +1 -1
- u_toolkit/merge.py +61 -3
- u_toolkit/naming.py +94 -0
- u_toolkit/signature.py +54 -16
- u_toolkit-0.1.20.dist-info/METADATA +5 -0
- u_toolkit-0.1.20.dist-info/RECORD +14 -0
- u_toolkit/alias_generators.py +0 -49
- u_toolkit/fastapi/__init__.py +0 -0
- u_toolkit/fastapi/cbv.py +0 -440
- u_toolkit/fastapi/config.py +0 -9
- u_toolkit/fastapi/exception.py +0 -163
- u_toolkit/fastapi/helpers.py +0 -18
- u_toolkit/fastapi/lifespan.py +0 -67
- u_toolkit/fastapi/pagination.py +0 -79
- u_toolkit/fastapi/responses.py +0 -67
- u_toolkit/logger.py +0 -11
- u_toolkit/pydantic/__init__.py +0 -0
- u_toolkit/pydantic/fields.py +0 -24
- u_toolkit/pydantic/models.py +0 -41
- u_toolkit/pydantic/type_vars.py +0 -6
- u_toolkit/sqlalchemy/__init__.py +0 -0
- u_toolkit/sqlalchemy/fields.py +0 -0
- u_toolkit/sqlalchemy/function.py +0 -12
- u_toolkit/sqlalchemy/orm/__init__.py +0 -0
- u_toolkit/sqlalchemy/orm/fields.py +0 -20
- u_toolkit/sqlalchemy/orm/models.py +0 -23
- u_toolkit/sqlalchemy/table_info.py +0 -17
- u_toolkit/sqlalchemy/type_vars.py +0 -6
- u_toolkit-0.1.19.dist-info/METADATA +0 -11
- u_toolkit-0.1.19.dist-info/RECORD +0 -35
- {u_toolkit-0.1.19.dist-info → u_toolkit-0.1.20.dist-info}/WHEEL +0 -0
- {u_toolkit-0.1.19.dist-info → u_toolkit-0.1.20.dist-info}/entry_points.txt +0 -0
u_toolkit/fastapi/cbv.py
DELETED
@@ -1,440 +0,0 @@
|
|
1
|
-
import inspect
|
2
|
-
import re
|
3
|
-
from collections.abc import Callable
|
4
|
-
from enum import Enum, StrEnum, auto
|
5
|
-
from functools import partial, update_wrapper, wraps
|
6
|
-
from typing import (
|
7
|
-
Generic,
|
8
|
-
Literal,
|
9
|
-
NamedTuple,
|
10
|
-
NotRequired,
|
11
|
-
TypeVar,
|
12
|
-
TypedDict,
|
13
|
-
cast,
|
14
|
-
overload,
|
15
|
-
)
|
16
|
-
|
17
|
-
from fastapi import APIRouter, Depends
|
18
|
-
from pydantic.alias_generators import to_snake
|
19
|
-
|
20
|
-
from u_toolkit.decorators import DefineMethodParams, define_method_handler
|
21
|
-
from u_toolkit.fastapi.helpers import get_depend_from_annotation, is_depend
|
22
|
-
from u_toolkit.fastapi.responses import Response, build_responses
|
23
|
-
from u_toolkit.helpers import is_annotated
|
24
|
-
from u_toolkit.merge import deep_merge_dict
|
25
|
-
from u_toolkit.signature import (
|
26
|
-
list_parameters,
|
27
|
-
update_parameters,
|
28
|
-
with_parameter,
|
29
|
-
)
|
30
|
-
|
31
|
-
|
32
|
-
_T = TypeVar("_T")
|
33
|
-
|
34
|
-
|
35
|
-
LiteralUpperMethod = Literal[
|
36
|
-
"GET",
|
37
|
-
"POST",
|
38
|
-
"PATCH",
|
39
|
-
"PUT",
|
40
|
-
"DELETE",
|
41
|
-
"OPTIONS",
|
42
|
-
"HEAD",
|
43
|
-
"TRACE",
|
44
|
-
]
|
45
|
-
LiteralLowerMethod = Literal[
|
46
|
-
"get",
|
47
|
-
"post",
|
48
|
-
"patch",
|
49
|
-
"put",
|
50
|
-
"delete",
|
51
|
-
"options",
|
52
|
-
"head",
|
53
|
-
"trace",
|
54
|
-
]
|
55
|
-
|
56
|
-
|
57
|
-
class Methods(StrEnum):
|
58
|
-
GET = auto()
|
59
|
-
POST = auto()
|
60
|
-
PATCH = auto()
|
61
|
-
PUT = auto()
|
62
|
-
DELETE = auto()
|
63
|
-
OPTIONS = auto()
|
64
|
-
HEAD = auto()
|
65
|
-
TRACE = auto()
|
66
|
-
|
67
|
-
|
68
|
-
RequestMethod = Methods | LiteralLowerMethod | LiteralUpperMethod
|
69
|
-
|
70
|
-
|
71
|
-
METHOD_PATTERNS = {
|
72
|
-
method: re.compile(f"^({method}_|{method})", re.IGNORECASE)
|
73
|
-
for method in Methods
|
74
|
-
}
|
75
|
-
|
76
|
-
|
77
|
-
_FnName = str
|
78
|
-
|
79
|
-
|
80
|
-
_MethodInfo = tuple[RequestMethod, re.Pattern[str]]
|
81
|
-
|
82
|
-
|
83
|
-
def get_method(name: str) -> _MethodInfo | None:
|
84
|
-
for method, method_pattern in METHOD_PATTERNS.items():
|
85
|
-
if method_pattern.search(name):
|
86
|
-
return method, method_pattern
|
87
|
-
return None
|
88
|
-
|
89
|
-
|
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
|
97
|
-
|
98
|
-
|
99
|
-
def iter_endpoints(
|
100
|
-
cls: type[_T],
|
101
|
-
valid_method: Callable[[str, Callable], list[_MethodInfo] | None]
|
102
|
-
| None = None,
|
103
|
-
):
|
104
|
-
prefix = ""
|
105
|
-
|
106
|
-
if not cls.__name__.startswith("_"):
|
107
|
-
prefix += f"/{to_snake(cls.__name__)}"
|
108
|
-
|
109
|
-
for name, handle in inspect.getmembers(
|
110
|
-
cls,
|
111
|
-
lambda arg: inspect.ismethoddescriptor(arg) or inspect.isfunction(arg),
|
112
|
-
):
|
113
|
-
paths = [prefix]
|
114
|
-
|
115
|
-
methods: list[_MethodInfo] = []
|
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)):
|
125
|
-
methods.append(method)
|
126
|
-
|
127
|
-
for method, pattern in methods:
|
128
|
-
handle_name = name if from_valid_method else pattern.sub("", name)
|
129
|
-
path = handle_name.replace("__", "/")
|
130
|
-
if path:
|
131
|
-
paths.append(path)
|
132
|
-
|
133
|
-
yield EndpointInfo(
|
134
|
-
handle=handle,
|
135
|
-
original_handle_name=name,
|
136
|
-
handle_name=handle_name,
|
137
|
-
path="/".join(paths),
|
138
|
-
method=method,
|
139
|
-
method_pattern=pattern,
|
140
|
-
)
|
141
|
-
|
142
|
-
|
143
|
-
def iter_dependencies(cls: type[_T]):
|
144
|
-
_split = re.compile(r"\s+|:|=")
|
145
|
-
dependencies: dict = dict(inspect.getmembers(cls, is_depend))
|
146
|
-
for name, type_ in inspect.get_annotations(cls).items():
|
147
|
-
if is_annotated(type_):
|
148
|
-
dependency = get_depend_from_annotation(type_)
|
149
|
-
dependencies[name] = dependency
|
150
|
-
|
151
|
-
for line in inspect.getsource(cls).split("\n"):
|
152
|
-
token: str = _split.split(line.strip(), 1)[0]
|
153
|
-
for name, dep in dependencies.items():
|
154
|
-
if name == token:
|
155
|
-
yield token, dep
|
156
|
-
|
157
|
-
|
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
|
-
CBVRoutesInfoT = TypeVar("CBVRoutesInfoT", bound=CBVRoutesInfo)
|
167
|
-
|
168
|
-
|
169
|
-
class CBVRouteInfo(CBVRoutesInfo, Generic[_T]):
|
170
|
-
methods: NotRequired[list[RequestMethod] | None]
|
171
|
-
response_model: NotRequired[type[_T] | None]
|
172
|
-
status: NotRequired[int | None]
|
173
|
-
summary: NotRequired[str | None]
|
174
|
-
description: NotRequired[str | None]
|
175
|
-
name: NotRequired[str | None]
|
176
|
-
|
177
|
-
|
178
|
-
class CBV(Generic[CBVRoutesInfoT]):
|
179
|
-
def __init__(self, router: APIRouter | None = None) -> None:
|
180
|
-
self.router = router or APIRouter()
|
181
|
-
|
182
|
-
self.state: dict[type, dict[_FnName, CBVRouteInfo]] = {}
|
183
|
-
self.routes_extra: dict[
|
184
|
-
type,
|
185
|
-
tuple[
|
186
|
-
CBVRoutesInfoT | None,
|
187
|
-
Callable[[type[_T]], _T] | None, # type: ignore
|
188
|
-
],
|
189
|
-
] = {}
|
190
|
-
self.initialed_state: dict[type[_T], _T] = {} # type: ignore
|
191
|
-
|
192
|
-
def create_route(
|
193
|
-
self,
|
194
|
-
*,
|
195
|
-
cls: type[_T],
|
196
|
-
path: str,
|
197
|
-
method: RequestMethod,
|
198
|
-
method_name: str,
|
199
|
-
):
|
200
|
-
class_routes_info = self.routes_extra[cls][0] or {}
|
201
|
-
|
202
|
-
class_tags = class_routes_info.get("tags") or []
|
203
|
-
endpoint_tags: list[str | Enum] = (
|
204
|
-
self.state[cls][method_name].get("tags") or []
|
205
|
-
)
|
206
|
-
tags = class_tags + endpoint_tags
|
207
|
-
|
208
|
-
class_dependencies = class_routes_info.get("dependencies") or []
|
209
|
-
endpoint_dependencies = (
|
210
|
-
self.state[cls][method_name].get("dependencies") or []
|
211
|
-
)
|
212
|
-
dependencies = class_dependencies + endpoint_dependencies
|
213
|
-
|
214
|
-
class_responses = class_routes_info.get("responses") or []
|
215
|
-
endpoint_responses = (
|
216
|
-
self.state[cls][method_name].get("responses") or []
|
217
|
-
)
|
218
|
-
responses = build_responses(*class_responses, *endpoint_responses)
|
219
|
-
|
220
|
-
status_code = self.state[cls][method_name].get("status")
|
221
|
-
|
222
|
-
deprecated = self.state[cls][method_name].get(
|
223
|
-
"deprecated", class_routes_info.get("deprecated")
|
224
|
-
)
|
225
|
-
|
226
|
-
response_model = self.state[cls][method_name].get("response_model")
|
227
|
-
|
228
|
-
endpoint_methods = [
|
229
|
-
i.upper()
|
230
|
-
for i in (self.state[cls][method_name].get("methods") or [method])
|
231
|
-
]
|
232
|
-
|
233
|
-
path = self.state[cls][method_name].get("path") or path
|
234
|
-
|
235
|
-
summary = self.state[cls][method_name].get("summary")
|
236
|
-
description = self.state[cls][method_name].get("description")
|
237
|
-
name = self.state[cls][method_name].get("name")
|
238
|
-
return self.router.api_route(
|
239
|
-
path,
|
240
|
-
methods=endpoint_methods,
|
241
|
-
tags=tags,
|
242
|
-
dependencies=dependencies,
|
243
|
-
response_model=response_model,
|
244
|
-
responses=responses,
|
245
|
-
status_code=status_code,
|
246
|
-
deprecated=deprecated,
|
247
|
-
summary=summary,
|
248
|
-
description=description,
|
249
|
-
name=name,
|
250
|
-
)
|
251
|
-
|
252
|
-
def info( # noqa: PLR0913
|
253
|
-
self,
|
254
|
-
*,
|
255
|
-
path: str | None = None,
|
256
|
-
methods: list[RequestMethod] | None = None,
|
257
|
-
tags: list[str | Enum] | None = None,
|
258
|
-
dependencies: list | None = None,
|
259
|
-
responses: list[Response] | None = None,
|
260
|
-
response_model: type[_T] | None = None,
|
261
|
-
summary: str | None = None,
|
262
|
-
description: str | None = None,
|
263
|
-
name: str | None = None,
|
264
|
-
status: int | None = None,
|
265
|
-
deprecated: bool | None = None,
|
266
|
-
):
|
267
|
-
state = self.state
|
268
|
-
initial_state = self._initial_state
|
269
|
-
data = CBVRouteInfo(
|
270
|
-
path=path,
|
271
|
-
methods=methods,
|
272
|
-
tags=tags,
|
273
|
-
dependencies=dependencies,
|
274
|
-
responses=responses,
|
275
|
-
response_model=response_model,
|
276
|
-
status=status,
|
277
|
-
deprecated=deprecated,
|
278
|
-
summary=summary,
|
279
|
-
description=description,
|
280
|
-
name=name,
|
281
|
-
)
|
282
|
-
|
283
|
-
def handle(params: DefineMethodParams):
|
284
|
-
initial_state(params.method_class)
|
285
|
-
deep_merge_dict(
|
286
|
-
state,
|
287
|
-
{params.method_class: {params.method_name: data}},
|
288
|
-
)
|
289
|
-
|
290
|
-
return define_method_handler(handle)
|
291
|
-
|
292
|
-
def _initial_state(self, cls: type[_T]) -> _T:
|
293
|
-
if result := self.initialed_state.get(cls):
|
294
|
-
return cast(_T, result)
|
295
|
-
|
296
|
-
default_data = {}
|
297
|
-
for endpoint in iter_endpoints(cls):
|
298
|
-
default_data[endpoint.original_handle_name] = {}
|
299
|
-
|
300
|
-
self.state.setdefault(cls, default_data)
|
301
|
-
result = self._build_cls(cls)
|
302
|
-
self.initialed_state[cls] = result
|
303
|
-
return result
|
304
|
-
|
305
|
-
def _build_cls(self, cls: type[_T]) -> _T:
|
306
|
-
if cls in self.routes_extra and (build := self.routes_extra[cls][1]):
|
307
|
-
return build(cls) # type: ignore
|
308
|
-
return cls()
|
309
|
-
|
310
|
-
def __create_class_dependencies_injector(self, cls: type[_T]):
|
311
|
-
"""将类的依赖添加到函数实例上
|
312
|
-
|
313
|
-
```python
|
314
|
-
@cbv
|
315
|
-
class A:
|
316
|
-
a = Depends(lambda: id(object()))
|
317
|
-
|
318
|
-
def get(self):
|
319
|
-
# 使得每次 self.a 可以访问到当前请求的依赖
|
320
|
-
print(self.a)
|
321
|
-
```
|
322
|
-
"""
|
323
|
-
|
324
|
-
def collect_cls_dependencies(**kwargs):
|
325
|
-
return kwargs
|
326
|
-
|
327
|
-
parameters = [
|
328
|
-
inspect.Parameter(
|
329
|
-
name=name,
|
330
|
-
kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
331
|
-
default=dep,
|
332
|
-
)
|
333
|
-
for name, dep in iter_dependencies(cls)
|
334
|
-
]
|
335
|
-
|
336
|
-
has_cls_deps = bool(parameters)
|
337
|
-
if has_cls_deps:
|
338
|
-
update_parameters(collect_cls_dependencies, *parameters)
|
339
|
-
|
340
|
-
def new_fn(method_name, kwargs):
|
341
|
-
instance = self._build_cls(cls)
|
342
|
-
dependencies = kwargs.pop(collect_cls_dependencies.__name__, {})
|
343
|
-
for dep_name, dep_value in dependencies.items():
|
344
|
-
setattr(instance, dep_name, dep_value)
|
345
|
-
return getattr(instance, method_name)
|
346
|
-
|
347
|
-
def decorator(method: Callable):
|
348
|
-
method_name = method.__name__
|
349
|
-
|
350
|
-
cls_fn = getattr(cls, method_name)
|
351
|
-
sign_cls_fn = partial(cls_fn)
|
352
|
-
update_wrapper(sign_cls_fn, cls_fn)
|
353
|
-
|
354
|
-
if has_cls_deps:
|
355
|
-
parameters, *_ = with_parameter(
|
356
|
-
sign_cls_fn,
|
357
|
-
name=collect_cls_dependencies.__name__,
|
358
|
-
default=Depends(collect_cls_dependencies),
|
359
|
-
)
|
360
|
-
else:
|
361
|
-
parameters = list_parameters(sign_cls_fn)
|
362
|
-
|
363
|
-
update_parameters(sign_cls_fn, *(parameters[1:]))
|
364
|
-
|
365
|
-
if inspect.iscoroutinefunction(method):
|
366
|
-
|
367
|
-
@wraps(sign_cls_fn)
|
368
|
-
async def awrapper(*args, **kwargs):
|
369
|
-
fn = new_fn(method_name, kwargs)
|
370
|
-
return await fn(*args, **kwargs)
|
371
|
-
|
372
|
-
return awrapper
|
373
|
-
|
374
|
-
@wraps(sign_cls_fn)
|
375
|
-
def wrapper(*args, **kwargs):
|
376
|
-
fn = new_fn(method_name, kwargs)
|
377
|
-
return fn(*args, **kwargs)
|
378
|
-
|
379
|
-
return wrapper
|
380
|
-
|
381
|
-
return decorator
|
382
|
-
|
383
|
-
@overload
|
384
|
-
def __call__(self, cls: type[_T], /) -> type[_T]: ...
|
385
|
-
@overload
|
386
|
-
def __call__(
|
387
|
-
self,
|
388
|
-
*,
|
389
|
-
info: CBVRoutesInfoT | None = None,
|
390
|
-
build: Callable[[type[_T]], _T] | None = None,
|
391
|
-
) -> Callable[[type[_T]], type[_T]]: ...
|
392
|
-
|
393
|
-
def __call__(self, *args, **kwargs):
|
394
|
-
info = None
|
395
|
-
build: Callable | None = None
|
396
|
-
|
397
|
-
def decorator(cls: type[_T]) -> type[_T]:
|
398
|
-
instance = self._initial_state(cls)
|
399
|
-
self.routes_extra[cls] = info, build
|
400
|
-
|
401
|
-
decorator = self.__create_class_dependencies_injector(cls)
|
402
|
-
|
403
|
-
def valid_method(
|
404
|
-
name: str, _handle: Callable
|
405
|
-
) -> None | list[_MethodInfo]:
|
406
|
-
if (cls_state := self.state.get(cls)) and (
|
407
|
-
method_state := cls_state.get(name)
|
408
|
-
):
|
409
|
-
methods: list[RequestMethod] = (
|
410
|
-
method_state.get("methods") or []
|
411
|
-
)
|
412
|
-
result: list[_MethodInfo] = []
|
413
|
-
for i in methods:
|
414
|
-
method = Methods(i.lower())
|
415
|
-
result.append((method, METHOD_PATTERNS[method]))
|
416
|
-
return result
|
417
|
-
|
418
|
-
return None
|
419
|
-
|
420
|
-
for endpoint_info in iter_endpoints(cls, valid_method):
|
421
|
-
route = self.create_route(
|
422
|
-
cls=cls,
|
423
|
-
path=endpoint_info.path,
|
424
|
-
method=endpoint_info.method,
|
425
|
-
method_name=endpoint_info.original_handle_name,
|
426
|
-
)
|
427
|
-
method = getattr(instance, endpoint_info.original_handle_name)
|
428
|
-
endpoint = decorator(method)
|
429
|
-
endpoint.__name__ = endpoint_info.handle_name
|
430
|
-
route(endpoint)
|
431
|
-
|
432
|
-
return cls
|
433
|
-
|
434
|
-
if args:
|
435
|
-
return decorator(args[0])
|
436
|
-
|
437
|
-
info = kwargs.get("info") or None
|
438
|
-
build = kwargs.get("build")
|
439
|
-
|
440
|
-
return decorator
|
u_toolkit/fastapi/config.py
DELETED
u_toolkit/fastapi/exception.py
DELETED
@@ -1,163 +0,0 @@
|
|
1
|
-
import inspect
|
2
|
-
from collections.abc import Callable, Sequence
|
3
|
-
from typing import Any, Generic, Literal, Protocol, TypeVar, cast
|
4
|
-
|
5
|
-
from fastapi import Response, status
|
6
|
-
from fastapi.responses import JSONResponse, ORJSONResponse
|
7
|
-
from fastapi.utils import is_body_allowed_for_status_code
|
8
|
-
from pydantic import BaseModel, create_model
|
9
|
-
|
10
|
-
from u_toolkit.pydantic.type_vars import BaseModelT
|
11
|
-
|
12
|
-
|
13
|
-
try:
|
14
|
-
import orjson # type: ignore
|
15
|
-
except ImportError: # pragma: nocover
|
16
|
-
orjson = None # type: ignore
|
17
|
-
|
18
|
-
|
19
|
-
class WrapperError(BaseModel, Generic[BaseModelT]):
|
20
|
-
@classmethod
|
21
|
-
def create(
|
22
|
-
cls: type["WrapperError[BaseModelT]"],
|
23
|
-
model: BaseModelT,
|
24
|
-
) -> "WrapperError[BaseModelT]":
|
25
|
-
raise NotImplementedError
|
26
|
-
|
27
|
-
|
28
|
-
WrapperErrorT = TypeVar("WrapperErrorT", bound=WrapperError)
|
29
|
-
|
30
|
-
|
31
|
-
class EndpointError(WrapperError[BaseModelT], Generic[BaseModelT]):
|
32
|
-
error: BaseModelT
|
33
|
-
|
34
|
-
@classmethod
|
35
|
-
def create(cls, model: BaseModelT):
|
36
|
-
return cls(error=model)
|
37
|
-
|
38
|
-
|
39
|
-
class HTTPErrorInterface(Protocol):
|
40
|
-
status: int
|
41
|
-
|
42
|
-
@classmethod
|
43
|
-
def response_class(cls) -> type[BaseModel]: ...
|
44
|
-
|
45
|
-
|
46
|
-
class NamedHTTPError(Exception, Generic[WrapperErrorT, BaseModelT]):
|
47
|
-
status: int = status.HTTP_400_BAD_REQUEST
|
48
|
-
code: str | None = None
|
49
|
-
targets: Sequence[Any] | None = None
|
50
|
-
target_transform: Callable[[Any], Any] | None = None
|
51
|
-
message: str | None = None
|
52
|
-
wrapper: (
|
53
|
-
type[WrapperError[BaseModelT]]
|
54
|
-
| tuple[WrapperErrorT, Callable[[BaseModelT], WrapperErrorT]]
|
55
|
-
| None
|
56
|
-
) = None
|
57
|
-
|
58
|
-
@classmethod
|
59
|
-
def error_name(cls):
|
60
|
-
return cls.__name__.removesuffix("Error")
|
61
|
-
|
62
|
-
@classmethod
|
63
|
-
def model_class(cls) -> type[BaseModelT]:
|
64
|
-
type_ = cls.error_name()
|
65
|
-
error_code = cls.code or type_
|
66
|
-
kwargs = {
|
67
|
-
"code": (Literal[error_code], ...),
|
68
|
-
"message": (Literal[cls.message] if cls.message else str, ...),
|
69
|
-
}
|
70
|
-
if cls.targets:
|
71
|
-
kwargs["target"] = (Literal[*cls.transformed_targets()], ...)
|
72
|
-
|
73
|
-
return cast(type[BaseModelT], create_model(f"{type_}Model", **kwargs))
|
74
|
-
|
75
|
-
@classmethod
|
76
|
-
def error_code(cls):
|
77
|
-
return cls.code or cls.error_name()
|
78
|
-
|
79
|
-
@classmethod
|
80
|
-
def transformed_targets(cls) -> list[str]:
|
81
|
-
if cls.targets:
|
82
|
-
result = []
|
83
|
-
for i in cls.targets:
|
84
|
-
if cls.target_transform:
|
85
|
-
result.append(cls.target_transform(i))
|
86
|
-
else:
|
87
|
-
result.append(i)
|
88
|
-
return result
|
89
|
-
return []
|
90
|
-
|
91
|
-
def __init__(
|
92
|
-
self,
|
93
|
-
*,
|
94
|
-
message: str | None = None,
|
95
|
-
target: str | None = None,
|
96
|
-
headers: dict[str, str] | None = None,
|
97
|
-
) -> None:
|
98
|
-
kwargs: dict[str, Any] = {
|
99
|
-
"code": self.error_code(),
|
100
|
-
"message": message or self.message or "operation failed",
|
101
|
-
}
|
102
|
-
|
103
|
-
if target:
|
104
|
-
if self.target_transform:
|
105
|
-
target = self.target_transform(target)
|
106
|
-
kwargs["target"] = target
|
107
|
-
kwargs["message"] = kwargs["message"].format(target=target)
|
108
|
-
|
109
|
-
self.model = self.model_class()(**kwargs)
|
110
|
-
create: Callable[[BaseModelT], BaseModel] | None = None
|
111
|
-
if inspect.isclass(self.wrapper):
|
112
|
-
create = self.wrapper.create
|
113
|
-
elif isinstance(self.wrapper, tuple):
|
114
|
-
create = self.wrapper[1]
|
115
|
-
self.data: BaseModel = (
|
116
|
-
create(self.model) if create is not None else self.model
|
117
|
-
)
|
118
|
-
|
119
|
-
self.headers = headers
|
120
|
-
|
121
|
-
def __str__(self) -> str:
|
122
|
-
return f"{self.status}: {self.data.error.code}" # type: ignore
|
123
|
-
|
124
|
-
def __repr__(self) -> str:
|
125
|
-
return f"{self.model_class: str(self.error)}"
|
126
|
-
|
127
|
-
@classmethod
|
128
|
-
def response_class(cls):
|
129
|
-
model = cls.model_class()
|
130
|
-
|
131
|
-
if cls.wrapper:
|
132
|
-
wrapper: Any
|
133
|
-
if inspect.isclass(cls.wrapper):
|
134
|
-
wrapper = cls.wrapper
|
135
|
-
else:
|
136
|
-
wrapper = cls.wrapper[0]
|
137
|
-
return wrapper[model]
|
138
|
-
|
139
|
-
return model
|
140
|
-
|
141
|
-
@classmethod
|
142
|
-
def response_schema(cls):
|
143
|
-
return {cls.status: {"model": cls.response_class()}}
|
144
|
-
|
145
|
-
|
146
|
-
def named_http_error_handler(_, exc: NamedHTTPError):
|
147
|
-
headers = exc.headers
|
148
|
-
|
149
|
-
if not is_body_allowed_for_status_code(exc.status):
|
150
|
-
return Response(status_code=exc.status, headers=headers)
|
151
|
-
|
152
|
-
if orjson:
|
153
|
-
return ORJSONResponse(
|
154
|
-
exc.data.model_dump(exclude_none=True),
|
155
|
-
status_code=exc.status,
|
156
|
-
headers=headers,
|
157
|
-
)
|
158
|
-
|
159
|
-
return JSONResponse(
|
160
|
-
exc.data.model_dump(exclude_none=True),
|
161
|
-
status_code=exc.status,
|
162
|
-
headers=headers,
|
163
|
-
)
|
u_toolkit/fastapi/helpers.py
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
from typing import Annotated, Any, get_args
|
2
|
-
|
3
|
-
from fastapi.params import Depends
|
4
|
-
|
5
|
-
|
6
|
-
def is_depend(value: Any):
|
7
|
-
return isinstance(value, Depends)
|
8
|
-
|
9
|
-
|
10
|
-
def get_depend_from_annotation(annotation: Annotated):
|
11
|
-
args = list(get_args(annotation))
|
12
|
-
# 因为 FastAPI 好像也是取最后的依赖运行的, 所以这里也将参数反转
|
13
|
-
args.reverse()
|
14
|
-
for arg in args:
|
15
|
-
if is_depend(arg):
|
16
|
-
return arg
|
17
|
-
|
18
|
-
raise ValueError
|
u_toolkit/fastapi/lifespan.py
DELETED
@@ -1,67 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
from collections.abc import Awaitable, Callable, Coroutine
|
3
|
-
from contextlib import (
|
4
|
-
AbstractAsyncContextManager,
|
5
|
-
AbstractContextManager,
|
6
|
-
AsyncExitStack,
|
7
|
-
asynccontextmanager,
|
8
|
-
)
|
9
|
-
from typing import TypeVar
|
10
|
-
|
11
|
-
from fastapi import FastAPI
|
12
|
-
|
13
|
-
|
14
|
-
Hook = Callable[
|
15
|
-
[FastAPI],
|
16
|
-
Awaitable[None] | Coroutine[None, None, None] | None,
|
17
|
-
]
|
18
|
-
|
19
|
-
HookT = TypeVar("HookT", bound=Hook)
|
20
|
-
|
21
|
-
ContextManager = Callable[
|
22
|
-
[FastAPI],
|
23
|
-
AbstractContextManager | AbstractAsyncContextManager,
|
24
|
-
]
|
25
|
-
|
26
|
-
ContextManagerT = TypeVar("ContextManagerT", bound=ContextManager)
|
27
|
-
|
28
|
-
|
29
|
-
class Lifespan:
|
30
|
-
def __init__(self) -> None:
|
31
|
-
self._startup_hooks: list[Hook] = []
|
32
|
-
self._shutdown_hooks: list[Hook] = []
|
33
|
-
self._context_managers: list[ContextManager] = []
|
34
|
-
|
35
|
-
def on_startup(self, fn: HookT) -> HookT:
|
36
|
-
self._startup_hooks.append(fn)
|
37
|
-
return fn
|
38
|
-
|
39
|
-
def on_shutdown(self, fn: HookT) -> HookT:
|
40
|
-
self._shutdown_hooks.append(fn)
|
41
|
-
return fn
|
42
|
-
|
43
|
-
def on_context(self, fn: ContextManagerT) -> ContextManagerT:
|
44
|
-
self._context_managers.append(fn)
|
45
|
-
return fn
|
46
|
-
|
47
|
-
@asynccontextmanager
|
48
|
-
async def __call__(self, _app: FastAPI):
|
49
|
-
for hook in self._startup_hooks:
|
50
|
-
ret = hook(_app)
|
51
|
-
if asyncio.iscoroutine(ret):
|
52
|
-
await ret
|
53
|
-
|
54
|
-
async with AsyncExitStack() as stack:
|
55
|
-
for ctx in self._context_managers:
|
56
|
-
i = ctx(_app)
|
57
|
-
if isinstance(i, AbstractContextManager):
|
58
|
-
stack.enter_context(i)
|
59
|
-
elif isinstance(i, AbstractAsyncContextManager):
|
60
|
-
await stack.enter_async_context(i)
|
61
|
-
|
62
|
-
yield
|
63
|
-
|
64
|
-
for hook in self._shutdown_hooks:
|
65
|
-
ret = hook(_app)
|
66
|
-
if asyncio.iscoroutine(ret):
|
67
|
-
await ret
|