fastapi-deferred-init 0.3.1__tar.gz → 0.3.3__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.1 → fastapi_deferred_init-0.3.3}/PKG-INFO +2 -2
- {fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/pyproject.toml +2 -2
- {fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/src/fastapi_deferred_init/patch.py +1 -1
- {fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/src/fastapi_deferred_init/routing.py +79 -52
- {fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/tests/data/template.py +4 -4
- {fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/tests/test_lib.py +8 -7
- {fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/LICENSE +0 -0
- {fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/README.md +0 -0
- {fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/src/fastapi_deferred_init/__init__.py +0 -0
- {fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/src/fastapi_deferred_init/py.typed +0 -0
- {fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/tests/__init__.py +0 -0
- {fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/tests/conftest.py +0 -0
- {fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/tests/data/__init__.py +0 -0
- {fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/tests/data/gen_code_ast.py +0 -0
- {fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/tests/helpers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fastapi-deferred-init
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Faster FastAPI start-up time for Projects with many nested routers
|
|
5
5
|
Keywords: fastapi,speed,router,startup,optimization
|
|
6
6
|
Author-Email: Jan Vollmer <jan@vllmr.dev>
|
|
@@ -18,7 +18,7 @@ Classifier: Typing :: Typed
|
|
|
18
18
|
Project-URL: Source, https://github.com/jvllmr/fastapi-deferred-init
|
|
19
19
|
Project-URL: Changelog, https://github.com/jvllmr/fastapi-deferred-init/releases
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
|
-
Requires-Dist: fastapi>=0.
|
|
21
|
+
Requires-Dist: fastapi>=0.141.1
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
|
|
24
24
|
# fastapi-deferred-init
|
|
@@ -12,7 +12,7 @@ authors = [
|
|
|
12
12
|
{ name = "Jan Vollmer", email = "jan@vllmr.dev" },
|
|
13
13
|
]
|
|
14
14
|
dependencies = [
|
|
15
|
-
"fastapi>=0.
|
|
15
|
+
"fastapi>=0.141.1",
|
|
16
16
|
]
|
|
17
17
|
requires-python = ">=3.10"
|
|
18
18
|
readme = "README.md"
|
|
@@ -29,7 +29,7 @@ classifiers = [
|
|
|
29
29
|
"Typing :: Typed",
|
|
30
30
|
]
|
|
31
31
|
dynamic = []
|
|
32
|
-
version = "0.3.
|
|
32
|
+
version = "0.3.3"
|
|
33
33
|
|
|
34
34
|
[project.license]
|
|
35
35
|
text = "MIT"
|
{fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/src/fastapi_deferred_init/routing.py
RENAMED
|
@@ -1,28 +1,27 @@
|
|
|
1
|
-
from fastapi.dependencies.models import Dependant
|
|
2
1
|
import inspect
|
|
2
|
+
from collections.abc import Callable, Sequence
|
|
3
3
|
from enum import Enum, IntEnum
|
|
4
4
|
from functools import cached_property
|
|
5
|
-
from typing import Any,
|
|
6
|
-
from collections.abc import Sequence
|
|
7
|
-
|
|
5
|
+
from typing import Any, cast
|
|
8
6
|
|
|
9
|
-
from fastapi.sse import (
|
|
10
|
-
EventSourceResponse,
|
|
11
|
-
ServerSentEvent,
|
|
12
|
-
)
|
|
13
|
-
from fastapi import params, routing # type: ignore[attr-defined]
|
|
14
7
|
from fastapi._compat import ModelField, lenient_issubclass
|
|
15
8
|
from fastapi.datastructures import Default, DefaultPlaceholder
|
|
9
|
+
from fastapi.dependencies.models import Dependant
|
|
16
10
|
from fastapi.dependencies.utils import (
|
|
11
|
+
_get_body_field,
|
|
12
|
+
_get_flat_body_params,
|
|
17
13
|
_should_embed_body_fields,
|
|
18
|
-
get_body_field,
|
|
19
14
|
get_dependant,
|
|
20
|
-
get_flat_dependant,
|
|
21
15
|
get_parameterless_sub_dependant,
|
|
22
|
-
get_typed_return_annotation,
|
|
23
16
|
get_stream_item_type,
|
|
17
|
+
get_typed_return_annotation,
|
|
24
18
|
)
|
|
25
19
|
from fastapi.responses import JSONResponse, Response
|
|
20
|
+
from fastapi.routing import _is_async_gen_callable, _is_gen_callable
|
|
21
|
+
from fastapi.sse import (
|
|
22
|
+
EventSourceResponse,
|
|
23
|
+
ServerSentEvent,
|
|
24
|
+
)
|
|
26
25
|
from fastapi.types import IncEx
|
|
27
26
|
from fastapi.utils import (
|
|
28
27
|
create_model_field,
|
|
@@ -30,6 +29,8 @@ from fastapi.utils import (
|
|
|
30
29
|
is_body_allowed_for_status_code,
|
|
31
30
|
)
|
|
32
31
|
|
|
32
|
+
from fastapi import params, routing # type: ignore[attr-defined]
|
|
33
|
+
|
|
33
34
|
|
|
34
35
|
def _add_cache_attribute(
|
|
35
36
|
instance: routing._APIRouteLike, name: str, func: Callable[[Any], Any]
|
|
@@ -53,7 +54,7 @@ def _populate_api_route_state(
|
|
|
53
54
|
path: str,
|
|
54
55
|
endpoint: Callable[..., Any],
|
|
55
56
|
*,
|
|
56
|
-
response_model: Any = Default(None),
|
|
57
|
+
response_model: Any = Default(None), # noqa: B008
|
|
57
58
|
status_code: int | None = None,
|
|
58
59
|
tags: list[str | Enum] | None = None,
|
|
59
60
|
dependencies: Sequence[params.Depends] | None = None,
|
|
@@ -72,40 +73,20 @@ def _populate_api_route_state(
|
|
|
72
73
|
response_model_exclude_defaults: bool = False,
|
|
73
74
|
response_model_exclude_none: bool = False,
|
|
74
75
|
include_in_schema: bool = True,
|
|
75
|
-
response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse),
|
|
76
|
+
response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse), # noqa: B008
|
|
76
77
|
dependency_overrides_provider: Any | None = None,
|
|
77
78
|
callbacks: list[routing.BaseRoute] | None = None,
|
|
78
79
|
openapi_extra: dict[str, Any] | None = None,
|
|
79
|
-
generate_unique_id_function: Callable[[Any], str] | DefaultPlaceholder = Default(
|
|
80
|
+
generate_unique_id_function: Callable[[Any], str] | DefaultPlaceholder = Default( # noqa: B008
|
|
80
81
|
generate_unique_id
|
|
81
82
|
),
|
|
82
|
-
strict_content_type: bool | DefaultPlaceholder = Default(True),
|
|
83
|
+
strict_content_type: bool | DefaultPlaceholder = Default(True), # noqa: B008
|
|
84
|
+
stream_item_type: Any | None = None,
|
|
83
85
|
) -> None:
|
|
84
86
|
route.path = path
|
|
85
87
|
route.endpoint = endpoint
|
|
86
|
-
route.stream_item_type =
|
|
87
|
-
|
|
88
|
-
return_annotation = get_typed_return_annotation(endpoint)
|
|
89
|
-
if lenient_issubclass(return_annotation, Response):
|
|
90
|
-
response_model = None
|
|
91
|
-
else:
|
|
92
|
-
stream_item = get_stream_item_type(return_annotation)
|
|
93
|
-
if stream_item is not None:
|
|
94
|
-
# Extract item type for JSONL or SSE streaming when
|
|
95
|
-
# response_class is DefaultPlaceholder (JSONL) or
|
|
96
|
-
# EventSourceResponse (SSE).
|
|
97
|
-
# ServerSentEvent is excluded: it's a transport
|
|
98
|
-
# wrapper, not a data model, so it shouldn't feed
|
|
99
|
-
# into validation or OpenAPI schema generation.
|
|
100
|
-
if (
|
|
101
|
-
isinstance(response_class, DefaultPlaceholder)
|
|
102
|
-
or lenient_issubclass(response_class, EventSourceResponse)
|
|
103
|
-
) and not lenient_issubclass(stream_item, ServerSentEvent):
|
|
104
|
-
route.stream_item_type = stream_item
|
|
105
|
-
response_model = None
|
|
106
|
-
else:
|
|
107
|
-
response_model = return_annotation
|
|
108
|
-
route.response_model = response_model
|
|
88
|
+
route.stream_item_type = stream_item_type
|
|
89
|
+
|
|
109
90
|
route.summary = summary
|
|
110
91
|
route.response_description = response_description
|
|
111
92
|
route.deprecated = deprecated
|
|
@@ -143,13 +124,12 @@ def _populate_api_route_state(
|
|
|
143
124
|
if isinstance(status_code, IntEnum):
|
|
144
125
|
status_code = int(status_code)
|
|
145
126
|
route.status_code = status_code
|
|
146
|
-
if route.response_model:
|
|
147
|
-
assert is_body_allowed_for_status_code(status_code), (
|
|
148
|
-
f"Status code {status_code} must not have a response body"
|
|
149
|
-
)
|
|
150
127
|
|
|
151
128
|
def _response_field(self):
|
|
152
129
|
if self.response_model:
|
|
130
|
+
assert is_body_allowed_for_status_code(status_code), (
|
|
131
|
+
f"Status code {status_code} must not have a response body"
|
|
132
|
+
)
|
|
153
133
|
response_name = "Response_" + self.unique_id
|
|
154
134
|
return create_model_field(
|
|
155
135
|
name=response_name,
|
|
@@ -179,8 +159,8 @@ def _populate_api_route_state(
|
|
|
179
159
|
# truncate description text to the content preceding the first "form feed"
|
|
180
160
|
route.description = route.description.split("\f")[0].strip()
|
|
181
161
|
|
|
182
|
-
def _response_fields(self) -> dict[
|
|
183
|
-
response_fields: dict[
|
|
162
|
+
def _response_fields(self) -> dict[int | str, ModelField]:
|
|
163
|
+
response_fields: dict[int | str, ModelField] = {}
|
|
184
164
|
for additional_status_code, response in self.responses.items():
|
|
185
165
|
assert isinstance(response, dict), "An additional response must be a dict"
|
|
186
166
|
model = response.get("model")
|
|
@@ -214,20 +194,20 @@ def _populate_api_route_state(
|
|
|
214
194
|
|
|
215
195
|
_add_cache_attribute(route, "dependant", _get_dependant)
|
|
216
196
|
|
|
217
|
-
def
|
|
218
|
-
return
|
|
197
|
+
def _flat_body_params(self) -> list[ModelField]:
|
|
198
|
+
return _get_flat_body_params(self.dependant)
|
|
219
199
|
|
|
220
|
-
_add_cache_attribute(route, "
|
|
200
|
+
_add_cache_attribute(route, "_flat_body_params", _flat_body_params)
|
|
221
201
|
|
|
222
202
|
def _embed_body_fields(self):
|
|
223
|
-
return _should_embed_body_fields(self.
|
|
203
|
+
return _should_embed_body_fields(self._flat_body_params)
|
|
224
204
|
|
|
225
205
|
_add_cache_attribute(route, "_embed_body_fields", _embed_body_fields)
|
|
226
206
|
|
|
227
207
|
def _body_field(self):
|
|
228
|
-
return
|
|
229
|
-
flat_dependant=self._flat_dependant,
|
|
208
|
+
return _get_body_field(
|
|
230
209
|
name=self.unique_id,
|
|
210
|
+
body_params=self._flat_body_params,
|
|
231
211
|
embed_body_fields=self._embed_body_fields,
|
|
232
212
|
)
|
|
233
213
|
|
|
@@ -235,7 +215,9 @@ def _populate_api_route_state(
|
|
|
235
215
|
|
|
236
216
|
# Detect generator endpoints that should stream as JSONL or SSE
|
|
237
217
|
def _is_generator(self) -> bool:
|
|
238
|
-
return self.dependant.
|
|
218
|
+
return _is_async_gen_callable(self.dependant.call) or _is_gen_callable(
|
|
219
|
+
self.dependant.call
|
|
220
|
+
)
|
|
239
221
|
|
|
240
222
|
_add_cache_attribute(route, "is_generator", _is_generator)
|
|
241
223
|
|
|
@@ -251,6 +233,51 @@ def _populate_api_route_state(
|
|
|
251
233
|
|
|
252
234
|
_add_cache_attribute(route, "is_json_stream", _is_json_stream)
|
|
253
235
|
|
|
236
|
+
def _resolved_response_model_stream_item(self):
|
|
237
|
+
nonlocal response_model, stream_item_type
|
|
238
|
+
resolved_model = response_model
|
|
239
|
+
resolved_item_type = stream_item_type
|
|
240
|
+
if isinstance(response_model, DefaultPlaceholder):
|
|
241
|
+
return_annotation = get_typed_return_annotation(endpoint)
|
|
242
|
+
if lenient_issubclass(return_annotation, Response):
|
|
243
|
+
resolved_model = None
|
|
244
|
+
else:
|
|
245
|
+
stream_item = get_stream_item_type(return_annotation)
|
|
246
|
+
if stream_item is not None and self.is_generator:
|
|
247
|
+
# Extract item type for JSONL or SSE streaming for
|
|
248
|
+
# generator endpoints when response_class is
|
|
249
|
+
# DefaultPlaceholder (JSONL) or EventSourceResponse (SSE).
|
|
250
|
+
# ServerSentEvent is excluded: it's a transport
|
|
251
|
+
# wrapper, not a data model, so it shouldn't feed
|
|
252
|
+
# into validation or OpenAPI schema generation.
|
|
253
|
+
if (
|
|
254
|
+
isinstance(response_class, DefaultPlaceholder)
|
|
255
|
+
or lenient_issubclass(response_class, EventSourceResponse)
|
|
256
|
+
) and not lenient_issubclass(stream_item, ServerSentEvent):
|
|
257
|
+
resolved_item_type = stream_item
|
|
258
|
+
resolved_model = None
|
|
259
|
+
else:
|
|
260
|
+
resolved_model = return_annotation
|
|
261
|
+
|
|
262
|
+
return resolved_model, resolved_item_type
|
|
263
|
+
|
|
264
|
+
_add_cache_attribute(
|
|
265
|
+
route,
|
|
266
|
+
"_resolved_response_model_stream_item",
|
|
267
|
+
_resolved_response_model_stream_item,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
def _response_model(self):
|
|
271
|
+
|
|
272
|
+
return self._resolved_response_model_stream_item[0]
|
|
273
|
+
|
|
274
|
+
_add_cache_attribute(route, "response_model", _response_model)
|
|
275
|
+
|
|
276
|
+
def _stream_item_type(self):
|
|
277
|
+
return self._resolved_response_model_stream_item[1]
|
|
278
|
+
|
|
279
|
+
_add_cache_attribute(route, "stream_item_type", _stream_item_type)
|
|
280
|
+
|
|
254
281
|
|
|
255
282
|
class DeferringAPIRoute(routing.APIRoute):
|
|
256
283
|
def __init__(
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from fastapi import APIRouter, Depends
|
|
2
2
|
|
|
3
|
-
dependency0 = lambda: 1
|
|
3
|
+
dependency0 = lambda: 1
|
|
4
4
|
|
|
5
|
-
dependency = lambda: 0
|
|
5
|
+
dependency = lambda: 0
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def dependency1(sub_dependant: int = Depends(dependency0)):
|
|
@@ -13,7 +13,7 @@ router1 = APIRouter(prefix="/prefix1")
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@router1.get("/get1")
|
|
16
|
-
def get1(dependency=Depends(dependency)):
|
|
16
|
+
def get1(dependency=Depends(dependency)): # noqa: B008
|
|
17
17
|
return {"1": 1}
|
|
18
18
|
|
|
19
19
|
|
|
@@ -21,7 +21,7 @@ router2 = APIRouter(prefix="/prefix2")
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
@router2.get("/get2")
|
|
24
|
-
def get2(dependency=Depends(dependency)):
|
|
24
|
+
def get2(dependency=Depends(dependency)): # noqa: B008
|
|
25
25
|
return {"2": 2}
|
|
26
26
|
|
|
27
27
|
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
+
from fastapi.testclient import TestClient
|
|
4
5
|
from pydantic import BaseModel
|
|
5
6
|
|
|
6
|
-
from fastapi import FastAPI,
|
|
7
|
-
from fastapi import routing
|
|
8
|
-
from fastapi.testclient import TestClient
|
|
7
|
+
from fastapi import APIRouter, FastAPI, routing
|
|
9
8
|
from fastapi_deferred_init.routing import (
|
|
10
|
-
_populate_api_route_state,
|
|
11
9
|
DeferringAPIRoute,
|
|
12
10
|
DeferringAPIRouter,
|
|
11
|
+
_populate_api_route_state,
|
|
13
12
|
)
|
|
14
13
|
|
|
15
14
|
from .data.gen_code_ast import create_code
|
|
@@ -107,10 +106,10 @@ def test_fastapi_openapi_schema(monkeypatch):
|
|
|
107
106
|
"fastapi/tests/test_openapi_examples.py",
|
|
108
107
|
)
|
|
109
108
|
|
|
110
|
-
from fastapi_clone.tests.test_additional_properties import ( #
|
|
109
|
+
from fastapi_clone.tests.test_additional_properties import ( # type: ignore
|
|
111
110
|
test_openapi_schema as fastapi_test_openapi_schema_additional,
|
|
112
111
|
)
|
|
113
|
-
from fastapi_clone.tests.test_openapi_examples import ( #
|
|
112
|
+
from fastapi_clone.tests.test_openapi_examples import ( # type: ignore
|
|
114
113
|
test_openapi_schema as fastapi_test_openapi_schema,
|
|
115
114
|
)
|
|
116
115
|
|
|
@@ -125,9 +124,11 @@ def test_fastapi_sse(monkeypatch):
|
|
|
125
124
|
"fastapi/tests/test_sse.py",
|
|
126
125
|
)
|
|
127
126
|
|
|
127
|
+
from fastapi_clone.tests.test_sse import (
|
|
128
|
+
client_fixture as fastapi_client_fixture,
|
|
129
|
+
)
|
|
128
130
|
from fastapi_clone.tests.test_sse import ( # type: ignore
|
|
129
131
|
test_raw_data_sent_without_json_encoding as fastapi_test_raw_data_sent_without_json_encoding,
|
|
130
|
-
client_fixture as fastapi_client_fixture,
|
|
131
132
|
)
|
|
132
133
|
|
|
133
134
|
for client in fastapi_client_fixture.__wrapped__():
|
|
File without changes
|
|
File without changes
|
{fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/src/fastapi_deferred_init/__init__.py
RENAMED
|
File without changes
|
{fastapi_deferred_init-0.3.1 → fastapi_deferred_init-0.3.3}/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
|