fastapi-deferred-init 0.2.11__tar.gz → 0.2.12__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.2.11 → fastapi_deferred_init-0.2.12}/PKG-INFO +1 -1
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/pyproject.toml +3 -3
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/src/fastapi_deferred_init/routing.py +69 -24
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/tests/test_lib.py +17 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/LICENSE +0 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/README.md +0 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/src/fastapi_deferred_init/__init__.py +0 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/src/fastapi_deferred_init/py.typed +0 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/tests/__init__.py +0 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/tests/conftest.py +0 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/tests/data/__init__.py +0 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/tests/data/gen_code_ast.py +0 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/tests/data/template.py +0 -0
- {fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/tests/helpers.py +0 -0
|
@@ -18,7 +18,7 @@ classifiers = [
|
|
|
18
18
|
"Framework :: FastAPI",
|
|
19
19
|
]
|
|
20
20
|
dynamic = []
|
|
21
|
-
version = "0.2.
|
|
21
|
+
version = "0.2.12"
|
|
22
22
|
|
|
23
23
|
[project.license]
|
|
24
24
|
text = "MIT"
|
|
@@ -74,12 +74,12 @@ all = true
|
|
|
74
74
|
[dependency-groups]
|
|
75
75
|
dev = [
|
|
76
76
|
"pytest>=8.4.2",
|
|
77
|
-
"ruff>=0.15.
|
|
77
|
+
"ruff>=0.15.2",
|
|
78
78
|
"pre-commit>=4.3.0",
|
|
79
79
|
"mypy>=1.19.1",
|
|
80
80
|
"tuna>=0.5.11",
|
|
81
81
|
"httpx>=0.28.1",
|
|
82
82
|
"pytest-benchmark>=5.2.3",
|
|
83
83
|
"pip>=26.0.1",
|
|
84
|
-
"inline-snapshot>=0.
|
|
84
|
+
"inline-snapshot>=0.32.2",
|
|
85
85
|
]
|
{fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/src/fastapi_deferred_init/routing.py
RENAMED
|
@@ -15,7 +15,10 @@ from starlette.routing import Mount as Mount # noqa
|
|
|
15
15
|
from starlette.routing import compile_path, get_name
|
|
16
16
|
from starlette.types import ASGIApp, Lifespan
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
from fastapi.sse import (
|
|
19
|
+
EventSourceResponse,
|
|
20
|
+
ServerSentEvent,
|
|
21
|
+
)
|
|
19
22
|
from fastapi import params, routing # type:ignore
|
|
20
23
|
from fastapi._compat import ModelField, lenient_issubclass
|
|
21
24
|
from fastapi.datastructures import Default, DefaultPlaceholder
|
|
@@ -26,6 +29,7 @@ from fastapi.dependencies.utils import (
|
|
|
26
29
|
get_flat_dependant,
|
|
27
30
|
get_parameterless_sub_dependant,
|
|
28
31
|
get_typed_return_annotation,
|
|
32
|
+
get_stream_item_type,
|
|
29
33
|
)
|
|
30
34
|
from fastapi.responses import JSONResponse, Response
|
|
31
35
|
from fastapi.types import IncEx
|
|
@@ -45,43 +49,56 @@ class DeferringAPIRoute(routing.APIRoute):
|
|
|
45
49
|
endpoint: Callable[..., Any],
|
|
46
50
|
*,
|
|
47
51
|
response_model: Any = Default(None),
|
|
48
|
-
status_code:
|
|
49
|
-
tags:
|
|
50
|
-
dependencies:
|
|
51
|
-
summary:
|
|
52
|
-
description:
|
|
52
|
+
status_code: int | None = None,
|
|
53
|
+
tags: list[str | Enum] | None = None,
|
|
54
|
+
dependencies: Sequence[params.Depends] | None = None,
|
|
55
|
+
summary: str | None = None,
|
|
56
|
+
description: str | None = None,
|
|
53
57
|
response_description: str = "Successful Response",
|
|
54
|
-
responses:
|
|
55
|
-
deprecated:
|
|
56
|
-
name:
|
|
57
|
-
methods:
|
|
58
|
-
operation_id:
|
|
59
|
-
response_model_include:
|
|
60
|
-
response_model_exclude:
|
|
58
|
+
responses: dict[int | str, dict[str, Any]] | None = None,
|
|
59
|
+
deprecated: bool | None = None,
|
|
60
|
+
name: str | None = None,
|
|
61
|
+
methods: set[str] | list[str] | None = None,
|
|
62
|
+
operation_id: str | None = None,
|
|
63
|
+
response_model_include: IncEx | None = None,
|
|
64
|
+
response_model_exclude: IncEx | None = None,
|
|
61
65
|
response_model_by_alias: bool = True,
|
|
62
66
|
response_model_exclude_unset: bool = False,
|
|
63
67
|
response_model_exclude_defaults: bool = False,
|
|
64
68
|
response_model_exclude_none: bool = False,
|
|
65
69
|
include_in_schema: bool = True,
|
|
66
|
-
response_class:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Callable[["routing.APIRoute"], str], DefaultPlaceholder
|
|
74
|
-
] = Default(generate_unique_id),
|
|
75
|
-
strict_content_type: bool = Default(True),
|
|
70
|
+
response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse),
|
|
71
|
+
dependency_overrides_provider: Any | None = None,
|
|
72
|
+
callbacks: list[BaseRoute] | None = None,
|
|
73
|
+
openapi_extra: dict[str, Any] | None = None,
|
|
74
|
+
generate_unique_id_function: Callable[[routing.APIRoute], str]
|
|
75
|
+
| DefaultPlaceholder = Default(generate_unique_id),
|
|
76
|
+
strict_content_type: bool | DefaultPlaceholder = Default(True),
|
|
76
77
|
) -> None:
|
|
77
78
|
self.path = path
|
|
78
79
|
self.endpoint = endpoint
|
|
80
|
+
self.stream_item_type: Any | None = None
|
|
79
81
|
if isinstance(response_model, DefaultPlaceholder):
|
|
80
82
|
return_annotation = get_typed_return_annotation(endpoint)
|
|
81
83
|
if lenient_issubclass(return_annotation, Response):
|
|
82
84
|
response_model = None
|
|
83
85
|
else:
|
|
84
|
-
|
|
86
|
+
stream_item = get_stream_item_type(return_annotation)
|
|
87
|
+
if stream_item is not None:
|
|
88
|
+
# Extract item type for JSONL or SSE streaming when
|
|
89
|
+
# response_class is DefaultPlaceholder (JSONL) or
|
|
90
|
+
# EventSourceResponse (SSE).
|
|
91
|
+
# ServerSentEvent is excluded: it's a transport
|
|
92
|
+
# wrapper, not a data model, so it shouldn't feed
|
|
93
|
+
# into validation or OpenAPI schema generation.
|
|
94
|
+
if (
|
|
95
|
+
isinstance(response_class, DefaultPlaceholder)
|
|
96
|
+
or lenient_issubclass(response_class, EventSourceResponse)
|
|
97
|
+
) and not lenient_issubclass(stream_item, ServerSentEvent):
|
|
98
|
+
self.stream_item_type = stream_item
|
|
99
|
+
response_model = None
|
|
100
|
+
else:
|
|
101
|
+
response_model = return_annotation
|
|
85
102
|
self.response_model = response_model
|
|
86
103
|
self.summary = summary
|
|
87
104
|
self.response_description = response_description
|
|
@@ -122,8 +139,11 @@ class DeferringAPIRoute(routing.APIRoute):
|
|
|
122
139
|
assert is_body_allowed_for_status_code(status_code), (
|
|
123
140
|
f"Status code {status_code} must not have a response body"
|
|
124
141
|
)
|
|
142
|
+
|
|
125
143
|
self.dependencies = list(dependencies or [])
|
|
126
144
|
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
|
|
145
|
+
# if a "form feed" character (page break) is found in the description text,
|
|
146
|
+
# truncate description text to the content preceding the first "form feed"
|
|
127
147
|
self.description = self.description.split("\f")[0].strip()
|
|
128
148
|
|
|
129
149
|
assert callable(endpoint), "An endpoint must be a callable"
|
|
@@ -141,6 +161,20 @@ class DeferringAPIRoute(routing.APIRoute):
|
|
|
141
161
|
)
|
|
142
162
|
return dependant
|
|
143
163
|
|
|
164
|
+
@property
|
|
165
|
+
def is_generator(self) -> bool:
|
|
166
|
+
return self.dependant.is_async_gen_callable or self.dependant.is_gen_callable
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def is_sse_stream(self) -> bool:
|
|
170
|
+
return self.is_generator and lenient_issubclass(
|
|
171
|
+
self.response_class, EventSourceResponse
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def is_json_stream(self) -> bool:
|
|
176
|
+
return self.is_generator and isinstance(self.response_class, DefaultPlaceholder)
|
|
177
|
+
|
|
144
178
|
@cached_property
|
|
145
179
|
def _flat_dependant(self):
|
|
146
180
|
return get_flat_dependant(self.dependant)
|
|
@@ -161,6 +195,17 @@ class DeferringAPIRoute(routing.APIRoute):
|
|
|
161
195
|
else:
|
|
162
196
|
return None
|
|
163
197
|
|
|
198
|
+
@cached_property
|
|
199
|
+
def stream_item_field(self) -> ModelField | None:
|
|
200
|
+
if not self.stream_item_type:
|
|
201
|
+
return None
|
|
202
|
+
stream_item_name = "StreamItem_" + self.unique_id
|
|
203
|
+
return create_model_field(
|
|
204
|
+
name=stream_item_name,
|
|
205
|
+
type_=self.stream_item_type,
|
|
206
|
+
mode="serialization",
|
|
207
|
+
)
|
|
208
|
+
|
|
164
209
|
@cached_property
|
|
165
210
|
def response_fields(self) -> dict[Union[int, str], ModelField]:
|
|
166
211
|
response_fields: dict[Union[int, str], ModelField] = {}
|
|
@@ -89,3 +89,20 @@ def test_fastapi_openapi_schema(monkeypatch):
|
|
|
89
89
|
|
|
90
90
|
fastapi_test_openapi_schema()
|
|
91
91
|
fastapi_test_openapi_schema_additional()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_fastapi_sse(monkeypatch):
|
|
95
|
+
monkeypatch.setattr("fastapi.routing.APIRoute", DeferringAPIRoute)
|
|
96
|
+
monkeypatch.setattr("fastapi.routing.APIRouter", DeferringAPIRouter)
|
|
97
|
+
import_via_file_path(
|
|
98
|
+
"fastapi_clone.tests.test_sse",
|
|
99
|
+
"fastapi/tests/test_sse.py",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
from fastapi_clone.tests.test_sse import ( # type: ignore
|
|
103
|
+
test_raw_data_sent_without_json_encoding as fastapi_test_raw_data_sent_without_json_encoding,
|
|
104
|
+
client_fixture as fastapi_client_fixture,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
for client in fastapi_client_fixture.__wrapped__():
|
|
108
|
+
fastapi_test_raw_data_sent_without_json_encoding(client)
|
|
File without changes
|
|
File without changes
|
{fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/src/fastapi_deferred_init/__init__.py
RENAMED
|
File without changes
|
{fastapi_deferred_init-0.2.11 → fastapi_deferred_init-0.2.12}/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
|
|
File without changes
|