Skip to content

Commit 34943d3

Browse files
committed
fixed inject type any and streaming media_type missing
1 parent ad4b0a4 commit 34943d3

File tree

4 files changed

+37
-19
lines changed

4 files changed

+37
-19
lines changed

ellar/common/params/decorators/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def P(
111111
Path = Annotated[T, param_functions.Path()]
112112
Query = Annotated[T, param_functions.Query()]
113113
WsBody = Annotated[T, param_functions.WsBody()]
114-
Inject = InjectShortcut()
114+
Inject = Annotated[T, InjectShortcut()]
115115

116116
else:
117117
Body = _ParamShortcut(param_functions.Body)

ellar/common/responses/models/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from .file import (
44
FileResponseModel,
55
StreamingResponseModel,
6-
StreamingResponseModelInvalidContent,
76
)
87
from .helper import create_response_model
98
from .html import HTMLResponseModel, HTMLResponseModelRuntimeError
@@ -23,7 +22,6 @@
2322
"HTMLResponseModel",
2423
"create_response_model",
2524
"HTMLResponseModelRuntimeError",
26-
"StreamingResponseModelInvalidContent",
2725
"RouteResponseExecution",
2826
"ResponseTypeDefinitionConverter",
2927
]

ellar/common/responses/models/file.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,12 @@
44
from ellar.common.interfaces import IExecutionContext
55
from ellar.common.logging import request_logger
66
from ellar.common.serializer import Serializer, SerializerFilter
7+
from ellar.pydantic import field_validator
78

89
from ..response_types import FileResponse, Response, StreamingResponse
910
from .base import ResponseModel, ResponseModelField
1011

1112

12-
class StreamingResponseModelInvalidContent(RuntimeError):
13-
pass
14-
15-
1613
class ContentDispositionType(str, Enum):
1714
inline = "inline"
1815
attachment = "attachment"
@@ -25,6 +22,19 @@ class FileResponseModelSchema(Serializer):
2522
method: t.Optional[str] = None
2623
content_disposition_type: ContentDispositionType = ContentDispositionType.attachment
2724

25+
class StreamResponseModelSchema(Serializer):
26+
media_type: t.Optional[str] = None
27+
content: t.Any
28+
29+
@field_validator('content', mode='before')
30+
def pre_validate_content(cls, value: t.Dict) -> t.Any:
31+
if not isinstance(value, (t.AsyncGenerator, t.Generator)):
32+
raise ValueError(
33+
"Content must typing.AsyncIterable OR typing.Iterable"
34+
)
35+
return value
36+
37+
2838

2939
class FileResponseModel(ResponseModel):
3040
__slots__ = ("_file_init_schema",)
@@ -79,11 +89,23 @@ def serialize(
7989

8090
class StreamingResponseModel(ResponseModel):
8191
response_type = StreamingResponse
92+
file_schema_type = StreamResponseModelSchema
8293

8394
def get_model_field(self) -> t.Optional[t.Union[ResponseModelField, t.Any]]:
8495
# We don't want any schema for this.
8596
return None
8697

98+
def serialize(
99+
self,
100+
response_obj: t.Any,
101+
serializer_filter: t.Optional[SerializerFilter] = None,
102+
) -> t.Union[t.List[t.Dict], t.Dict, t.Any, StreamResponseModelSchema]:
103+
if isinstance(response_obj, (t.AsyncGenerator, t.Generator)):
104+
response_obj = {"content": response_obj, "media_type": self.media_type}
105+
106+
value = self.file_schema_type.from_orm(response_obj)
107+
return value
108+
87109
def create_response(
88110
self, context: IExecutionContext, response_obj: t.Any, status_code: int
89111
) -> Response:
@@ -94,12 +116,11 @@ def create_response(
94116
response_args, headers = self.get_context_response(
95117
context=context, status_code=status_code
96118
)
97-
if not isinstance(response_obj, (t.AsyncGenerator, t.Generator)):
98-
raise StreamingResponseModelInvalidContent(
99-
"Content must typing.AsyncIterable OR typing.Iterable"
100-
)
119+
data = self.serialize(response_obj)
101120

102121
response = self._response_type(
103-
**response_args, headers=headers, content=response_obj
122+
**response_args,
123+
headers=headers, content=data.content,
124+
media_type=data.media_type or self.media_type
104125
)
105126
return response

tests/test_response/test_response_streaming.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
import pytest
55
from ellar.common import Controller, ModuleRouter, file, get, serialize_object
66
from ellar.common.responses.models import (
7-
StreamingResponseModel,
8-
StreamingResponseModelInvalidContent,
7+
StreamingResponseModel
98
)
109
from ellar.openapi import OpenAPIDocumentBuilder
1110
from ellar.testing import Test
@@ -40,7 +39,7 @@ def file_template():
4039
@streaming_mr.get("/index-decorator")
4140
@file(media_type="text/html", streaming=True)
4241
def render_template():
43-
return slow_numbers(1, 4)
42+
return {"content": slow_numbers(1, 4)}
4443

4544

4645
@Controller
@@ -57,17 +56,17 @@ def index2(self):
5756
@file(media_type="text/html", streaming=True)
5857
def index(self):
5958
"""detest its mvc and Looks for ellar/index"""
60-
return slow_numbers(1, 4)
59+
return {"content": slow_numbers(1, 4), "media_type": "text/html"}
6160

6261
@get("/index-invalid")
6362
@file(media_type="text/html", streaming=True)
6463
def index3(self):
6564
"""detest its mvc and Looks for ellar/index"""
66-
return {
65+
return {"content": {
6766
"path": f"{BASEDIR}/private/test.css",
6867
"filename": "file-test-css.css",
6968
"content_disposition_type": "whatever",
70-
}
69+
}}
7170

7271

7372
test_module = Test.create_test_module(
@@ -115,7 +114,7 @@ def test_response_schema(path):
115114
def test_invalid_parameter_returned():
116115
client = test_module.get_test_client()
117116
with pytest.raises(
118-
StreamingResponseModelInvalidContent,
117+
ValueError,
119118
match="Content must typing.AsyncIterable OR typing.Iterable",
120119
):
121120
client.get("/ellar/index-invalid")

0 commit comments

Comments
 (0)