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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastapi-deferred-init
3
- Version: 0.3.1
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.139.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.139.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.1"
32
+ version = "0.3.3"
33
33
 
34
34
  [project.license]
35
35
  text = "MIT"
@@ -1,4 +1,4 @@
1
- from .routing import _populate_api_route_state, DeferringAPIRoute, DeferringAPIRouter
1
+ from .routing import DeferringAPIRoute, DeferringAPIRouter, _populate_api_route_state
2
2
 
3
3
 
4
4
  def apply_patch() -> None:
@@ -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, Callable, Union, cast
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 = None
87
- if isinstance(response_model, DefaultPlaceholder):
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[Union[int, str], ModelField]:
183
- response_fields: dict[Union[int, str], ModelField] = {}
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 _get_flat_dependant(self) -> Dependant:
218
- return get_flat_dependant(self.dependant)
197
+ def _flat_body_params(self) -> list[ModelField]:
198
+ return _get_flat_body_params(self.dependant)
219
199
 
220
- _add_cache_attribute(route, "_flat_dependant", _get_flat_dependant)
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._flat_dependant.body_params)
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 get_body_field(
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.is_async_gen_callable or self.dependant.is_gen_callable
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 # noqa: E731
3
+ dependency0 = lambda: 1
4
4
 
5
- dependency = lambda: 0 # noqa: E731
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, APIRouter
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 ( # noqa # type: ignore
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 ( # noqa # type: ignore
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__():