fastapi-deferred-init 0.2.11__tar.gz → 0.3.0__tar.gz
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_deferred_init-0.3.0/PKG-INFO +64 -0
- fastapi_deferred_init-0.2.11/PKG-INFO → fastapi_deferred_init-0.3.0/README.md +19 -13
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.3.0}/pyproject.toml +20 -8
- fastapi_deferred_init-0.3.0/src/fastapi_deferred_init/__init__.py +3 -0
- fastapi_deferred_init-0.3.0/src/fastapi_deferred_init/patch.py +10 -0
- fastapi_deferred_init-0.3.0/src/fastapi_deferred_init/routing.py +270 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.3.0}/tests/data/gen_code_ast.py +17 -14
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.3.0}/tests/test_lib.py +61 -16
- fastapi_deferred_init-0.2.11/README.md +0 -22
- fastapi_deferred_init-0.2.11/src/fastapi_deferred_init/__init__.py +0 -3
- fastapi_deferred_init-0.2.11/src/fastapi_deferred_init/routing.py +0 -238
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.3.0}/LICENSE +0 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.3.0}/src/fastapi_deferred_init/py.typed +0 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.3.0}/tests/__init__.py +0 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.3.0}/tests/conftest.py +0 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.3.0}/tests/data/__init__.py +0 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.3.0}/tests/data/template.py +0 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.3.0}/tests/helpers.py +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: fastapi-deferred-init
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Faster FastAPI start-up time for Projects with many nested routers
|
|
5
|
+
Keywords: fastapi,speed,router,startup,optimization
|
|
6
|
+
Author-Email: Jan Vollmer <jan@vllmr.dev>
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Environment :: Plugins
|
|
9
|
+
Classifier: Framework :: FastAPI
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Project-URL: Source, https://github.com/jvllmr/fastapi-deferred-init
|
|
19
|
+
Project-URL: Changelog, https://github.com/jvllmr/fastapi-deferred-init/releases
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: fastapi>=0.137.2
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# fastapi-deferred-init
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
[](https://github.com/jvllmr/fastapi-deferred-init/blob/master/LICENSE)
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
## The Problem
|
|
31
|
+
|
|
32
|
+
When using nested routers in a FastAPI project its start-up time can get long quite fast.
|
|
33
|
+
That is because every router re-calculates the routes defined by a nested router when including it and the pre-calculated values by the nested router never get used. In short: values in nested routers are calculated although they will never be used.
|
|
34
|
+
|
|
35
|
+
## The Solution
|
|
36
|
+
|
|
37
|
+
This library provides a modified APIRoute that defers the calculation of values to the first actual attribute access. A router which uses the route as a default is also provided.
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
Older versions of this library worked by simply using custom APIRoute and APIRouter classes, but since FastAPI version 0.137.0 the optimizations require monkey patching to take effect. The following example shows how the patches can be applied by creating an `asgi.py` file as your entrypoint:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
# my_app/asgi.py
|
|
45
|
+
from fastapi_deferred_init import apply_patch
|
|
46
|
+
|
|
47
|
+
# important! don't import anything else before this to make sure the patch works as expected
|
|
48
|
+
apply_patch()
|
|
49
|
+
|
|
50
|
+
# run your app with i.e. gunicorn my_app.asgi:app
|
|
51
|
+
from my_app.app import app
|
|
52
|
+
|
|
53
|
+
# mark app as re-export
|
|
54
|
+
__all__ = ["app"]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Caveat
|
|
58
|
+
|
|
59
|
+
When using the deferred APIRoute on every layer of the app, startup errors are deferred to the first time a route is called. So errors related to route registration might go undetected if the route is not tested at least once.
|
|
60
|
+
|
|
61
|
+
## Pull Request
|
|
62
|
+
|
|
63
|
+
I created a pull request to merge this change back into FastAPI: https://github.com/tiangolo/fastapi/pull/10589
|
|
64
|
+
It was closed since major refactors to FastAPI are underway, but I might re-open it once the maintainers are ready.
|
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: fastapi-deferred-init
|
|
3
|
-
Version: 0.2.11
|
|
4
|
-
Summary: Faster FastAPI start-up time for Projects with many nested routers
|
|
5
|
-
Keywords: fastapi,speed,router
|
|
6
|
-
Author-Email: Jan Vollmer <jan@vllmr.dev>
|
|
7
|
-
License: MIT
|
|
8
|
-
Classifier: Framework :: FastAPI
|
|
9
|
-
Project-URL: Source, https://github.com/jvllmr/fastapi-deferred-init
|
|
10
|
-
Requires-Python: >=3.10
|
|
11
|
-
Requires-Dist: fastapi>=0.132.0
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
|
|
14
1
|
# fastapi-deferred-init
|
|
15
2
|
|
|
16
3
|

|
|
@@ -26,6 +13,24 @@ That is because every router re-calculates the routes defined by a nested router
|
|
|
26
13
|
|
|
27
14
|
This library provides a modified APIRoute that defers the calculation of values to the first actual attribute access. A router which uses the route as a default is also provided.
|
|
28
15
|
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
Older versions of this library worked by simply using custom APIRoute and APIRouter classes, but since FastAPI version 0.137.0 the optimizations require monkey patching to take effect. The following example shows how the patches can be applied by creating an `asgi.py` file as your entrypoint:
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
# my_app/asgi.py
|
|
22
|
+
from fastapi_deferred_init import apply_patch
|
|
23
|
+
|
|
24
|
+
# important! don't import anything else before this to make sure the patch works as expected
|
|
25
|
+
apply_patch()
|
|
26
|
+
|
|
27
|
+
# run your app with i.e. gunicorn my_app.asgi:app
|
|
28
|
+
from my_app.app import app
|
|
29
|
+
|
|
30
|
+
# mark app as re-export
|
|
31
|
+
__all__ = ["app"]
|
|
32
|
+
```
|
|
33
|
+
|
|
29
34
|
## Caveat
|
|
30
35
|
|
|
31
36
|
When using the deferred APIRoute on every layer of the app, startup errors are deferred to the first time a route is called. So errors related to route registration might go undetected if the route is not tested at least once.
|
|
@@ -33,3 +38,4 @@ When using the deferred APIRoute on every layer of the app, startup errors are d
|
|
|
33
38
|
## Pull Request
|
|
34
39
|
|
|
35
40
|
I created a pull request to merge this change back into FastAPI: https://github.com/tiangolo/fastapi/pull/10589
|
|
41
|
+
It was closed since major refactors to FastAPI are underway, but I might re-open it once the maintainers are ready.
|
|
@@ -5,26 +5,38 @@ keywords = [
|
|
|
5
5
|
"fastapi",
|
|
6
6
|
"speed",
|
|
7
7
|
"router",
|
|
8
|
+
"startup",
|
|
9
|
+
"optimization",
|
|
8
10
|
]
|
|
9
11
|
authors = [
|
|
10
12
|
{ name = "Jan Vollmer", email = "jan@vllmr.dev" },
|
|
11
13
|
]
|
|
12
14
|
dependencies = [
|
|
13
|
-
"fastapi>=0.
|
|
15
|
+
"fastapi>=0.137.2",
|
|
14
16
|
]
|
|
15
17
|
requires-python = ">=3.10"
|
|
16
18
|
readme = "README.md"
|
|
17
19
|
classifiers = [
|
|
20
|
+
"Environment :: Plugins",
|
|
18
21
|
"Framework :: FastAPI",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Programming Language :: Python :: 3.14",
|
|
29
|
+
"Typing :: Typed",
|
|
19
30
|
]
|
|
20
31
|
dynamic = []
|
|
21
|
-
version = "0.
|
|
32
|
+
version = "0.3.0"
|
|
22
33
|
|
|
23
34
|
[project.license]
|
|
24
35
|
text = "MIT"
|
|
25
36
|
|
|
26
37
|
[project.urls]
|
|
27
38
|
Source = "https://github.com/jvllmr/fastapi-deferred-init"
|
|
39
|
+
Changelog = "https://github.com/jvllmr/fastapi-deferred-init/releases"
|
|
28
40
|
|
|
29
41
|
[build-system]
|
|
30
42
|
requires = [
|
|
@@ -73,13 +85,13 @@ all = true
|
|
|
73
85
|
|
|
74
86
|
[dependency-groups]
|
|
75
87
|
dev = [
|
|
76
|
-
"pytest>=
|
|
77
|
-
"ruff>=0.15.
|
|
88
|
+
"pytest>=9.1.0",
|
|
89
|
+
"ruff>=0.15.17",
|
|
78
90
|
"pre-commit>=4.3.0",
|
|
79
|
-
"mypy>=1.
|
|
80
|
-
"tuna>=0.5.
|
|
91
|
+
"mypy>=1.20.2",
|
|
92
|
+
"tuna>=0.5.15",
|
|
81
93
|
"httpx>=0.28.1",
|
|
82
94
|
"pytest-benchmark>=5.2.3",
|
|
83
|
-
"pip>=26.
|
|
84
|
-
"inline-snapshot>=0.
|
|
95
|
+
"pip>=26.1.2",
|
|
96
|
+
"inline-snapshot>=0.34.1",
|
|
85
97
|
]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from .routing import _populate_api_route_state, DeferringAPIRoute, DeferringAPIRouter
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def apply_patch() -> None:
|
|
5
|
+
|
|
6
|
+
from fastapi import routing # type: ignore[attr-defined]
|
|
7
|
+
|
|
8
|
+
routing._populate_api_route_state = _populate_api_route_state # ty:ignore[invalid-assignment]
|
|
9
|
+
routing.APIRoute = DeferringAPIRoute # ty:ignore[invalid-assignment]
|
|
10
|
+
routing.APIRouter = DeferringAPIRouter
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
from fastapi.dependencies.models import Dependant
|
|
2
|
+
import inspect
|
|
3
|
+
from enum import Enum, IntEnum
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
from typing import Any, Callable, Union, cast
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from fastapi.sse import (
|
|
10
|
+
EventSourceResponse,
|
|
11
|
+
ServerSentEvent,
|
|
12
|
+
)
|
|
13
|
+
from fastapi import params, routing # type: ignore[attr-defined]
|
|
14
|
+
from fastapi._compat import ModelField, lenient_issubclass
|
|
15
|
+
from fastapi.datastructures import Default, DefaultPlaceholder
|
|
16
|
+
from fastapi.dependencies.utils import (
|
|
17
|
+
_should_embed_body_fields,
|
|
18
|
+
get_body_field,
|
|
19
|
+
get_dependant,
|
|
20
|
+
get_flat_dependant,
|
|
21
|
+
get_parameterless_sub_dependant,
|
|
22
|
+
get_typed_return_annotation,
|
|
23
|
+
get_stream_item_type,
|
|
24
|
+
)
|
|
25
|
+
from fastapi.responses import JSONResponse, Response
|
|
26
|
+
from fastapi.types import IncEx
|
|
27
|
+
from fastapi.utils import (
|
|
28
|
+
create_model_field,
|
|
29
|
+
generate_unique_id,
|
|
30
|
+
is_body_allowed_for_status_code,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _add_cache_attribute(
|
|
35
|
+
instance: routing._APIRouteLike, name: str, func: Callable[[Any], Any]
|
|
36
|
+
) -> None:
|
|
37
|
+
cls = type(instance)
|
|
38
|
+
if hasattr(instance, name) and (
|
|
39
|
+
(val := getattr(instance, name)) is None or val is False
|
|
40
|
+
):
|
|
41
|
+
delattr(instance, name)
|
|
42
|
+
|
|
43
|
+
if not hasattr(cls, name) or not isinstance(getattr(cls, name), cached_property):
|
|
44
|
+
prop = cached_property(func)
|
|
45
|
+
setattr(cls, name, prop)
|
|
46
|
+
prop.__set_name__(cls, name)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _populate_api_route_state(
|
|
50
|
+
route: routing._APIRouteLike,
|
|
51
|
+
path: str,
|
|
52
|
+
endpoint: Callable[..., Any],
|
|
53
|
+
*,
|
|
54
|
+
response_model: Any = Default(None),
|
|
55
|
+
status_code: int | None = None,
|
|
56
|
+
tags: list[str | Enum] | None = None,
|
|
57
|
+
dependencies: Sequence[params.Depends] | None = None,
|
|
58
|
+
summary: str | None = None,
|
|
59
|
+
description: str | None = None,
|
|
60
|
+
response_description: str = "Successful Response",
|
|
61
|
+
responses: dict[int | str, dict[str, Any]] | None = None,
|
|
62
|
+
deprecated: bool | None = None,
|
|
63
|
+
name: str | None = None,
|
|
64
|
+
methods: set[str] | list[str] | None = None,
|
|
65
|
+
operation_id: str | None = None,
|
|
66
|
+
response_model_include: IncEx | None = None,
|
|
67
|
+
response_model_exclude: IncEx | None = None,
|
|
68
|
+
response_model_by_alias: bool = True,
|
|
69
|
+
response_model_exclude_unset: bool = False,
|
|
70
|
+
response_model_exclude_defaults: bool = False,
|
|
71
|
+
response_model_exclude_none: bool = False,
|
|
72
|
+
include_in_schema: bool = True,
|
|
73
|
+
response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse),
|
|
74
|
+
dependency_overrides_provider: Any | None = None,
|
|
75
|
+
callbacks: list[routing.BaseRoute] | None = None,
|
|
76
|
+
openapi_extra: dict[str, Any] | None = None,
|
|
77
|
+
generate_unique_id_function: Callable[[Any], str] | DefaultPlaceholder = Default(
|
|
78
|
+
generate_unique_id
|
|
79
|
+
),
|
|
80
|
+
strict_content_type: bool | DefaultPlaceholder = Default(True),
|
|
81
|
+
) -> None:
|
|
82
|
+
route.path = path
|
|
83
|
+
route.endpoint = endpoint
|
|
84
|
+
route.stream_item_type = None
|
|
85
|
+
if isinstance(response_model, DefaultPlaceholder):
|
|
86
|
+
return_annotation = get_typed_return_annotation(endpoint)
|
|
87
|
+
if lenient_issubclass(return_annotation, Response):
|
|
88
|
+
response_model = None
|
|
89
|
+
else:
|
|
90
|
+
stream_item = get_stream_item_type(return_annotation)
|
|
91
|
+
if stream_item is not None:
|
|
92
|
+
# Extract item type for JSONL or SSE streaming when
|
|
93
|
+
# response_class is DefaultPlaceholder (JSONL) or
|
|
94
|
+
# EventSourceResponse (SSE).
|
|
95
|
+
# ServerSentEvent is excluded: it's a transport
|
|
96
|
+
# wrapper, not a data model, so it shouldn't feed
|
|
97
|
+
# into validation or OpenAPI schema generation.
|
|
98
|
+
if (
|
|
99
|
+
isinstance(response_class, DefaultPlaceholder)
|
|
100
|
+
or lenient_issubclass(response_class, EventSourceResponse)
|
|
101
|
+
) and not lenient_issubclass(stream_item, ServerSentEvent):
|
|
102
|
+
route.stream_item_type = stream_item
|
|
103
|
+
response_model = None
|
|
104
|
+
else:
|
|
105
|
+
response_model = return_annotation
|
|
106
|
+
route.response_model = response_model
|
|
107
|
+
route.summary = summary
|
|
108
|
+
route.response_description = response_description
|
|
109
|
+
route.deprecated = deprecated
|
|
110
|
+
route.operation_id = operation_id
|
|
111
|
+
route.response_model_include = response_model_include
|
|
112
|
+
route.response_model_exclude = response_model_exclude
|
|
113
|
+
route.response_model_by_alias = response_model_by_alias
|
|
114
|
+
route.response_model_exclude_unset = response_model_exclude_unset
|
|
115
|
+
route.response_model_exclude_defaults = response_model_exclude_defaults
|
|
116
|
+
route.response_model_exclude_none = response_model_exclude_none
|
|
117
|
+
route.include_in_schema = include_in_schema
|
|
118
|
+
route.response_class = response_class
|
|
119
|
+
route.dependency_overrides_provider = dependency_overrides_provider
|
|
120
|
+
route.callbacks = callbacks
|
|
121
|
+
route.openapi_extra = openapi_extra
|
|
122
|
+
route.generate_unique_id_function = generate_unique_id_function
|
|
123
|
+
route.strict_content_type = strict_content_type
|
|
124
|
+
route.tags = tags or []
|
|
125
|
+
route.responses = responses or {}
|
|
126
|
+
route.name = routing.get_name(endpoint) if name is None else name
|
|
127
|
+
route.path_regex, route.path_format, route.param_convertors = routing.compile_path(
|
|
128
|
+
path
|
|
129
|
+
)
|
|
130
|
+
if methods is None:
|
|
131
|
+
methods = ["GET"]
|
|
132
|
+
route.methods = {method.upper() for method in methods}
|
|
133
|
+
if isinstance(generate_unique_id_function, DefaultPlaceholder):
|
|
134
|
+
current_generate_unique_id: Callable[[Any], str] = (
|
|
135
|
+
generate_unique_id_function.value
|
|
136
|
+
)
|
|
137
|
+
else:
|
|
138
|
+
current_generate_unique_id = generate_unique_id_function
|
|
139
|
+
route.unique_id = route.operation_id or current_generate_unique_id(route)
|
|
140
|
+
# normalize enums e.g. http.HTTPStatus
|
|
141
|
+
if isinstance(status_code, IntEnum):
|
|
142
|
+
status_code = int(status_code)
|
|
143
|
+
route.status_code = status_code
|
|
144
|
+
if route.response_model:
|
|
145
|
+
assert is_body_allowed_for_status_code(status_code), (
|
|
146
|
+
f"Status code {status_code} must not have a response body"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def _response_field(self):
|
|
150
|
+
if self.response_model:
|
|
151
|
+
response_name = "Response_" + self.unique_id
|
|
152
|
+
return create_model_field(
|
|
153
|
+
name=response_name,
|
|
154
|
+
type_=self.response_model,
|
|
155
|
+
mode="serialization",
|
|
156
|
+
)
|
|
157
|
+
else:
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
_add_cache_attribute(route, "response_field", _response_field)
|
|
161
|
+
|
|
162
|
+
def _stream_item_field(self) -> ModelField | None:
|
|
163
|
+
if not self.stream_item_type:
|
|
164
|
+
return None
|
|
165
|
+
stream_item_name = "StreamItem_" + self.unique_id
|
|
166
|
+
return create_model_field(
|
|
167
|
+
name=stream_item_name,
|
|
168
|
+
type_=self.stream_item_type,
|
|
169
|
+
mode="serialization",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
_add_cache_attribute(route, "stream_item_field", _stream_item_field)
|
|
173
|
+
|
|
174
|
+
route.dependencies = list(dependencies or [])
|
|
175
|
+
route.description = description or inspect.cleandoc(route.endpoint.__doc__ or "")
|
|
176
|
+
# if a "form feed" character (page break) is found in the description text,
|
|
177
|
+
# truncate description text to the content preceding the first "form feed"
|
|
178
|
+
route.description = route.description.split("\f")[0].strip()
|
|
179
|
+
|
|
180
|
+
def _response_fields(self) -> dict[Union[int, str], ModelField]:
|
|
181
|
+
response_fields: dict[Union[int, str], ModelField] = {}
|
|
182
|
+
for additional_status_code, response in self.responses.items():
|
|
183
|
+
assert isinstance(response, dict), "An additional response must be a dict"
|
|
184
|
+
model = response.get("model")
|
|
185
|
+
if model:
|
|
186
|
+
assert is_body_allowed_for_status_code(additional_status_code), (
|
|
187
|
+
f"Status code {additional_status_code} must not have a response body"
|
|
188
|
+
)
|
|
189
|
+
response_name = f"Response_{additional_status_code}_{self.unique_id}"
|
|
190
|
+
response_field = create_model_field(
|
|
191
|
+
name=response_name, type_=model, mode="serialization"
|
|
192
|
+
)
|
|
193
|
+
response_fields[additional_status_code] = response_field
|
|
194
|
+
return response_fields
|
|
195
|
+
|
|
196
|
+
_add_cache_attribute(route, "response_fields", _response_fields)
|
|
197
|
+
|
|
198
|
+
assert callable(endpoint), "An endpoint must be a callable"
|
|
199
|
+
|
|
200
|
+
def _get_dependant(self: routing._APIRouteLike) -> Dependant:
|
|
201
|
+
dependant = get_dependant(
|
|
202
|
+
path=self.path_format, call=self.endpoint, scope="function"
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
for depends in self.dependencies[::-1]:
|
|
206
|
+
dependant.dependencies.insert(
|
|
207
|
+
0,
|
|
208
|
+
get_parameterless_sub_dependant(depends=depends, path=self.path_format),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return dependant
|
|
212
|
+
|
|
213
|
+
_add_cache_attribute(route, "dependant", _get_dependant)
|
|
214
|
+
|
|
215
|
+
def _get_flat_dependant(self) -> Dependant:
|
|
216
|
+
return get_flat_dependant(self.dependant)
|
|
217
|
+
|
|
218
|
+
_add_cache_attribute(route, "_flat_dependant", _get_flat_dependant)
|
|
219
|
+
|
|
220
|
+
def _embed_body_fields(self):
|
|
221
|
+
return _should_embed_body_fields(self._flat_dependant.body_params)
|
|
222
|
+
|
|
223
|
+
_add_cache_attribute(route, "_embed_body_fields", _embed_body_fields)
|
|
224
|
+
|
|
225
|
+
def _body_field(self):
|
|
226
|
+
return get_body_field(
|
|
227
|
+
flat_dependant=self._flat_dependant,
|
|
228
|
+
name=self.unique_id,
|
|
229
|
+
embed_body_fields=self._embed_body_fields,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
_add_cache_attribute(route, "body_field", _body_field)
|
|
233
|
+
|
|
234
|
+
# Detect generator endpoints that should stream as JSONL or SSE
|
|
235
|
+
def _is_generator(self) -> bool:
|
|
236
|
+
return self.dependant.is_async_gen_callable or self.dependant.is_gen_callable
|
|
237
|
+
|
|
238
|
+
_add_cache_attribute(route, "is_generator", _is_generator)
|
|
239
|
+
|
|
240
|
+
def _is_sse_stream(self) -> bool:
|
|
241
|
+
return self.is_generator and lenient_issubclass(
|
|
242
|
+
self.response_class, EventSourceResponse
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
_add_cache_attribute(route, "is_sse_stream", _is_sse_stream)
|
|
246
|
+
|
|
247
|
+
def _is_json_stream(self) -> bool:
|
|
248
|
+
return self.is_generator and isinstance(self.response_class, DefaultPlaceholder)
|
|
249
|
+
|
|
250
|
+
_add_cache_attribute(route, "is_json_stream", _is_json_stream)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class DeferringAPIRoute(routing.APIRoute):
|
|
254
|
+
def __init__(
|
|
255
|
+
self, path: str, endpoint: Callable[..., Any], *args, **kwargs
|
|
256
|
+
) -> None:
|
|
257
|
+
_populate_api_route_state(
|
|
258
|
+
cast(routing._APIRouteLike, self), path, endpoint, *args, **kwargs
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
@cached_property
|
|
262
|
+
def app(self):
|
|
263
|
+
return routing.request_response(self.get_route_handler())
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class DeferringAPIRouter(routing.APIRouter):
|
|
267
|
+
def __init__(self, *args, **kwargs):
|
|
268
|
+
if "route_class" not in kwargs:
|
|
269
|
+
kwargs["route_class"] = DeferringAPIRoute
|
|
270
|
+
super().__init__(*args, **kwargs)
|
|
@@ -13,11 +13,6 @@ base_module = ast.Module(
|
|
|
13
13
|
names=[ast.alias(name="JSONResponse")],
|
|
14
14
|
level=0,
|
|
15
15
|
),
|
|
16
|
-
ast.ImportFrom(
|
|
17
|
-
module="fastapi_deferred_init",
|
|
18
|
-
names=[ast.alias(name="DeferringAPIRouter")],
|
|
19
|
-
level=0,
|
|
20
|
-
),
|
|
21
16
|
ast.Assign(
|
|
22
17
|
targets=[ast.Name(id="dependency0", ctx=ast.Store())],
|
|
23
18
|
value=ast.Lambda(
|
|
@@ -58,14 +53,12 @@ def gen_func_def(n=1, last=False):
|
|
|
58
53
|
)
|
|
59
54
|
|
|
60
55
|
|
|
61
|
-
def gen_router(n=2, last=False
|
|
56
|
+
def gen_router(n=2, last=False):
|
|
62
57
|
return [
|
|
63
58
|
ast.Assign(
|
|
64
59
|
targets=[ast.Name(id=f"router{'' if last else n}", ctx=ast.Store())],
|
|
65
60
|
value=ast.Call(
|
|
66
|
-
func=ast.Name(
|
|
67
|
-
id="DeferringAPIRouter" if use_lib else "APIRouter", ctx=ast.Load()
|
|
68
|
-
),
|
|
61
|
+
func=ast.Name(id="APIRouter", ctx=ast.Load()),
|
|
69
62
|
args=[],
|
|
70
63
|
keywords=[
|
|
71
64
|
ast.keyword(
|
|
@@ -151,7 +144,9 @@ def gen_router(n=2, last=False, use_lib=True):
|
|
|
151
144
|
)
|
|
152
145
|
|
|
153
146
|
|
|
154
|
-
def get_tree(
|
|
147
|
+
def get_tree(
|
|
148
|
+
n=1000,
|
|
149
|
+
):
|
|
155
150
|
module = copy.deepcopy(base_module)
|
|
156
151
|
for i in range(1, n + 1):
|
|
157
152
|
module.body.append(gen_func_def(i))
|
|
@@ -159,16 +154,24 @@ def get_tree(n=1000, use_lib=True):
|
|
|
159
154
|
module.body.append(gen_func_def(i + 1, True))
|
|
160
155
|
|
|
161
156
|
for i in range(n):
|
|
162
|
-
module.body.extend(
|
|
157
|
+
module.body.extend(
|
|
158
|
+
gen_router(
|
|
159
|
+
i,
|
|
160
|
+
)
|
|
161
|
+
)
|
|
163
162
|
|
|
164
|
-
module.body.extend(gen_router(i + 1, True
|
|
163
|
+
module.body.extend(gen_router(i + 1, True))
|
|
165
164
|
|
|
166
165
|
ast.fix_missing_locations(module)
|
|
167
166
|
return module
|
|
168
167
|
|
|
169
168
|
|
|
170
|
-
def create_code(
|
|
171
|
-
|
|
169
|
+
def create_code(
|
|
170
|
+
n=1000,
|
|
171
|
+
):
|
|
172
|
+
tree = get_tree(
|
|
173
|
+
n,
|
|
174
|
+
)
|
|
172
175
|
|
|
173
176
|
with open("tests/data/code.py", "w") as f:
|
|
174
177
|
f.write(ast.unparse(tree))
|
|
@@ -2,12 +2,15 @@ import os
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
from pydantic import BaseModel
|
|
5
|
-
from starlette.routing import Route
|
|
6
5
|
|
|
7
|
-
from fastapi import
|
|
8
|
-
from fastapi
|
|
6
|
+
from fastapi import FastAPI
|
|
7
|
+
from fastapi import routing
|
|
9
8
|
from fastapi.testclient import TestClient
|
|
10
|
-
from fastapi_deferred_init import
|
|
9
|
+
from fastapi_deferred_init.routing import (
|
|
10
|
+
_populate_api_route_state,
|
|
11
|
+
DeferringAPIRoute,
|
|
12
|
+
DeferringAPIRouter,
|
|
13
|
+
)
|
|
11
14
|
|
|
12
15
|
from .data.gen_code_ast import create_code
|
|
13
16
|
from .helpers import import_via_file_path, load_code
|
|
@@ -18,10 +21,25 @@ def skip_test(use_lib: bool):
|
|
|
18
21
|
pytest.skip()
|
|
19
22
|
|
|
20
23
|
|
|
24
|
+
def apply_monkeypatch(monkeypatch):
|
|
25
|
+
monkeypatch.setattr(
|
|
26
|
+
"fastapi.routing._populate_api_route_state", _populate_api_route_state
|
|
27
|
+
)
|
|
28
|
+
monkeypatch.setattr("fastapi.routing.APIRoute", DeferringAPIRoute)
|
|
29
|
+
monkeypatch.setattr("fastapi.routing.APIRouter", DeferringAPIRouter)
|
|
30
|
+
# monkeypatch.setattr(
|
|
31
|
+
# "fastapi.APIRouter", DeferringAPIRouter
|
|
32
|
+
# )
|
|
33
|
+
|
|
34
|
+
|
|
21
35
|
@pytest.mark.parametrize(["use_lib"], [(True,), (False,)])
|
|
22
|
-
def test_basic(use_lib: bool, benchmark):
|
|
36
|
+
def test_basic(use_lib: bool, benchmark, monkeypatch):
|
|
23
37
|
skip_test(use_lib=use_lib)
|
|
24
|
-
|
|
38
|
+
if use_lib:
|
|
39
|
+
apply_monkeypatch(monkeypatch)
|
|
40
|
+
create_code(
|
|
41
|
+
50,
|
|
42
|
+
)
|
|
25
43
|
|
|
26
44
|
generated_code = benchmark(load_code)
|
|
27
45
|
|
|
@@ -30,22 +48,27 @@ def test_basic(use_lib: bool, benchmark):
|
|
|
30
48
|
|
|
31
49
|
app.include_router(router)
|
|
32
50
|
|
|
33
|
-
assert type(router) is (DeferringAPIRouter if use_lib else APIRouter)
|
|
34
51
|
client = TestClient(app)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
52
|
+
routes = list(routing.iter_route_contexts(app.routes))
|
|
53
|
+
assert len(routes) == 54
|
|
54
|
+
for route in routes:
|
|
55
|
+
if not route.path or route.path in ("/openapi.json"):
|
|
56
|
+
continue
|
|
40
57
|
resp = client.get(route.path)
|
|
41
58
|
assert resp.status_code == 200
|
|
42
59
|
|
|
43
60
|
|
|
44
61
|
@pytest.mark.parametrize(["use_lib"], [(True,), (False,)])
|
|
45
|
-
def test_with_pydantic_model(use_lib: bool):
|
|
62
|
+
def test_with_pydantic_model(use_lib: bool, monkeypatch):
|
|
63
|
+
|
|
46
64
|
skip_test(use_lib=use_lib)
|
|
65
|
+
if use_lib:
|
|
66
|
+
apply_monkeypatch(monkeypatch)
|
|
47
67
|
app = FastAPI()
|
|
48
|
-
router =
|
|
68
|
+
router = routing.APIRouter()
|
|
69
|
+
assert isinstance(router, DeferringAPIRouter if use_lib else routing.APIRouter), (
|
|
70
|
+
type(router)
|
|
71
|
+
)
|
|
49
72
|
|
|
50
73
|
class Login(BaseModel):
|
|
51
74
|
username: str
|
|
@@ -59,6 +82,12 @@ def test_with_pydantic_model(use_lib: bool):
|
|
|
59
82
|
|
|
60
83
|
return {"userdata": "[...]"}
|
|
61
84
|
|
|
85
|
+
assert isinstance(
|
|
86
|
+
router.routes[0], DeferringAPIRoute if use_lib else routing.APIRoute
|
|
87
|
+
), (
|
|
88
|
+
router.routes,
|
|
89
|
+
type(router.routes[0]),
|
|
90
|
+
)
|
|
62
91
|
app.include_router(router)
|
|
63
92
|
|
|
64
93
|
client = TestClient(app)
|
|
@@ -68,8 +97,8 @@ def test_with_pydantic_model(use_lib: bool):
|
|
|
68
97
|
|
|
69
98
|
|
|
70
99
|
def test_fastapi_openapi_schema(monkeypatch):
|
|
71
|
-
monkeypatch
|
|
72
|
-
|
|
100
|
+
apply_monkeypatch(monkeypatch)
|
|
101
|
+
|
|
73
102
|
import_via_file_path(
|
|
74
103
|
"fastapi_clone.tests.test_additional_properties",
|
|
75
104
|
"fastapi/tests/test_additional_properties.py",
|
|
@@ -89,3 +118,19 @@ def test_fastapi_openapi_schema(monkeypatch):
|
|
|
89
118
|
|
|
90
119
|
fastapi_test_openapi_schema()
|
|
91
120
|
fastapi_test_openapi_schema_additional()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_fastapi_sse(monkeypatch):
|
|
124
|
+
apply_monkeypatch(monkeypatch)
|
|
125
|
+
import_via_file_path(
|
|
126
|
+
"fastapi_clone.tests.test_sse",
|
|
127
|
+
"fastapi/tests/test_sse.py",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
from fastapi_clone.tests.test_sse import ( # type: ignore
|
|
131
|
+
test_raw_data_sent_without_json_encoding as fastapi_test_raw_data_sent_without_json_encoding,
|
|
132
|
+
client_fixture as fastapi_client_fixture,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
for client in fastapi_client_fixture.__wrapped__():
|
|
136
|
+
fastapi_test_raw_data_sent_without_json_encoding(client)
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# fastapi-deferred-init
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-
[](https://github.com/jvllmr/fastapi-deferred-init/blob/master/LICENSE)
|
|
5
|
-

|
|
6
|
-
|
|
7
|
-
## The Problem
|
|
8
|
-
|
|
9
|
-
When using nested routers in a FastAPI project its start-up time can get long quite fast.
|
|
10
|
-
That is because every router re-calculates the routes defined by a nested router when including it and the pre-calculated values by the nested router never get used. In short: values in nested routers are calculated although they will never be used.
|
|
11
|
-
|
|
12
|
-
## The Solution
|
|
13
|
-
|
|
14
|
-
This library provides a modified APIRoute that defers the calculation of values to the first actual attribute access. A router which uses the route as a default is also provided.
|
|
15
|
-
|
|
16
|
-
## Caveat
|
|
17
|
-
|
|
18
|
-
When using the deferred APIRoute on every layer of the app, startup errors are deferred to the first time a route is called. So errors related to route registration might go undetected if the route is not tested at least once.
|
|
19
|
-
|
|
20
|
-
## Pull Request
|
|
21
|
-
|
|
22
|
-
I created a pull request to merge this change back into FastAPI: https://github.com/tiangolo/fastapi/pull/10589
|
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
import inspect
|
|
2
|
-
from enum import Enum, IntEnum
|
|
3
|
-
from functools import cached_property
|
|
4
|
-
from typing import (
|
|
5
|
-
Any,
|
|
6
|
-
Callable,
|
|
7
|
-
ClassVar,
|
|
8
|
-
Optional,
|
|
9
|
-
Union,
|
|
10
|
-
)
|
|
11
|
-
from collections.abc import Sequence
|
|
12
|
-
|
|
13
|
-
from starlette.routing import BaseRoute
|
|
14
|
-
from starlette.routing import Mount as Mount # noqa
|
|
15
|
-
from starlette.routing import compile_path, get_name
|
|
16
|
-
from starlette.types import ASGIApp, Lifespan
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
from fastapi import params, routing # type:ignore
|
|
20
|
-
from fastapi._compat import ModelField, lenient_issubclass
|
|
21
|
-
from fastapi.datastructures import Default, DefaultPlaceholder
|
|
22
|
-
from fastapi.dependencies.utils import (
|
|
23
|
-
_should_embed_body_fields,
|
|
24
|
-
get_body_field,
|
|
25
|
-
get_dependant,
|
|
26
|
-
get_flat_dependant,
|
|
27
|
-
get_parameterless_sub_dependant,
|
|
28
|
-
get_typed_return_annotation,
|
|
29
|
-
)
|
|
30
|
-
from fastapi.responses import JSONResponse, Response
|
|
31
|
-
from fastapi.types import IncEx
|
|
32
|
-
from fastapi.utils import (
|
|
33
|
-
create_model_field,
|
|
34
|
-
generate_unique_id,
|
|
35
|
-
is_body_allowed_for_status_code,
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class DeferringAPIRoute(routing.APIRoute):
|
|
40
|
-
_getattr: ClassVar[Callable[[Any, str], Any]]
|
|
41
|
-
|
|
42
|
-
def __init__(
|
|
43
|
-
self,
|
|
44
|
-
path: str,
|
|
45
|
-
endpoint: Callable[..., Any],
|
|
46
|
-
*,
|
|
47
|
-
response_model: Any = Default(None),
|
|
48
|
-
status_code: Optional[int] = None,
|
|
49
|
-
tags: Optional[list[Union[str, Enum]]] = None,
|
|
50
|
-
dependencies: Optional[Sequence[params.Depends]] = None,
|
|
51
|
-
summary: Optional[str] = None,
|
|
52
|
-
description: Optional[str] = None,
|
|
53
|
-
response_description: str = "Successful Response",
|
|
54
|
-
responses: Optional[dict[Union[int, str], dict[str, Any]]] = None,
|
|
55
|
-
deprecated: Optional[bool] = None,
|
|
56
|
-
name: Optional[str] = None,
|
|
57
|
-
methods: Optional[Union[set[str], list[str]]] = None,
|
|
58
|
-
operation_id: Optional[str] = None,
|
|
59
|
-
response_model_include: Optional[IncEx] = None,
|
|
60
|
-
response_model_exclude: Optional[IncEx] = None,
|
|
61
|
-
response_model_by_alias: bool = True,
|
|
62
|
-
response_model_exclude_unset: bool = False,
|
|
63
|
-
response_model_exclude_defaults: bool = False,
|
|
64
|
-
response_model_exclude_none: bool = False,
|
|
65
|
-
include_in_schema: bool = True,
|
|
66
|
-
response_class: Union[type[Response], DefaultPlaceholder] = Default(
|
|
67
|
-
JSONResponse
|
|
68
|
-
),
|
|
69
|
-
dependency_overrides_provider: Optional[Any] = None,
|
|
70
|
-
callbacks: Optional[list[BaseRoute]] = None,
|
|
71
|
-
openapi_extra: Optional[dict[str, Any]] = None,
|
|
72
|
-
generate_unique_id_function: Union[
|
|
73
|
-
Callable[["routing.APIRoute"], str], DefaultPlaceholder
|
|
74
|
-
] = Default(generate_unique_id),
|
|
75
|
-
strict_content_type: bool = Default(True),
|
|
76
|
-
) -> None:
|
|
77
|
-
self.path = path
|
|
78
|
-
self.endpoint = endpoint
|
|
79
|
-
if isinstance(response_model, DefaultPlaceholder):
|
|
80
|
-
return_annotation = get_typed_return_annotation(endpoint)
|
|
81
|
-
if lenient_issubclass(return_annotation, Response):
|
|
82
|
-
response_model = None
|
|
83
|
-
else:
|
|
84
|
-
response_model = return_annotation
|
|
85
|
-
self.response_model = response_model
|
|
86
|
-
self.summary = summary
|
|
87
|
-
self.response_description = response_description
|
|
88
|
-
self.deprecated = deprecated
|
|
89
|
-
self.operation_id = operation_id
|
|
90
|
-
self.response_model_include = response_model_include
|
|
91
|
-
self.response_model_exclude = response_model_exclude
|
|
92
|
-
self.response_model_by_alias = response_model_by_alias
|
|
93
|
-
self.response_model_exclude_unset = response_model_exclude_unset
|
|
94
|
-
self.response_model_exclude_defaults = response_model_exclude_defaults
|
|
95
|
-
self.response_model_exclude_none = response_model_exclude_none
|
|
96
|
-
self.include_in_schema = include_in_schema
|
|
97
|
-
self.response_class = response_class
|
|
98
|
-
self.dependency_overrides_provider = dependency_overrides_provider
|
|
99
|
-
self.callbacks = callbacks
|
|
100
|
-
self.openapi_extra = openapi_extra
|
|
101
|
-
self.generate_unique_id_function = generate_unique_id_function
|
|
102
|
-
self.strict_content_type = strict_content_type
|
|
103
|
-
self.tags = tags or []
|
|
104
|
-
self.responses = responses or {}
|
|
105
|
-
self.name = get_name(endpoint) if name is None else name
|
|
106
|
-
self.path_regex, self.path_format, self.param_convertors = compile_path(path)
|
|
107
|
-
if methods is None:
|
|
108
|
-
methods = ["GET"]
|
|
109
|
-
self.methods: set[str] = {method.upper() for method in methods}
|
|
110
|
-
if isinstance(generate_unique_id_function, DefaultPlaceholder):
|
|
111
|
-
current_generate_unique_id: Callable[[routing.APIRoute], str] = (
|
|
112
|
-
generate_unique_id_function.value
|
|
113
|
-
)
|
|
114
|
-
else:
|
|
115
|
-
current_generate_unique_id = generate_unique_id_function
|
|
116
|
-
self.unique_id = self.operation_id or current_generate_unique_id(self)
|
|
117
|
-
# normalize enums e.g. http.HTTPStatus
|
|
118
|
-
if isinstance(status_code, IntEnum):
|
|
119
|
-
status_code = int(status_code)
|
|
120
|
-
self.status_code = status_code
|
|
121
|
-
if self.response_model:
|
|
122
|
-
assert is_body_allowed_for_status_code(status_code), (
|
|
123
|
-
f"Status code {status_code} must not have a response body"
|
|
124
|
-
)
|
|
125
|
-
self.dependencies = list(dependencies or [])
|
|
126
|
-
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
|
|
127
|
-
self.description = self.description.split("\f")[0].strip()
|
|
128
|
-
|
|
129
|
-
assert callable(endpoint), "An endpoint must be a callable"
|
|
130
|
-
|
|
131
|
-
@cached_property
|
|
132
|
-
def dependant(self):
|
|
133
|
-
dependant = get_dependant(
|
|
134
|
-
path=self.path_format, call=self.endpoint, scope="function"
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
for depends in self.dependencies[::-1]:
|
|
138
|
-
dependant.dependencies.insert(
|
|
139
|
-
0,
|
|
140
|
-
get_parameterless_sub_dependant(depends=depends, path=self.path_format),
|
|
141
|
-
)
|
|
142
|
-
return dependant
|
|
143
|
-
|
|
144
|
-
@cached_property
|
|
145
|
-
def _flat_dependant(self):
|
|
146
|
-
return get_flat_dependant(self.dependant)
|
|
147
|
-
|
|
148
|
-
@cached_property
|
|
149
|
-
def _embed_body_fields(self):
|
|
150
|
-
return _should_embed_body_fields(self._flat_dependant.body_params)
|
|
151
|
-
|
|
152
|
-
@cached_property
|
|
153
|
-
def response_field(self):
|
|
154
|
-
if self.response_model:
|
|
155
|
-
response_name = "Response_" + self.unique_id
|
|
156
|
-
return create_model_field(
|
|
157
|
-
name=response_name,
|
|
158
|
-
type_=self.response_model,
|
|
159
|
-
mode="serialization",
|
|
160
|
-
)
|
|
161
|
-
else:
|
|
162
|
-
return None
|
|
163
|
-
|
|
164
|
-
@cached_property
|
|
165
|
-
def response_fields(self) -> dict[Union[int, str], ModelField]:
|
|
166
|
-
response_fields: dict[Union[int, str], ModelField] = {}
|
|
167
|
-
for additional_status_code, response in self.responses.items():
|
|
168
|
-
assert isinstance(response, dict), "An additional response must be a dict"
|
|
169
|
-
model = response.get("model")
|
|
170
|
-
if model:
|
|
171
|
-
assert is_body_allowed_for_status_code(additional_status_code), (
|
|
172
|
-
f"Status code {additional_status_code} must not have a response body"
|
|
173
|
-
)
|
|
174
|
-
response_name = f"Response_{additional_status_code}_{self.unique_id}"
|
|
175
|
-
response_field = create_model_field(
|
|
176
|
-
name=response_name, type_=model, mode="serialization"
|
|
177
|
-
)
|
|
178
|
-
response_fields[additional_status_code] = response_field
|
|
179
|
-
return response_fields
|
|
180
|
-
|
|
181
|
-
@cached_property
|
|
182
|
-
def body_field(self):
|
|
183
|
-
return get_body_field(
|
|
184
|
-
flat_dependant=self._flat_dependant,
|
|
185
|
-
name=self.unique_id,
|
|
186
|
-
embed_body_fields=self._embed_body_fields,
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
@cached_property
|
|
190
|
-
def app(self):
|
|
191
|
-
return routing.request_response(self.get_route_handler())
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
class DeferringAPIRouter(routing.APIRouter):
|
|
195
|
-
def __init__(
|
|
196
|
-
self,
|
|
197
|
-
*,
|
|
198
|
-
prefix: str = "",
|
|
199
|
-
tags: Optional[list[Union[str, Enum]]] = None,
|
|
200
|
-
dependencies: Optional[Sequence[params.Depends]] = None,
|
|
201
|
-
default_response_class: type[Response] = Default(JSONResponse),
|
|
202
|
-
responses: Optional[dict[Union[int, str], dict[str, Any]]] = None,
|
|
203
|
-
callbacks: Optional[list[BaseRoute]] = None,
|
|
204
|
-
routes: Optional[list[BaseRoute]] = None,
|
|
205
|
-
redirect_slashes: bool = True,
|
|
206
|
-
default: Optional[ASGIApp] = None,
|
|
207
|
-
dependency_overrides_provider: Optional[Any] = None,
|
|
208
|
-
route_class: type[routing.APIRoute] = DeferringAPIRoute,
|
|
209
|
-
on_startup: Optional[Sequence[Callable[[], Any]]] = None,
|
|
210
|
-
on_shutdown: Optional[Sequence[Callable[[], Any]]] = None,
|
|
211
|
-
lifespan: Optional[Lifespan[Any]] = None,
|
|
212
|
-
deprecated: Optional[bool] = None,
|
|
213
|
-
include_in_schema: bool = True,
|
|
214
|
-
generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
|
|
215
|
-
generate_unique_id
|
|
216
|
-
),
|
|
217
|
-
strict_content_type: bool = Default(True),
|
|
218
|
-
) -> None:
|
|
219
|
-
super().__init__(
|
|
220
|
-
prefix=prefix,
|
|
221
|
-
tags=tags,
|
|
222
|
-
dependencies=dependencies,
|
|
223
|
-
default_response_class=default_response_class,
|
|
224
|
-
responses=responses,
|
|
225
|
-
callbacks=callbacks,
|
|
226
|
-
routes=routes,
|
|
227
|
-
redirect_slashes=redirect_slashes,
|
|
228
|
-
default=default,
|
|
229
|
-
dependency_overrides_provider=dependency_overrides_provider,
|
|
230
|
-
route_class=route_class,
|
|
231
|
-
on_startup=on_startup,
|
|
232
|
-
on_shutdown=on_shutdown,
|
|
233
|
-
lifespan=lifespan,
|
|
234
|
-
deprecated=deprecated,
|
|
235
|
-
include_in_schema=include_in_schema,
|
|
236
|
-
generate_unique_id_function=generate_unique_id_function,
|
|
237
|
-
strict_content_type=strict_content_type,
|
|
238
|
-
)
|
|
File without changes
|
{fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.3.0}/src/fastapi_deferred_init/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|