fastapi-deferred-init 0.3.2__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.2
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.140.7
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.140.7",
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.2"
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,29 +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.routing import _is_async_gen_callable, _is_gen_callable
14
- from fastapi import params, routing # type: ignore[attr-defined]
15
7
  from fastapi._compat import ModelField, lenient_issubclass
16
8
  from fastapi.datastructures import Default, DefaultPlaceholder
9
+ from fastapi.dependencies.models import Dependant
17
10
  from fastapi.dependencies.utils import (
18
- _should_embed_body_fields,
19
11
  _get_body_field,
12
+ _get_flat_body_params,
13
+ _should_embed_body_fields,
20
14
  get_dependant,
21
15
  get_parameterless_sub_dependant,
22
- get_typed_return_annotation,
23
16
  get_stream_item_type,
24
- _get_flat_body_params,
17
+ get_typed_return_annotation,
25
18
  )
26
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
+ )
27
25
  from fastapi.types import IncEx
28
26
  from fastapi.utils import (
29
27
  create_model_field,
@@ -31,6 +29,8 @@ from fastapi.utils import (
31
29
  is_body_allowed_for_status_code,
32
30
  )
33
31
 
32
+ from fastapi import params, routing # type: ignore[attr-defined]
33
+
34
34
 
35
35
  def _add_cache_attribute(
36
36
  instance: routing._APIRouteLike, name: str, func: Callable[[Any], Any]
@@ -54,7 +54,7 @@ def _populate_api_route_state(
54
54
  path: str,
55
55
  endpoint: Callable[..., Any],
56
56
  *,
57
- response_model: Any = Default(None),
57
+ response_model: Any = Default(None), # noqa: B008
58
58
  status_code: int | None = None,
59
59
  tags: list[str | Enum] | None = None,
60
60
  dependencies: Sequence[params.Depends] | None = None,
@@ -73,40 +73,20 @@ def _populate_api_route_state(
73
73
  response_model_exclude_defaults: bool = False,
74
74
  response_model_exclude_none: bool = False,
75
75
  include_in_schema: bool = True,
76
- response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse),
76
+ response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse), # noqa: B008
77
77
  dependency_overrides_provider: Any | None = None,
78
78
  callbacks: list[routing.BaseRoute] | None = None,
79
79
  openapi_extra: dict[str, Any] | None = None,
80
- generate_unique_id_function: Callable[[Any], str] | DefaultPlaceholder = Default(
80
+ generate_unique_id_function: Callable[[Any], str] | DefaultPlaceholder = Default( # noqa: B008
81
81
  generate_unique_id
82
82
  ),
83
- strict_content_type: bool | DefaultPlaceholder = Default(True),
83
+ strict_content_type: bool | DefaultPlaceholder = Default(True), # noqa: B008
84
+ stream_item_type: Any | None = None,
84
85
  ) -> None:
85
86
  route.path = path
86
87
  route.endpoint = endpoint
87
- route.stream_item_type = None
88
- if isinstance(response_model, DefaultPlaceholder):
89
- return_annotation = get_typed_return_annotation(endpoint)
90
- if lenient_issubclass(return_annotation, Response):
91
- response_model = None
92
- else:
93
- stream_item = get_stream_item_type(return_annotation)
94
- if stream_item is not None:
95
- # Extract item type for JSONL or SSE streaming when
96
- # response_class is DefaultPlaceholder (JSONL) or
97
- # EventSourceResponse (SSE).
98
- # ServerSentEvent is excluded: it's a transport
99
- # wrapper, not a data model, so it shouldn't feed
100
- # into validation or OpenAPI schema generation.
101
- if (
102
- isinstance(response_class, DefaultPlaceholder)
103
- or lenient_issubclass(response_class, EventSourceResponse)
104
- ) and not lenient_issubclass(stream_item, ServerSentEvent):
105
- route.stream_item_type = stream_item
106
- response_model = None
107
- else:
108
- response_model = return_annotation
109
- route.response_model = response_model
88
+ route.stream_item_type = stream_item_type
89
+
110
90
  route.summary = summary
111
91
  route.response_description = response_description
112
92
  route.deprecated = deprecated
@@ -144,13 +124,12 @@ def _populate_api_route_state(
144
124
  if isinstance(status_code, IntEnum):
145
125
  status_code = int(status_code)
146
126
  route.status_code = status_code
147
- if route.response_model:
148
- assert is_body_allowed_for_status_code(status_code), (
149
- f"Status code {status_code} must not have a response body"
150
- )
151
127
 
152
128
  def _response_field(self):
153
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
+ )
154
133
  response_name = "Response_" + self.unique_id
155
134
  return create_model_field(
156
135
  name=response_name,
@@ -180,8 +159,8 @@ def _populate_api_route_state(
180
159
  # truncate description text to the content preceding the first "form feed"
181
160
  route.description = route.description.split("\f")[0].strip()
182
161
 
183
- def _response_fields(self) -> dict[Union[int, str], ModelField]:
184
- response_fields: dict[Union[int, str], ModelField] = {}
162
+ def _response_fields(self) -> dict[int | str, ModelField]:
163
+ response_fields: dict[int | str, ModelField] = {}
185
164
  for additional_status_code, response in self.responses.items():
186
165
  assert isinstance(response, dict), "An additional response must be a dict"
187
166
  model = response.get("model")
@@ -254,6 +233,51 @@ def _populate_api_route_state(
254
233
 
255
234
  _add_cache_attribute(route, "is_json_stream", _is_json_stream)
256
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
+
257
281
 
258
282
  class DeferringAPIRoute(routing.APIRoute):
259
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__():