Skip to content

Commit ef806c3

Browse files
authored
Merge pull request #199 from python-ellar/minor_patches
Minor Fixes
2 parents ad4b0a4 + 43c7976 commit ef806c3

File tree

5 files changed

+40
-23
lines changed

5 files changed

+40
-23
lines changed

ellar/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Ellar - Python ASGI web framework for building fast, efficient, and scalable RESTful APIs and server-side applications."""
22

3-
__version__ = "0.7.2"
3+
__version__ = "0.7.3"

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: 29 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"
@@ -26,6 +23,17 @@ class FileResponseModelSchema(Serializer):
2623
content_disposition_type: ContentDispositionType = ContentDispositionType.attachment
2724

2825

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

@@ -79,11 +87,23 @@ def serialize(
7987

8088
class StreamingResponseModel(ResponseModel):
8189
response_type = StreamingResponse
90+
file_schema_type = StreamResponseModelSchema
8291

8392
def get_model_field(self) -> t.Optional[t.Union[ResponseModelField, t.Any]]:
8493
# We don't want any schema for this.
8594
return None
8695

96+
def serialize(
97+
self,
98+
response_obj: t.Any,
99+
serializer_filter: t.Optional[SerializerFilter] = None,
100+
) -> t.Union[t.List[t.Dict], t.Dict, t.Any, StreamResponseModelSchema]:
101+
if isinstance(response_obj, (t.AsyncGenerator, t.Generator)):
102+
response_obj = {"content": response_obj, "media_type": self.media_type}
103+
104+
value = self.file_schema_type.from_orm(response_obj)
105+
return value
106+
87107
def create_response(
88108
self, context: IExecutionContext, response_obj: t.Any, status_code: int
89109
) -> Response:
@@ -94,12 +114,12 @@ def create_response(
94114
response_args, headers = self.get_context_response(
95115
context=context, status_code=status_code
96116
)
97-
if not isinstance(response_obj, (t.AsyncGenerator, t.Generator)):
98-
raise StreamingResponseModelInvalidContent(
99-
"Content must typing.AsyncIterable OR typing.Iterable"
100-
)
117+
data = t.cast(StreamResponseModelSchema, self.serialize(response_obj))
101118

102119
response = self._response_type(
103-
**response_args, headers=headers, content=response_obj
120+
**response_args,
121+
headers=headers,
122+
content=data.content,
123+
media_type=data.media_type or self.media_type,
104124
)
105125
return response

tests/test_response/test_response_streaming.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33

44
import pytest
55
from ellar.common import Controller, ModuleRouter, file, get, serialize_object
6-
from ellar.common.responses.models import (
7-
StreamingResponseModel,
8-
StreamingResponseModelInvalidContent,
9-
)
6+
from ellar.common.responses.models import StreamingResponseModel
107
from ellar.openapi import OpenAPIDocumentBuilder
118
from ellar.testing import Test
129

@@ -40,7 +37,7 @@ def file_template():
4037
@streaming_mr.get("/index-decorator")
4138
@file(media_type="text/html", streaming=True)
4239
def render_template():
43-
return slow_numbers(1, 4)
40+
return {"content": slow_numbers(1, 4)}
4441

4542

4643
@Controller
@@ -57,16 +54,18 @@ def index2(self):
5754
@file(media_type="text/html", streaming=True)
5855
def index(self):
5956
"""detest its mvc and Looks for ellar/index"""
60-
return slow_numbers(1, 4)
57+
return {"content": slow_numbers(1, 4), "media_type": "text/html"}
6158

6259
@get("/index-invalid")
6360
@file(media_type="text/html", streaming=True)
6461
def index3(self):
6562
"""detest its mvc and Looks for ellar/index"""
6663
return {
67-
"path": f"{BASEDIR}/private/test.css",
68-
"filename": "file-test-css.css",
69-
"content_disposition_type": "whatever",
64+
"content": {
65+
"path": f"{BASEDIR}/private/test.css",
66+
"filename": "file-test-css.css",
67+
"content_disposition_type": "whatever",
68+
}
7069
}
7170

7271

@@ -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)