hypern 0.2.0__cp310-none-win32.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.
- hypern/__init__.py +4 -0
- hypern/application.py +412 -0
- hypern/auth/__init__.py +0 -0
- hypern/auth/authorization.py +2 -0
- hypern/background.py +4 -0
- hypern/caching/__init__.py +0 -0
- hypern/caching/base/__init__.py +8 -0
- hypern/caching/base/backend.py +3 -0
- hypern/caching/base/key_maker.py +8 -0
- hypern/caching/cache_manager.py +56 -0
- hypern/caching/cache_tag.py +10 -0
- hypern/caching/custom_key_maker.py +11 -0
- hypern/caching/redis_backend.py +3 -0
- hypern/cli/__init__.py +0 -0
- hypern/cli/commands.py +0 -0
- hypern/config.py +149 -0
- hypern/datastructures.py +40 -0
- hypern/db/__init__.py +0 -0
- hypern/db/nosql/__init__.py +25 -0
- hypern/db/nosql/addons/__init__.py +4 -0
- hypern/db/nosql/addons/color.py +16 -0
- hypern/db/nosql/addons/daterange.py +30 -0
- hypern/db/nosql/addons/encrypted.py +53 -0
- hypern/db/nosql/addons/password.py +134 -0
- hypern/db/nosql/addons/unicode.py +10 -0
- hypern/db/sql/__init__.py +179 -0
- hypern/db/sql/addons/__init__.py +14 -0
- hypern/db/sql/addons/color.py +16 -0
- hypern/db/sql/addons/daterange.py +23 -0
- hypern/db/sql/addons/datetime.py +22 -0
- hypern/db/sql/addons/encrypted.py +58 -0
- hypern/db/sql/addons/password.py +171 -0
- hypern/db/sql/addons/ts_vector.py +46 -0
- hypern/db/sql/addons/unicode.py +15 -0
- hypern/db/sql/repository.py +290 -0
- hypern/enum.py +13 -0
- hypern/exceptions.py +97 -0
- hypern/hypern.cp310-win32.pyd +0 -0
- hypern/hypern.pyi +266 -0
- hypern/i18n/__init__.py +0 -0
- hypern/logging/__init__.py +3 -0
- hypern/logging/logger.py +82 -0
- hypern/middleware/__init__.py +5 -0
- hypern/middleware/base.py +18 -0
- hypern/middleware/cors.py +38 -0
- hypern/middleware/i18n.py +1 -0
- hypern/middleware/limit.py +176 -0
- hypern/openapi/__init__.py +5 -0
- hypern/openapi/schemas.py +53 -0
- hypern/openapi/swagger.py +3 -0
- hypern/processpool.py +106 -0
- hypern/py.typed +0 -0
- hypern/response/__init__.py +3 -0
- hypern/response/response.py +134 -0
- hypern/routing/__init__.py +4 -0
- hypern/routing/dispatcher.py +67 -0
- hypern/routing/endpoint.py +30 -0
- hypern/routing/parser.py +100 -0
- hypern/routing/route.py +284 -0
- hypern/scheduler.py +5 -0
- hypern/security.py +44 -0
- hypern/worker.py +30 -0
- hypern-0.2.0.dist-info/METADATA +127 -0
- hypern-0.2.0.dist-info/RECORD +66 -0
- hypern-0.2.0.dist-info/WHEEL +4 -0
- hypern-0.2.0.dist-info/licenses/LICENSE +24 -0
hypern/processpool.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import signal
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any, Dict, List
|
|
5
|
+
|
|
6
|
+
from multiprocess import Process
|
|
7
|
+
|
|
8
|
+
from .hypern import FunctionInfo, Router, Server, SocketHeld
|
|
9
|
+
from .logging import logger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run_processes(
|
|
13
|
+
host: str,
|
|
14
|
+
port: int,
|
|
15
|
+
workers: int,
|
|
16
|
+
processes: int,
|
|
17
|
+
max_blocking_threads: int,
|
|
18
|
+
router: Router,
|
|
19
|
+
injectables: Dict[str, Any],
|
|
20
|
+
before_request: List[FunctionInfo],
|
|
21
|
+
after_request: List[FunctionInfo],
|
|
22
|
+
response_headers: Dict[str, str],
|
|
23
|
+
) -> List[Process]:
|
|
24
|
+
socket = SocketHeld(host, port)
|
|
25
|
+
|
|
26
|
+
process_pool = init_processpool(router, socket, workers, processes, max_blocking_threads, injectables, before_request, after_request, response_headers)
|
|
27
|
+
|
|
28
|
+
def terminating_signal_handler(_sig, _frame):
|
|
29
|
+
logger.info("Terminating server!!")
|
|
30
|
+
for process in process_pool:
|
|
31
|
+
process.kill()
|
|
32
|
+
|
|
33
|
+
signal.signal(signal.SIGINT, terminating_signal_handler)
|
|
34
|
+
signal.signal(signal.SIGTERM, terminating_signal_handler)
|
|
35
|
+
|
|
36
|
+
logger.info("Press Ctrl + C to stop \n")
|
|
37
|
+
for process in process_pool:
|
|
38
|
+
process.join()
|
|
39
|
+
|
|
40
|
+
return process_pool
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def init_processpool(
|
|
44
|
+
router: Router,
|
|
45
|
+
socket: SocketHeld,
|
|
46
|
+
workers: int,
|
|
47
|
+
processes: int,
|
|
48
|
+
max_blocking_threads: int,
|
|
49
|
+
injectables: Dict[str, Any],
|
|
50
|
+
before_request: List[FunctionInfo],
|
|
51
|
+
after_request: List[FunctionInfo],
|
|
52
|
+
response_headers: Dict[str, str],
|
|
53
|
+
) -> List[Process]:
|
|
54
|
+
process_pool = []
|
|
55
|
+
|
|
56
|
+
for _ in range(processes):
|
|
57
|
+
copied_socket = socket.try_clone()
|
|
58
|
+
process = Process(
|
|
59
|
+
target=spawn_process,
|
|
60
|
+
args=(router, copied_socket, workers, max_blocking_threads, injectables, before_request, after_request, response_headers),
|
|
61
|
+
)
|
|
62
|
+
process.start()
|
|
63
|
+
process_pool.append(process)
|
|
64
|
+
|
|
65
|
+
return process_pool
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def initialize_event_loop():
|
|
69
|
+
if sys.platform.startswith("win32") or sys.platform.startswith("linux-cross"):
|
|
70
|
+
loop = asyncio.new_event_loop()
|
|
71
|
+
asyncio.set_event_loop(loop)
|
|
72
|
+
return loop
|
|
73
|
+
else:
|
|
74
|
+
import uvloop
|
|
75
|
+
|
|
76
|
+
uvloop.install()
|
|
77
|
+
loop = uvloop.new_event_loop()
|
|
78
|
+
asyncio.set_event_loop(loop)
|
|
79
|
+
return loop
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def spawn_process(
|
|
83
|
+
router: Router,
|
|
84
|
+
socket: SocketHeld,
|
|
85
|
+
workers: int,
|
|
86
|
+
max_blocking_threads: int,
|
|
87
|
+
injectables: Dict[str, Any],
|
|
88
|
+
before_request: List[FunctionInfo],
|
|
89
|
+
after_request: List[FunctionInfo],
|
|
90
|
+
response_headers: Dict[str, str],
|
|
91
|
+
):
|
|
92
|
+
loop = initialize_event_loop()
|
|
93
|
+
|
|
94
|
+
server = Server()
|
|
95
|
+
server.set_router(router=router)
|
|
96
|
+
server.set_injected(injected=injectables)
|
|
97
|
+
server.set_before_hooks(hooks=before_request)
|
|
98
|
+
server.set_after_hooks(hooks=after_request)
|
|
99
|
+
server.set_response_headers(headers=response_headers)
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
server.start(socket, workers, max_blocking_threads)
|
|
103
|
+
loop = asyncio.get_event_loop()
|
|
104
|
+
loop.run_forever()
|
|
105
|
+
except KeyboardInterrupt:
|
|
106
|
+
loop.close()
|
hypern/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
from urllib.parse import quote
|
|
5
|
+
from hypern.hypern import Response as InternalResponse, Header
|
|
6
|
+
import orjson
|
|
7
|
+
|
|
8
|
+
from hypern.background import BackgroundTask, BackgroundTasks
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseResponse:
|
|
12
|
+
media_type = None
|
|
13
|
+
charset = "utf-8"
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
content: typing.Any = None,
|
|
18
|
+
status_code: int = 200,
|
|
19
|
+
headers: typing.Mapping[str, str] | None = None,
|
|
20
|
+
media_type: str | None = None,
|
|
21
|
+
backgrounds: typing.List[BackgroundTask] | None = None,
|
|
22
|
+
) -> None:
|
|
23
|
+
self.status_code = status_code
|
|
24
|
+
if media_type is not None:
|
|
25
|
+
self.media_type = media_type
|
|
26
|
+
self.body = self.render(content)
|
|
27
|
+
self.init_headers(headers)
|
|
28
|
+
self.backgrounds = backgrounds
|
|
29
|
+
|
|
30
|
+
def render(self, content: typing.Any) -> bytes | memoryview:
|
|
31
|
+
if content is None:
|
|
32
|
+
return b""
|
|
33
|
+
if isinstance(content, (bytes, memoryview)):
|
|
34
|
+
return content
|
|
35
|
+
if isinstance(content, str):
|
|
36
|
+
return content.encode(self.charset)
|
|
37
|
+
return orjson.dumps(content) # type: ignore
|
|
38
|
+
|
|
39
|
+
def init_headers(self, headers: typing.Mapping[str, str] | None = None) -> None:
|
|
40
|
+
if headers is None:
|
|
41
|
+
raw_headers: dict = {}
|
|
42
|
+
populate_content_length = True
|
|
43
|
+
populate_content_type = True
|
|
44
|
+
else:
|
|
45
|
+
raw_headers = {k.lower(): v for k, v in headers.items()}
|
|
46
|
+
keys = raw_headers.keys()
|
|
47
|
+
populate_content_length = "content-length" not in keys
|
|
48
|
+
populate_content_type = "content-type" not in keys
|
|
49
|
+
|
|
50
|
+
body = getattr(self, "body", None)
|
|
51
|
+
if body is not None and populate_content_length and not (self.status_code < 200 or self.status_code in (204, 304)):
|
|
52
|
+
content_length = str(len(body))
|
|
53
|
+
raw_headers.setdefault("content-length", content_length)
|
|
54
|
+
|
|
55
|
+
content_type = self.media_type
|
|
56
|
+
if content_type is not None and populate_content_type:
|
|
57
|
+
if content_type.startswith("text/") and "charset=" not in content_type.lower():
|
|
58
|
+
content_type += "; charset=" + self.charset
|
|
59
|
+
raw_headers.setdefault("content-type", content_type)
|
|
60
|
+
|
|
61
|
+
self.raw_headers = raw_headers
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def to_response(cls):
|
|
65
|
+
class ResponseWrapper(cls):
|
|
66
|
+
def __new__(cls, *args, **kwargs):
|
|
67
|
+
instance = super().__new__(cls)
|
|
68
|
+
instance.__init__(*args, **kwargs)
|
|
69
|
+
# Execute background tasks
|
|
70
|
+
task_manager = BackgroundTasks()
|
|
71
|
+
if instance.backgrounds:
|
|
72
|
+
for task in instance.backgrounds:
|
|
73
|
+
task_manager.add_task(task)
|
|
74
|
+
task_manager.execute_all()
|
|
75
|
+
del task_manager
|
|
76
|
+
|
|
77
|
+
headers = Header(instance.raw_headers)
|
|
78
|
+
return InternalResponse(
|
|
79
|
+
status_code=instance.status_code,
|
|
80
|
+
headers=headers,
|
|
81
|
+
description=instance.body,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return ResponseWrapper
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@to_response
|
|
88
|
+
class Response(BaseResponse):
|
|
89
|
+
media_type = None
|
|
90
|
+
charset = "utf-8"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@to_response
|
|
94
|
+
class JSONResponse(BaseResponse):
|
|
95
|
+
media_type = "application/json"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@to_response
|
|
99
|
+
class HTMLResponse(BaseResponse):
|
|
100
|
+
media_type = "text/html"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@to_response
|
|
104
|
+
class PlainTextResponse(BaseResponse):
|
|
105
|
+
media_type = "text/plain"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@to_response
|
|
109
|
+
class RedirectResponse(BaseResponse):
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
url: str,
|
|
113
|
+
status_code: int = 307,
|
|
114
|
+
headers: typing.Mapping[str, str] | None = None,
|
|
115
|
+
backgrounds: typing.List[BackgroundTask] | None = None,
|
|
116
|
+
) -> None:
|
|
117
|
+
super().__init__(content=b"", status_code=status_code, headers=headers, backgrounds=backgrounds)
|
|
118
|
+
self.raw_headers["location"] = quote(str(url), safe=":/%#?=@[]!$&'()*+,;")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@to_response
|
|
122
|
+
class FileResponse(BaseResponse):
|
|
123
|
+
def __init__(
|
|
124
|
+
self,
|
|
125
|
+
content: bytes | memoryview,
|
|
126
|
+
filename: str,
|
|
127
|
+
status_code: int = 200,
|
|
128
|
+
headers: typing.Mapping[str, str] | None = None,
|
|
129
|
+
backgrounds: typing.List[BackgroundTask] | None = None,
|
|
130
|
+
) -> None:
|
|
131
|
+
super().__init__(content=content, status_code=status_code, headers=headers, backgrounds=backgrounds)
|
|
132
|
+
self.raw_headers["content-disposition"] = f'attachment; filename="{filename}"'
|
|
133
|
+
self.raw_headers.setdefault("content-type", "application/octet-stream")
|
|
134
|
+
self.raw_headers.setdefault("content-length", str(len(content)))
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import functools
|
|
6
|
+
import inspect
|
|
7
|
+
import traceback
|
|
8
|
+
import typing
|
|
9
|
+
|
|
10
|
+
import orjson
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
|
|
13
|
+
from hypern.exceptions import BaseException
|
|
14
|
+
from hypern.hypern import Request, Response
|
|
15
|
+
from hypern.response import JSONResponse
|
|
16
|
+
|
|
17
|
+
from .parser import InputHandler
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def is_async_callable(obj: typing.Any) -> bool:
|
|
21
|
+
while isinstance(obj, functools.partial):
|
|
22
|
+
obj = obj.funcz
|
|
23
|
+
return asyncio.iscoroutinefunction(obj) or (callable(obj) and asyncio.iscoroutinefunction(obj.__call__))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def run_in_threadpool(func: typing.Callable, *args, **kwargs):
|
|
27
|
+
if kwargs: # pragma: no cover
|
|
28
|
+
# run_sync doesn't accept 'kwargs', so bind them in here
|
|
29
|
+
func = functools.partial(func, **kwargs)
|
|
30
|
+
return await asyncio.to_thread(func, *args)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def dispatch(handler, request: Request, inject: typing.Dict[str, typing.Any]) -> Response:
|
|
34
|
+
try:
|
|
35
|
+
is_async = is_async_callable(handler)
|
|
36
|
+
signature = inspect.signature(handler)
|
|
37
|
+
input_handler = InputHandler(request)
|
|
38
|
+
_response_type = signature.return_annotation
|
|
39
|
+
_kwargs = await input_handler.get_input_handler(signature, inject)
|
|
40
|
+
|
|
41
|
+
if is_async:
|
|
42
|
+
response = await handler(**_kwargs) # type: ignore
|
|
43
|
+
else:
|
|
44
|
+
response = await run_in_threadpool(handler, **_kwargs)
|
|
45
|
+
if not isinstance(response, Response):
|
|
46
|
+
if isinstance(_response_type, type) and issubclass(_response_type, BaseModel):
|
|
47
|
+
response = _response_type.model_validate(response).model_dump(mode="json") # type: ignore
|
|
48
|
+
response = JSONResponse(
|
|
49
|
+
content=orjson.dumps({"message": response, "error_code": None}),
|
|
50
|
+
status_code=200,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
_res: typing.Dict = {"message": "", "error_code": "UNKNOWN_ERROR"}
|
|
55
|
+
if isinstance(e, BaseException):
|
|
56
|
+
_res["error_code"] = e.error_code
|
|
57
|
+
_res["message"] = e.msg
|
|
58
|
+
_status = e.status
|
|
59
|
+
else:
|
|
60
|
+
traceback.print_exc()
|
|
61
|
+
_res["message"] = str(e)
|
|
62
|
+
_status = 400
|
|
63
|
+
response = JSONResponse(
|
|
64
|
+
content=orjson.dumps(_res),
|
|
65
|
+
status_code=_status,
|
|
66
|
+
)
|
|
67
|
+
return response
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import typing
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
import orjson
|
|
8
|
+
|
|
9
|
+
from hypern.hypern import Request, Response
|
|
10
|
+
from hypern.response import JSONResponse
|
|
11
|
+
|
|
12
|
+
from .dispatcher import dispatch
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HTTPEndpoint:
|
|
16
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
17
|
+
super().__init__(*args, **kwargs)
|
|
18
|
+
|
|
19
|
+
def method_not_allowed(self, request: Request) -> Response:
|
|
20
|
+
return JSONResponse(
|
|
21
|
+
description=orjson.dumps({"message": "Method Not Allowed", "error_code": "METHOD_NOT_ALLOW"}),
|
|
22
|
+
status_code=405,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
async def dispatch(self, request: Request, inject: Dict[str, Any]) -> Response:
|
|
26
|
+
handler_name = "get" if request.method == "HEAD" and not hasattr(self, "head") else request.method.lower()
|
|
27
|
+
handler: typing.Callable[[Request], typing.Any] = getattr( # type: ignore
|
|
28
|
+
self, handler_name, self.method_not_allowed
|
|
29
|
+
)
|
|
30
|
+
return await dispatch(handler, request, inject)
|
hypern/routing/parser.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import inspect
|
|
5
|
+
import typing
|
|
6
|
+
|
|
7
|
+
import orjson
|
|
8
|
+
from pydantic import BaseModel, ValidationError
|
|
9
|
+
from pydash import get
|
|
10
|
+
|
|
11
|
+
from hypern.auth.authorization import Authorization
|
|
12
|
+
from hypern.exceptions import BadRequest
|
|
13
|
+
from hypern.exceptions import ValidationError as HypernValidationError
|
|
14
|
+
from hypern.hypern import Request
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ParamParser:
|
|
18
|
+
def __init__(self, request: Request):
|
|
19
|
+
self.request = request
|
|
20
|
+
|
|
21
|
+
def parse_data_by_name(self, param_name: str) -> dict:
|
|
22
|
+
param_name = param_name.lower()
|
|
23
|
+
data_parsers = {
|
|
24
|
+
"query_params": self._parse_query_params,
|
|
25
|
+
"path_params": self._parse_path_params,
|
|
26
|
+
"form_data": self._parse_form_data,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
parser = data_parsers.get(param_name)
|
|
30
|
+
if not parser:
|
|
31
|
+
raise BadRequest(msg="Backend Error: Invalid parameter type, must be query_params, path_params or form_data.")
|
|
32
|
+
return parser()
|
|
33
|
+
|
|
34
|
+
def _parse_query_params(self) -> dict:
|
|
35
|
+
query_params = self.request.query_params.to_dict()
|
|
36
|
+
return {k: v[0] for k, v in query_params.items()}
|
|
37
|
+
|
|
38
|
+
def _parse_path_params(self) -> dict:
|
|
39
|
+
return lambda: dict(self.request.path_params.items())
|
|
40
|
+
|
|
41
|
+
def _parse_form_data(self) -> dict:
|
|
42
|
+
return self.request.json()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class InputHandler:
|
|
46
|
+
def __init__(self, request):
|
|
47
|
+
self.request = request
|
|
48
|
+
self.param_parser = ParamParser(request)
|
|
49
|
+
|
|
50
|
+
async def parse_pydantic_model(self, param_name: str, model_class: typing.Type[BaseModel]) -> BaseModel:
|
|
51
|
+
try:
|
|
52
|
+
data = self.param_parser.parse_data_by_name(param_name)
|
|
53
|
+
return model_class(**data)
|
|
54
|
+
except ValidationError as e:
|
|
55
|
+
invalid_fields = orjson.loads(e.json())
|
|
56
|
+
raise HypernValidationError(
|
|
57
|
+
msg=orjson.dumps(
|
|
58
|
+
[
|
|
59
|
+
{
|
|
60
|
+
"field": get(item, "loc")[0],
|
|
61
|
+
"msg": get(item, "msg"),
|
|
62
|
+
}
|
|
63
|
+
for item in invalid_fields
|
|
64
|
+
]
|
|
65
|
+
).decode("utf-8"),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
async def handle_special_params(self, param_name: str) -> typing.Any:
|
|
69
|
+
special_params = {
|
|
70
|
+
"request": lambda: self.request,
|
|
71
|
+
}
|
|
72
|
+
return special_params.get(param_name, lambda: None)()
|
|
73
|
+
|
|
74
|
+
async def get_input_handler(self, signature: inspect.Signature, inject: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
|
|
75
|
+
"""
|
|
76
|
+
Parse the request data and return the kwargs for the handler
|
|
77
|
+
"""
|
|
78
|
+
kwargs = {}
|
|
79
|
+
|
|
80
|
+
for param in signature.parameters.values():
|
|
81
|
+
name = param.name
|
|
82
|
+
ptype = param.annotation
|
|
83
|
+
|
|
84
|
+
# Handle Pydantic models
|
|
85
|
+
if isinstance(ptype, type) and issubclass(ptype, BaseModel):
|
|
86
|
+
kwargs[name] = await self.parse_pydantic_model(name, ptype)
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
# Handle Authorization
|
|
90
|
+
if isinstance(ptype, type) and issubclass(ptype, Authorization):
|
|
91
|
+
kwargs[name] = await ptype().validate(self.request)
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
# Handle special parameters
|
|
95
|
+
special_value = await self.handle_special_params(name)
|
|
96
|
+
if special_value is not None:
|
|
97
|
+
kwargs[name] = special_value
|
|
98
|
+
if name in inject:
|
|
99
|
+
kwargs[name] = inject[name]
|
|
100
|
+
return kwargs
|