Skip to content

Commit 34905e1

Browse files
committed
Added FileResponseModel and StreamingResponseModel for file and streaming response.
1 parent b3c8c57 commit 34905e1

File tree

4 files changed

+114
-7
lines changed

4 files changed

+114
-7
lines changed

ellar/core/response/model/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
RouteResponseExecution,
77
)
88
from .factory import create_response_model
9-
from .html import HTMLResponseModel
9+
from .file import (
10+
FileResponseModel,
11+
StreamingResponseModel,
12+
StreamingResponseModelInvalidContent,
13+
)
14+
from .html import HTMLResponseModel, HTMLResponseModelRuntimeError
1015
from .interface import IResponseModel
1116
from .json import EmptyAPIResponseModel, JSONResponseModel
1217
from .route import RouteResponseModel
@@ -18,9 +23,13 @@
1823
"ResponseModelField",
1924
"JSONResponseModel",
2025
"EmptyAPIResponseModel",
26+
"FileResponseModel",
27+
"StreamingResponseModel",
2128
"RouteResponseModel",
2229
"ResponseTypeDefinitionConverter",
2330
"IResponseModel",
2431
"HTMLResponseModel",
2532
"create_response_model",
33+
"HTMLResponseModelRuntimeError",
34+
"StreamingResponseModelInvalidContent",
2635
]

ellar/core/response/model/base.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,17 @@ class BaseResponseModel(IResponseModel, ABC):
6767
__slots__ = (
6868
"_response_type",
6969
"_media_type",
70-
"_description",
7170
"meta",
7271
"_model_field",
7372
)
7473

7574
response_type: t.Type[Response] = Response
7675
model_field_or_schema: t.Union[ResponseModelField, t.Any] = None
76+
default_description: str = "Successful Response"
7777

7878
def __init__(
7979
self,
80-
description: str = "Successful Response",
80+
description: str = None,
8181
model_field_or_schema: t.Union[ResponseModelField, t.Any] = None,
8282
**kwargs: t.Any,
8383
) -> None:
@@ -87,7 +87,7 @@ def __init__(
8787
self._media_type = str(
8888
kwargs.get("media_type") or self._response_type.media_type
8989
)
90-
self._description = description
90+
self.default_description = description or self.default_description
9191
self.meta = kwargs
9292
self._model_field = self._get_model_field_from_schema(model_field_or_schema)
9393

@@ -97,7 +97,7 @@ def media_type(self) -> str:
9797

9898
@property
9999
def description(self) -> str:
100-
return self._description
100+
return self.default_description
101101

102102
def _get_model_field_from_schema(
103103
self, model_field_or_schema: t.Optional[t.Union[ResponseModelField, t.Any]]
@@ -139,7 +139,9 @@ def create_response(
139139
self, context: IExecutionContext, response_obj: t.Any, status_code: int
140140
) -> Response:
141141
"""Cant create custom responses, Please override this function to create a custom response"""
142-
response_args, headers = self.get_context_response(context=context)
142+
response_args, headers = self.get_context_response(
143+
context=context, status_code=status_code
144+
)
143145
serializer_filter: t.Optional[SerializerFilter] = reflect.get_metadata(
144146
SERIALIZER_FILTER_KEY, context.get_handler()
145147
)

ellar/core/response/model/factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def create_response_model(
1212
response_model: t.Union[t.Type["ResponseModel"], t.Type[T]],
1313
*,
1414
response_type: t.Type[Response] = None,
15-
description: str = "Successful Response",
15+
description: str = None,
1616
model_field_or_schema: t.Union[t.Type["ResponseModelField"], t.Any] = None,
1717
**kwargs: t.Any,
1818
) -> t.Union["ResponseModel", T]:

ellar/core/response/model/file.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import typing as t
2+
from enum import Enum
3+
4+
from ellar.core.context import IExecutionContext
5+
from ellar.serializer import Serializer, SerializerFilter, serialize_object
6+
7+
from ..response_types import FileResponse, Response, StreamingResponse
8+
from .base import ResponseModel, ResponseModelField
9+
10+
11+
class StreamingResponseModelInvalidContent(RuntimeError):
12+
pass
13+
14+
15+
class ContentDispositionType(str, Enum):
16+
inline = "inline"
17+
attachment = "attachment"
18+
19+
20+
class FileResponseModelSchema(Serializer):
21+
path: str
22+
media_type: t.Optional[str] = None
23+
filename: t.Optional[str] = None
24+
method: t.Optional[str] = None
25+
content_disposition_type: ContentDispositionType = ContentDispositionType.attachment
26+
27+
28+
class FileResponseModel(ResponseModel):
29+
__slots__ = ("_file_init_schema",)
30+
31+
response_type: t.Type[Response] = FileResponse
32+
file_init_schema_type = FileResponseModelSchema
33+
34+
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
35+
super(FileResponseModel, self).__init__(*args, **kwargs)
36+
self._file_init_schema = self._get_model_field_from_schema(
37+
self.file_init_schema_type
38+
)
39+
40+
def create_response(
41+
self, context: IExecutionContext, response_obj: t.Any, status_code: int
42+
) -> Response:
43+
response_args, headers = self.get_context_response(
44+
context=context, status_code=status_code
45+
)
46+
47+
init_kwargs = serialize_object(self.serialize(response_obj))
48+
response_args.update(init_kwargs)
49+
50+
response = self._response_type(
51+
**response_args,
52+
headers=headers,
53+
)
54+
return response
55+
56+
def get_init_kwargs_schema(self) -> ResponseModelField:
57+
assert self._file_init_schema
58+
return self._file_init_schema
59+
60+
def get_model_field(self) -> t.Optional[t.Union[ResponseModelField, t.Any]]:
61+
# We don't want any schema for this.
62+
return None
63+
64+
def serialize(
65+
self,
66+
response_obj: t.Any,
67+
serializer_filter: t.Optional[SerializerFilter] = None,
68+
) -> t.Union[t.List[t.Dict], t.Dict, t.Any]:
69+
_response_model_field = self.get_init_kwargs_schema()
70+
return _response_model_field.serialize(
71+
response_obj, serializer_filter=serializer_filter
72+
)
73+
74+
75+
class StreamingResponseModel(ResponseModel):
76+
response_type = StreamingResponse
77+
78+
def get_model_field(self) -> t.Optional[t.Union[ResponseModelField, t.Any]]:
79+
# We don't want any schema for this.
80+
return None
81+
82+
def create_response(
83+
self, context: IExecutionContext, response_obj: t.Any, status_code: int
84+
) -> Response:
85+
response_args, headers = self.get_context_response(
86+
context=context, status_code=status_code
87+
)
88+
if not isinstance(response_obj, (t.AsyncGenerator, t.Generator)):
89+
raise StreamingResponseModelInvalidContent(
90+
"Content must typing.AsyncIterable OR typing.Iterable"
91+
)
92+
93+
response = self._response_type(
94+
**response_args, headers=headers, content=response_obj
95+
)
96+
return response

0 commit comments

Comments
 (0)