fastapi 0.128.0__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.
- fastapi/__init__.py +25 -0
- fastapi/__main__.py +3 -0
- fastapi/_compat/__init__.py +41 -0
- fastapi/_compat/shared.py +206 -0
- fastapi/_compat/v2.py +568 -0
- fastapi/applications.py +4669 -0
- fastapi/background.py +60 -0
- fastapi/cli.py +13 -0
- fastapi/concurrency.py +41 -0
- fastapi/datastructures.py +183 -0
- fastapi/dependencies/__init__.py +0 -0
- fastapi/dependencies/models.py +193 -0
- fastapi/dependencies/utils.py +1021 -0
- fastapi/encoders.py +346 -0
- fastapi/exception_handlers.py +34 -0
- fastapi/exceptions.py +246 -0
- fastapi/logger.py +3 -0
- fastapi/middleware/__init__.py +1 -0
- fastapi/middleware/asyncexitstack.py +18 -0
- fastapi/middleware/cors.py +1 -0
- fastapi/middleware/gzip.py +1 -0
- fastapi/middleware/httpsredirect.py +3 -0
- fastapi/middleware/trustedhost.py +3 -0
- fastapi/middleware/wsgi.py +1 -0
- fastapi/openapi/__init__.py +0 -0
- fastapi/openapi/constants.py +3 -0
- fastapi/openapi/docs.py +344 -0
- fastapi/openapi/models.py +438 -0
- fastapi/openapi/utils.py +567 -0
- fastapi/param_functions.py +2369 -0
- fastapi/params.py +755 -0
- fastapi/py.typed +0 -0
- fastapi/requests.py +2 -0
- fastapi/responses.py +48 -0
- fastapi/routing.py +4508 -0
- fastapi/security/__init__.py +15 -0
- fastapi/security/api_key.py +318 -0
- fastapi/security/base.py +6 -0
- fastapi/security/http.py +423 -0
- fastapi/security/oauth2.py +663 -0
- fastapi/security/open_id_connect_url.py +94 -0
- fastapi/security/utils.py +10 -0
- fastapi/staticfiles.py +1 -0
- fastapi/templating.py +1 -0
- fastapi/testclient.py +1 -0
- fastapi/types.py +11 -0
- fastapi/utils.py +164 -0
- fastapi/websockets.py +3 -0
- fastapi-0.128.0.dist-info/METADATA +645 -0
- fastapi-0.128.0.dist-info/RECORD +53 -0
- fastapi-0.128.0.dist-info/WHEEL +4 -0
- fastapi-0.128.0.dist-info/entry_points.txt +5 -0
- fastapi-0.128.0.dist-info/licenses/LICENSE +21 -0
fastapi/background.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import Annotated, Any, Callable
|
|
2
|
+
|
|
3
|
+
from annotated_doc import Doc
|
|
4
|
+
from starlette.background import BackgroundTasks as StarletteBackgroundTasks
|
|
5
|
+
from typing_extensions import ParamSpec
|
|
6
|
+
|
|
7
|
+
P = ParamSpec("P")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BackgroundTasks(StarletteBackgroundTasks):
|
|
11
|
+
"""
|
|
12
|
+
A collection of background tasks that will be called after a response has been
|
|
13
|
+
sent to the client.
|
|
14
|
+
|
|
15
|
+
Read more about it in the
|
|
16
|
+
[FastAPI docs for Background Tasks](https://fastapi.tiangolo.com/tutorial/background-tasks/).
|
|
17
|
+
|
|
18
|
+
## Example
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from fastapi import BackgroundTasks, FastAPI
|
|
22
|
+
|
|
23
|
+
app = FastAPI()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def write_notification(email: str, message=""):
|
|
27
|
+
with open("log.txt", mode="w") as email_file:
|
|
28
|
+
content = f"notification for {email}: {message}"
|
|
29
|
+
email_file.write(content)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.post("/send-notification/{email}")
|
|
33
|
+
async def send_notification(email: str, background_tasks: BackgroundTasks):
|
|
34
|
+
background_tasks.add_task(write_notification, email, message="some notification")
|
|
35
|
+
return {"message": "Notification sent in the background"}
|
|
36
|
+
```
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def add_task(
|
|
40
|
+
self,
|
|
41
|
+
func: Annotated[
|
|
42
|
+
Callable[P, Any],
|
|
43
|
+
Doc(
|
|
44
|
+
"""
|
|
45
|
+
The function to call after the response is sent.
|
|
46
|
+
|
|
47
|
+
It can be a regular `def` function or an `async def` function.
|
|
48
|
+
"""
|
|
49
|
+
),
|
|
50
|
+
],
|
|
51
|
+
*args: P.args,
|
|
52
|
+
**kwargs: P.kwargs,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Add a function to be called in the background after the response is sent.
|
|
56
|
+
|
|
57
|
+
Read more about it in the
|
|
58
|
+
[FastAPI docs for Background Tasks](https://fastapi.tiangolo.com/tutorial/background-tasks/).
|
|
59
|
+
"""
|
|
60
|
+
return super().add_task(func, *args, **kwargs)
|
fastapi/cli.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from fastapi_cli.cli import main as cli_main
|
|
3
|
+
|
|
4
|
+
except ImportError: # pragma: no cover
|
|
5
|
+
cli_main = None # type: ignore
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
if not cli_main: # type: ignore[truthy-function]
|
|
10
|
+
message = 'To use the fastapi command, please install "fastapi[standard]":\n\n\tpip install "fastapi[standard]"\n'
|
|
11
|
+
print(message)
|
|
12
|
+
raise RuntimeError(message) # noqa: B904
|
|
13
|
+
cli_main()
|
fastapi/concurrency.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from collections.abc import AsyncGenerator
|
|
2
|
+
from contextlib import AbstractContextManager
|
|
3
|
+
from contextlib import asynccontextmanager as asynccontextmanager
|
|
4
|
+
from typing import TypeVar
|
|
5
|
+
|
|
6
|
+
import anyio.to_thread
|
|
7
|
+
from anyio import CapacityLimiter
|
|
8
|
+
from starlette.concurrency import iterate_in_threadpool as iterate_in_threadpool # noqa
|
|
9
|
+
from starlette.concurrency import run_in_threadpool as run_in_threadpool # noqa
|
|
10
|
+
from starlette.concurrency import ( # noqa
|
|
11
|
+
run_until_first_complete as run_until_first_complete,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
_T = TypeVar("_T")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@asynccontextmanager
|
|
18
|
+
async def contextmanager_in_threadpool(
|
|
19
|
+
cm: AbstractContextManager[_T],
|
|
20
|
+
) -> AsyncGenerator[_T, None]:
|
|
21
|
+
# blocking __exit__ from running waiting on a free thread
|
|
22
|
+
# can create race conditions/deadlocks if the context manager itself
|
|
23
|
+
# has its own internal pool (e.g. a database connection pool)
|
|
24
|
+
# to avoid this we let __exit__ run without a capacity limit
|
|
25
|
+
# since we're creating a new limiter for each call, any non-zero limit
|
|
26
|
+
# works (1 is arbitrary)
|
|
27
|
+
exit_limiter = CapacityLimiter(1)
|
|
28
|
+
try:
|
|
29
|
+
yield await run_in_threadpool(cm.__enter__)
|
|
30
|
+
except Exception as e:
|
|
31
|
+
ok = bool(
|
|
32
|
+
await anyio.to_thread.run_sync(
|
|
33
|
+
cm.__exit__, type(e), e, e.__traceback__, limiter=exit_limiter
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
if not ok:
|
|
37
|
+
raise e
|
|
38
|
+
else:
|
|
39
|
+
await anyio.to_thread.run_sync(
|
|
40
|
+
cm.__exit__, None, None, None, limiter=exit_limiter
|
|
41
|
+
)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from typing import (
|
|
3
|
+
Annotated,
|
|
4
|
+
Any,
|
|
5
|
+
BinaryIO,
|
|
6
|
+
Callable,
|
|
7
|
+
Optional,
|
|
8
|
+
TypeVar,
|
|
9
|
+
cast,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from annotated_doc import Doc
|
|
13
|
+
from pydantic import GetJsonSchemaHandler
|
|
14
|
+
from starlette.datastructures import URL as URL # noqa: F401
|
|
15
|
+
from starlette.datastructures import Address as Address # noqa: F401
|
|
16
|
+
from starlette.datastructures import FormData as FormData # noqa: F401
|
|
17
|
+
from starlette.datastructures import Headers as Headers # noqa: F401
|
|
18
|
+
from starlette.datastructures import QueryParams as QueryParams # noqa: F401
|
|
19
|
+
from starlette.datastructures import State as State # noqa: F401
|
|
20
|
+
from starlette.datastructures import UploadFile as StarletteUploadFile
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class UploadFile(StarletteUploadFile):
|
|
24
|
+
"""
|
|
25
|
+
A file uploaded in a request.
|
|
26
|
+
|
|
27
|
+
Define it as a *path operation function* (or dependency) parameter.
|
|
28
|
+
|
|
29
|
+
If you are using a regular `def` function, you can use the `upload_file.file`
|
|
30
|
+
attribute to access the raw standard Python file (blocking, not async), useful and
|
|
31
|
+
needed for non-async code.
|
|
32
|
+
|
|
33
|
+
Read more about it in the
|
|
34
|
+
[FastAPI docs for Request Files](https://fastapi.tiangolo.com/tutorial/request-files/).
|
|
35
|
+
|
|
36
|
+
## Example
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from typing import Annotated
|
|
40
|
+
|
|
41
|
+
from fastapi import FastAPI, File, UploadFile
|
|
42
|
+
|
|
43
|
+
app = FastAPI()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@app.post("/files/")
|
|
47
|
+
async def create_file(file: Annotated[bytes, File()]):
|
|
48
|
+
return {"file_size": len(file)}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.post("/uploadfile/")
|
|
52
|
+
async def create_upload_file(file: UploadFile):
|
|
53
|
+
return {"filename": file.filename}
|
|
54
|
+
```
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
file: Annotated[
|
|
58
|
+
BinaryIO,
|
|
59
|
+
Doc("The standard Python file object (non-async)."),
|
|
60
|
+
]
|
|
61
|
+
filename: Annotated[Optional[str], Doc("The original file name.")]
|
|
62
|
+
size: Annotated[Optional[int], Doc("The size of the file in bytes.")]
|
|
63
|
+
headers: Annotated[Headers, Doc("The headers of the request.")]
|
|
64
|
+
content_type: Annotated[
|
|
65
|
+
Optional[str], Doc("The content type of the request, from the headers.")
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
async def write(
|
|
69
|
+
self,
|
|
70
|
+
data: Annotated[
|
|
71
|
+
bytes,
|
|
72
|
+
Doc(
|
|
73
|
+
"""
|
|
74
|
+
The bytes to write to the file.
|
|
75
|
+
"""
|
|
76
|
+
),
|
|
77
|
+
],
|
|
78
|
+
) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Write some bytes to the file.
|
|
81
|
+
|
|
82
|
+
You normally wouldn't use this from a file you read in a request.
|
|
83
|
+
|
|
84
|
+
To be awaitable, compatible with async, this is run in threadpool.
|
|
85
|
+
"""
|
|
86
|
+
return await super().write(data)
|
|
87
|
+
|
|
88
|
+
async def read(
|
|
89
|
+
self,
|
|
90
|
+
size: Annotated[
|
|
91
|
+
int,
|
|
92
|
+
Doc(
|
|
93
|
+
"""
|
|
94
|
+
The number of bytes to read from the file.
|
|
95
|
+
"""
|
|
96
|
+
),
|
|
97
|
+
] = -1,
|
|
98
|
+
) -> bytes:
|
|
99
|
+
"""
|
|
100
|
+
Read some bytes from the file.
|
|
101
|
+
|
|
102
|
+
To be awaitable, compatible with async, this is run in threadpool.
|
|
103
|
+
"""
|
|
104
|
+
return await super().read(size)
|
|
105
|
+
|
|
106
|
+
async def seek(
|
|
107
|
+
self,
|
|
108
|
+
offset: Annotated[
|
|
109
|
+
int,
|
|
110
|
+
Doc(
|
|
111
|
+
"""
|
|
112
|
+
The position in bytes to seek to in the file.
|
|
113
|
+
"""
|
|
114
|
+
),
|
|
115
|
+
],
|
|
116
|
+
) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Move to a position in the file.
|
|
119
|
+
|
|
120
|
+
Any next read or write will be done from that position.
|
|
121
|
+
|
|
122
|
+
To be awaitable, compatible with async, this is run in threadpool.
|
|
123
|
+
"""
|
|
124
|
+
return await super().seek(offset)
|
|
125
|
+
|
|
126
|
+
async def close(self) -> None:
|
|
127
|
+
"""
|
|
128
|
+
Close the file.
|
|
129
|
+
|
|
130
|
+
To be awaitable, compatible with async, this is run in threadpool.
|
|
131
|
+
"""
|
|
132
|
+
return await super().close()
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def _validate(cls, __input_value: Any, _: Any) -> "UploadFile":
|
|
136
|
+
if not isinstance(__input_value, StarletteUploadFile):
|
|
137
|
+
raise ValueError(f"Expected UploadFile, received: {type(__input_value)}")
|
|
138
|
+
return cast(UploadFile, __input_value)
|
|
139
|
+
|
|
140
|
+
@classmethod
|
|
141
|
+
def __get_pydantic_json_schema__(
|
|
142
|
+
cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler
|
|
143
|
+
) -> dict[str, Any]:
|
|
144
|
+
return {"type": "string", "format": "binary"}
|
|
145
|
+
|
|
146
|
+
@classmethod
|
|
147
|
+
def __get_pydantic_core_schema__(
|
|
148
|
+
cls, source: type[Any], handler: Callable[[Any], Mapping[str, Any]]
|
|
149
|
+
) -> Mapping[str, Any]:
|
|
150
|
+
from ._compat.v2 import with_info_plain_validator_function
|
|
151
|
+
|
|
152
|
+
return with_info_plain_validator_function(cls._validate)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class DefaultPlaceholder:
|
|
156
|
+
"""
|
|
157
|
+
You shouldn't use this class directly.
|
|
158
|
+
|
|
159
|
+
It's used internally to recognize when a default value has been overwritten, even
|
|
160
|
+
if the overridden default value was truthy.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
def __init__(self, value: Any):
|
|
164
|
+
self.value = value
|
|
165
|
+
|
|
166
|
+
def __bool__(self) -> bool:
|
|
167
|
+
return bool(self.value)
|
|
168
|
+
|
|
169
|
+
def __eq__(self, o: object) -> bool:
|
|
170
|
+
return isinstance(o, DefaultPlaceholder) and o.value == self.value
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
DefaultType = TypeVar("DefaultType")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def Default(value: DefaultType) -> DefaultType:
|
|
177
|
+
"""
|
|
178
|
+
You shouldn't use this function directly.
|
|
179
|
+
|
|
180
|
+
It's used internally to recognize when a default value has been overwritten, even
|
|
181
|
+
if the overridden default value was truthy.
|
|
182
|
+
"""
|
|
183
|
+
return DefaultPlaceholder(value) # type: ignore
|
|
File without changes
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import sys
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from functools import cached_property, partial
|
|
5
|
+
from typing import Any, Callable, Optional, Union
|
|
6
|
+
|
|
7
|
+
from fastapi._compat import ModelField
|
|
8
|
+
from fastapi.security.base import SecurityBase
|
|
9
|
+
from fastapi.types import DependencyCacheKey
|
|
10
|
+
from typing_extensions import Literal
|
|
11
|
+
|
|
12
|
+
if sys.version_info >= (3, 13): # pragma: no cover
|
|
13
|
+
from inspect import iscoroutinefunction
|
|
14
|
+
else: # pragma: no cover
|
|
15
|
+
from asyncio import iscoroutinefunction
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _unwrapped_call(call: Optional[Callable[..., Any]]) -> Any:
|
|
19
|
+
if call is None:
|
|
20
|
+
return call # pragma: no cover
|
|
21
|
+
unwrapped = inspect.unwrap(_impartial(call))
|
|
22
|
+
return unwrapped
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _impartial(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
26
|
+
while isinstance(func, partial):
|
|
27
|
+
func = func.func
|
|
28
|
+
return func
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class Dependant:
|
|
33
|
+
path_params: list[ModelField] = field(default_factory=list)
|
|
34
|
+
query_params: list[ModelField] = field(default_factory=list)
|
|
35
|
+
header_params: list[ModelField] = field(default_factory=list)
|
|
36
|
+
cookie_params: list[ModelField] = field(default_factory=list)
|
|
37
|
+
body_params: list[ModelField] = field(default_factory=list)
|
|
38
|
+
dependencies: list["Dependant"] = field(default_factory=list)
|
|
39
|
+
name: Optional[str] = None
|
|
40
|
+
call: Optional[Callable[..., Any]] = None
|
|
41
|
+
request_param_name: Optional[str] = None
|
|
42
|
+
websocket_param_name: Optional[str] = None
|
|
43
|
+
http_connection_param_name: Optional[str] = None
|
|
44
|
+
response_param_name: Optional[str] = None
|
|
45
|
+
background_tasks_param_name: Optional[str] = None
|
|
46
|
+
security_scopes_param_name: Optional[str] = None
|
|
47
|
+
own_oauth_scopes: Optional[list[str]] = None
|
|
48
|
+
parent_oauth_scopes: Optional[list[str]] = None
|
|
49
|
+
use_cache: bool = True
|
|
50
|
+
path: Optional[str] = None
|
|
51
|
+
scope: Union[Literal["function", "request"], None] = None
|
|
52
|
+
|
|
53
|
+
@cached_property
|
|
54
|
+
def oauth_scopes(self) -> list[str]:
|
|
55
|
+
scopes = self.parent_oauth_scopes.copy() if self.parent_oauth_scopes else []
|
|
56
|
+
# This doesn't use a set to preserve order, just in case
|
|
57
|
+
for scope in self.own_oauth_scopes or []:
|
|
58
|
+
if scope not in scopes:
|
|
59
|
+
scopes.append(scope)
|
|
60
|
+
return scopes
|
|
61
|
+
|
|
62
|
+
@cached_property
|
|
63
|
+
def cache_key(self) -> DependencyCacheKey:
|
|
64
|
+
scopes_for_cache = (
|
|
65
|
+
tuple(sorted(set(self.oauth_scopes or []))) if self._uses_scopes else ()
|
|
66
|
+
)
|
|
67
|
+
return (
|
|
68
|
+
self.call,
|
|
69
|
+
scopes_for_cache,
|
|
70
|
+
self.computed_scope or "",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
@cached_property
|
|
74
|
+
def _uses_scopes(self) -> bool:
|
|
75
|
+
if self.own_oauth_scopes:
|
|
76
|
+
return True
|
|
77
|
+
if self.security_scopes_param_name is not None:
|
|
78
|
+
return True
|
|
79
|
+
if self._is_security_scheme:
|
|
80
|
+
return True
|
|
81
|
+
for sub_dep in self.dependencies:
|
|
82
|
+
if sub_dep._uses_scopes:
|
|
83
|
+
return True
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
@cached_property
|
|
87
|
+
def _is_security_scheme(self) -> bool:
|
|
88
|
+
if self.call is None:
|
|
89
|
+
return False # pragma: no cover
|
|
90
|
+
unwrapped = _unwrapped_call(self.call)
|
|
91
|
+
return isinstance(unwrapped, SecurityBase)
|
|
92
|
+
|
|
93
|
+
# Mainly to get the type of SecurityBase, but it's the same self.call
|
|
94
|
+
@cached_property
|
|
95
|
+
def _security_scheme(self) -> SecurityBase:
|
|
96
|
+
unwrapped = _unwrapped_call(self.call)
|
|
97
|
+
assert isinstance(unwrapped, SecurityBase)
|
|
98
|
+
return unwrapped
|
|
99
|
+
|
|
100
|
+
@cached_property
|
|
101
|
+
def _security_dependencies(self) -> list["Dependant"]:
|
|
102
|
+
security_deps = [dep for dep in self.dependencies if dep._is_security_scheme]
|
|
103
|
+
return security_deps
|
|
104
|
+
|
|
105
|
+
@cached_property
|
|
106
|
+
def is_gen_callable(self) -> bool:
|
|
107
|
+
if self.call is None:
|
|
108
|
+
return False # pragma: no cover
|
|
109
|
+
if inspect.isgeneratorfunction(
|
|
110
|
+
_impartial(self.call)
|
|
111
|
+
) or inspect.isgeneratorfunction(_unwrapped_call(self.call)):
|
|
112
|
+
return True
|
|
113
|
+
if inspect.isclass(_unwrapped_call(self.call)):
|
|
114
|
+
return False
|
|
115
|
+
dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004
|
|
116
|
+
if dunder_call is None:
|
|
117
|
+
return False # pragma: no cover
|
|
118
|
+
if inspect.isgeneratorfunction(
|
|
119
|
+
_impartial(dunder_call)
|
|
120
|
+
) or inspect.isgeneratorfunction(_unwrapped_call(dunder_call)):
|
|
121
|
+
return True
|
|
122
|
+
dunder_unwrapped_call = getattr(_unwrapped_call(self.call), "__call__", None) # noqa: B004
|
|
123
|
+
if dunder_unwrapped_call is None:
|
|
124
|
+
return False # pragma: no cover
|
|
125
|
+
if inspect.isgeneratorfunction(
|
|
126
|
+
_impartial(dunder_unwrapped_call)
|
|
127
|
+
) or inspect.isgeneratorfunction(_unwrapped_call(dunder_unwrapped_call)):
|
|
128
|
+
return True
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
@cached_property
|
|
132
|
+
def is_async_gen_callable(self) -> bool:
|
|
133
|
+
if self.call is None:
|
|
134
|
+
return False # pragma: no cover
|
|
135
|
+
if inspect.isasyncgenfunction(
|
|
136
|
+
_impartial(self.call)
|
|
137
|
+
) or inspect.isasyncgenfunction(_unwrapped_call(self.call)):
|
|
138
|
+
return True
|
|
139
|
+
if inspect.isclass(_unwrapped_call(self.call)):
|
|
140
|
+
return False
|
|
141
|
+
dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004
|
|
142
|
+
if dunder_call is None:
|
|
143
|
+
return False # pragma: no cover
|
|
144
|
+
if inspect.isasyncgenfunction(
|
|
145
|
+
_impartial(dunder_call)
|
|
146
|
+
) or inspect.isasyncgenfunction(_unwrapped_call(dunder_call)):
|
|
147
|
+
return True
|
|
148
|
+
dunder_unwrapped_call = getattr(_unwrapped_call(self.call), "__call__", None) # noqa: B004
|
|
149
|
+
if dunder_unwrapped_call is None:
|
|
150
|
+
return False # pragma: no cover
|
|
151
|
+
if inspect.isasyncgenfunction(
|
|
152
|
+
_impartial(dunder_unwrapped_call)
|
|
153
|
+
) or inspect.isasyncgenfunction(_unwrapped_call(dunder_unwrapped_call)):
|
|
154
|
+
return True
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
@cached_property
|
|
158
|
+
def is_coroutine_callable(self) -> bool:
|
|
159
|
+
if self.call is None:
|
|
160
|
+
return False # pragma: no cover
|
|
161
|
+
if inspect.isroutine(_impartial(self.call)) and iscoroutinefunction(
|
|
162
|
+
_impartial(self.call)
|
|
163
|
+
):
|
|
164
|
+
return True
|
|
165
|
+
if inspect.isroutine(_unwrapped_call(self.call)) and iscoroutinefunction(
|
|
166
|
+
_unwrapped_call(self.call)
|
|
167
|
+
):
|
|
168
|
+
return True
|
|
169
|
+
if inspect.isclass(_unwrapped_call(self.call)):
|
|
170
|
+
return False
|
|
171
|
+
dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004
|
|
172
|
+
if dunder_call is None:
|
|
173
|
+
return False # pragma: no cover
|
|
174
|
+
if iscoroutinefunction(_impartial(dunder_call)) or iscoroutinefunction(
|
|
175
|
+
_unwrapped_call(dunder_call)
|
|
176
|
+
):
|
|
177
|
+
return True
|
|
178
|
+
dunder_unwrapped_call = getattr(_unwrapped_call(self.call), "__call__", None) # noqa: B004
|
|
179
|
+
if dunder_unwrapped_call is None:
|
|
180
|
+
return False # pragma: no cover
|
|
181
|
+
if iscoroutinefunction(
|
|
182
|
+
_impartial(dunder_unwrapped_call)
|
|
183
|
+
) or iscoroutinefunction(_unwrapped_call(dunder_unwrapped_call)):
|
|
184
|
+
return True
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
@cached_property
|
|
188
|
+
def computed_scope(self) -> Union[str, None]:
|
|
189
|
+
if self.scope:
|
|
190
|
+
return self.scope
|
|
191
|
+
if self.is_gen_callable or self.is_async_gen_callable:
|
|
192
|
+
return "request"
|
|
193
|
+
return None
|