Skip to content

Commit

Permalink
pass Accept charset to the response serializer
Browse files Browse the repository at this point in the history
  • Loading branch information
Rollmops committed Mar 20, 2020
1 parent 4487ebd commit 949c9ac
Show file tree
Hide file tree
Showing 10 changed files with 73 additions and 47 deletions.
@@ -1,9 +1,9 @@
from typing import List, Tuple, Union

from restit.common import guess_text_content_subtype_bytes, get_default_encoding
from restit.common import guess_text_content_subtype_bytes
from restit.internal.response_status_parameter import ResponseStatusParameter
from restit.internal.schema_or_field_deserializer import SchemaOrFieldDeserializer
from restit.response_serializer import ResponseSerializer
from restit.response_serializer import ResponseSerializer, CanHandleResultType


class DefaultBytesTextResponseSerializer(ResponseSerializer):
Expand All @@ -15,16 +15,19 @@ def get_response_data_type(self) -> type:
return bytes

def validate_and_serialize(
self, response_input: bytes, response_status_parameter: Union[None, ResponseStatusParameter]
self,
response_input: bytes,
response_status_parameter: Union[None, ResponseStatusParameter],
can_handle_result: CanHandleResultType
) -> Tuple[bytes, str]:
content_type = guess_text_content_subtype_bytes(response_input)
schema_or_field = self.find_schema(content_type, response_status_parameter)
if schema_or_field:
response_input = bytes(
str(SchemaOrFieldDeserializer.deserialize(
response_input.decode(get_default_encoding()), schema_or_field
response_input.decode(can_handle_result.mime_type.charset), schema_or_field
)),
encoding=get_default_encoding()
encoding=can_handle_result.mime_type.charset
)

return response_input, content_type
@@ -1,9 +1,8 @@
import json
from typing import List, Tuple, Union

from restit.common import get_default_encoding
from restit.internal.response_status_parameter import ResponseStatusParameter
from restit.response_serializer import ResponseSerializer
from restit.response_serializer import ResponseSerializer, CanHandleResultType


class DefaultDictJsonResponseSerializer(ResponseSerializer):
Expand All @@ -14,12 +13,15 @@ def get_response_data_type(self) -> type:
return dict

def validate_and_serialize(
self, response_input: dict, response_status_parameter: Union[None, ResponseStatusParameter]
self,
response_input: dict,
response_status_parameter: Union[None, ResponseStatusParameter],
can_handle_result: CanHandleResultType
) -> Tuple[bytes, str]:
content_type = "application/json"
schema = ResponseSerializer.find_schema(content_type, response_status_parameter)
if schema:
json_string = schema.dumps(response_input)
else:
json_string = json.dumps(response_input)
return json_string.encode(encoding=get_default_encoding()), content_type
return json_string.encode(encoding=can_handle_result.mime_type.charset), content_type
Expand Up @@ -3,7 +3,7 @@
from restit.internal.default_response_serializer.default_dict_json_response_serializer import \
DefaultDictJsonResponseSerializer
from restit.internal.response_status_parameter import ResponseStatusParameter
from restit.response_serializer import ResponseSerializer
from restit.response_serializer import ResponseSerializer, CanHandleResultType


class DefaultDictTextResponseSerializer(ResponseSerializer):
Expand All @@ -15,10 +15,13 @@ def get_response_data_type(self) -> type:
return dict

def validate_and_serialize(
self, response_input: dict, response_status_parameter: Union[None, ResponseStatusParameter]
self,
response_input: dict,
response_status_parameter: Union[None, ResponseStatusParameter],
can_handle_result: CanHandleResultType
) -> Tuple[bytes, str]:
response_in_bytes, _ = DefaultDictJsonResponseSerializer().validate_and_serialize(
response_input, response_status_parameter
response_input, response_status_parameter, can_handle_result
)

return response_in_bytes, "text/plain"
@@ -1,9 +1,9 @@
from typing import List, Tuple, Union

from restit.common import get_default_encoding, guess_text_content_subtype_string
from restit.common import guess_text_content_subtype_string
from restit.internal.response_status_parameter import ResponseStatusParameter
from restit.internal.schema_or_field_deserializer import SchemaOrFieldDeserializer
from restit.response_serializer import ResponseSerializer
from restit.response_serializer import ResponseSerializer, CanHandleResultType


class DefaultStrTextResponseSerializer(ResponseSerializer):
Expand All @@ -14,14 +14,16 @@ def get_response_data_type(self) -> type:
return str

def validate_and_serialize(
self, response_input: str, response_status_parameter: Union[None, ResponseStatusParameter]
self, response_input: str,
response_status_parameter: Union[None, ResponseStatusParameter],
can_handle_result: CanHandleResultType
) -> Tuple[bytes, str]:
content_type = guess_text_content_subtype_string(response_input)
schema_or_field = self.find_schema(content_type, response_status_parameter)
if schema_or_field:
response_input = str(SchemaOrFieldDeserializer.deserialize(response_input, schema_or_field))

return response_input.encode(encoding=get_default_encoding()), content_type
return response_input.encode(encoding=can_handle_result.mime_type.charset), content_type

class SchemaNotSupportedForStringResponseBody(Exception):
pass
Expand Up @@ -2,6 +2,7 @@

from restit.internal.default_response_serializer.default_dict_json_response_serializer import \
DefaultDictJsonResponseSerializer
from restit.internal.mime_type import MIMEType
from restit.internal.response_status_parameter import ResponseStatusParameter
from restit.response_serializer import ResponseSerializer

Expand All @@ -14,10 +15,13 @@ def get_response_data_type(self) -> type:
return dict

def validate_and_serialize(
self, response_input: dict, response_status_parameter: Union[None, ResponseStatusParameter]
self,
response_input: dict,
response_status_parameter: Union[None, ResponseStatusParameter],
can_handle_result: MIMEType
) -> Tuple[bytes, str]:
response_in_bytes, _ = DefaultDictJsonResponseSerializer().validate_and_serialize(
response_input, response_status_parameter
response_input, response_status_parameter, can_handle_result
)

return response_in_bytes, "application/json"
@@ -1,10 +1,10 @@
from typing import List, Tuple, Union

from restit.common import get_default_encoding, guess_text_content_subtype_string
from restit.common import guess_text_content_subtype_string
from restit.internal.default_response_serializer.default_str_text_response_serializer import \
DefaultStrTextResponseSerializer
from restit.internal.response_status_parameter import ResponseStatusParameter
from restit.response_serializer import ResponseSerializer
from restit.response_serializer import ResponseSerializer, CanHandleResultType


class StringFallbackResponseSerializer(ResponseSerializer):
Expand All @@ -15,10 +15,13 @@ def get_response_data_type(self) -> type:
return str

def validate_and_serialize(
self, response_input: str, response_status_parameter: Union[None, ResponseStatusParameter]
self,
response_input: str,
response_status_parameter: Union[None, ResponseStatusParameter],
can_handle_result: CanHandleResultType
) -> Tuple[bytes, str]:
content_type = guess_text_content_subtype_string(response_input)
response_input_bytes = response_input.encode(encoding=get_default_encoding())
response_input_bytes = response_input.encode(encoding=can_handle_result.mime_type.charset)
if self.find_schema(content_type, response_status_parameter):
raise DefaultStrTextResponseSerializer.SchemaNotSupportedForStringResponseBody()

Expand Down
23 changes: 12 additions & 11 deletions restit/internal/response_serializer_service.py
@@ -1,4 +1,4 @@
from typing import List, Union
from typing import List, Union, Tuple

from restit.exception import NotAcceptable
from restit.internal.default_response_serializer.default_bytes_text_response_serializer import \
Expand All @@ -15,7 +15,7 @@
from restit.internal.http_accept import HttpAccept
from restit.internal.response_status_parameter import ResponseStatusParameter
from restit.response import Response
from restit.response_serializer import ResponseSerializer
from restit.response_serializer import ResponseSerializer, CanHandleResultType

_DEFAULT_RESPONSE_SERIALIZER = [
DefaultDictJsonResponseSerializer(),
Expand Down Expand Up @@ -43,14 +43,15 @@ def restore_default_response_serializer():
ResponseSerializerService._RESPONSE_SERIALIZER = _DEFAULT_RESPONSE_SERIALIZER.copy()

@staticmethod
def get_matching_response_serializer_for_media_type(http_accept: HttpAccept) -> List[ResponseSerializer]:
matching_response_serializer = [
response_serializer
for response_serializer in ResponseSerializerService._RESPONSE_SERIALIZER
if response_serializer.can_handle_incoming_media_type(http_accept)
]
def get_matching_response_serializer_for_media_type(
http_accept: HttpAccept) -> List[Tuple[ResponseSerializer, CanHandleResultType]]:
response_serializer_matches = []
for response_serializer in ResponseSerializerService._RESPONSE_SERIALIZER:
can_handle_result = response_serializer.can_handle_incoming_media_type(http_accept)
if can_handle_result is not None:
response_serializer_matches.append((response_serializer, can_handle_result))

return sorted(matching_response_serializer, key=lambda s: s.priority, reverse=True)
return sorted(response_serializer_matches, key=lambda c: c[1].mime_type.quality, reverse=True)

@staticmethod
def validate_and_serialize_response_body(
Expand All @@ -63,10 +64,10 @@ def validate_and_serialize_response_body(
if not matching_response_serializer_list:
raise NotAcceptable()

for response_serializer in matching_response_serializer_list:
for response_serializer, can_handle_result in matching_response_serializer_list:
if isinstance(response.response_body_input, response_serializer.get_response_data_type()):
response.content, content_type = response_serializer.validate_and_serialize(
response.response_body_input, response_status_parameter
response.response_body_input, response_status_parameter, can_handle_result
)
# Todo encoding from incoming accept charset
response.text = response.content.decode()
Expand Down
22 changes: 13 additions & 9 deletions restit/response_serializer.py
@@ -1,22 +1,23 @@
from typing import Any, List, Tuple, Union
from typing import Any, List, Tuple, Union, NamedTuple

from marshmallow import Schema
from marshmallow.fields import Field

from restit.internal.http_accept import HttpAccept
from restit.internal.mime_type import MIMEType
from restit.internal.response_status_parameter import ResponseStatusParameter


class ResponseSerializer:
def __init__(self):
self.priority = 0
class CanHandleResultType(NamedTuple):
content_type: str
mime_type: MIMEType


def can_handle_incoming_media_type(self, http_accept: HttpAccept) -> bool:
class ResponseSerializer:
def can_handle_incoming_media_type(self, http_accept: HttpAccept) -> Union[None, CanHandleResultType]:
best_match = http_accept.get_best_match(self.get_media_type_strings())
if best_match is not None:
self.priority = best_match[1].quality
return True
return False
return CanHandleResultType(best_match[0], best_match[1])

def get_media_type_strings(self) -> List[str]:
raise NotImplemented()
Expand All @@ -25,7 +26,10 @@ def get_response_data_type(self) -> type:
raise NotImplemented()

def validate_and_serialize(
self, response_input: Any, response_status_parameter: Union[None, ResponseStatusParameter]
self,
response_input: Any,
response_status_parameter: Union[None, ResponseStatusParameter],
can_handle_result: CanHandleResultType
) -> Tuple[bytes, str]:
"""Returns a tuple of the serialized bytes and the content type"""
raise NotImplemented()
Expand Down
2 changes: 1 addition & 1 deletion test/restit/open_api/open_api_spec_test.py
Expand Up @@ -17,7 +17,7 @@
class MyRequestBodySchema(Schema):
"""A bird with a flight speed exceeding that of an unladen swallow.
"""
field1 = fields.String(required=True, validate=[Regexp("\w+")])
field1 = fields.String(required=True, validate=[Regexp(r"\w+")])
field1.__doc__ = "Description for field1"
field2 = fields.Integer(validate=[Range(min=1, max=100)])

Expand Down
12 changes: 8 additions & 4 deletions test/restit/response_serializer_test.py
Expand Up @@ -16,7 +16,7 @@
from restit.internal.response_serializer_service import ResponseSerializerService
from restit.internal.response_status_parameter import ResponseStatusParameter
from restit.response import Response
from restit.response_serializer import ResponseSerializer
from restit.response_serializer import ResponseSerializer, CanHandleResultType


class ResponseSerializerTestCase(unittest.TestCase):
Expand Down Expand Up @@ -64,14 +64,18 @@ def get_media_type_strings(self) -> List[str]:
return ["my/type"]

def validate_and_serialize(
self, response_input: Any, response_status_parameter: Union[None, ResponseStatusParameter]
self,
response_input: Any,
response_status_parameter: Union[None, ResponseStatusParameter],
can_handle_result: CanHandleResultType
) -> Tuple[bytes, str]:
return "".join(reversed(response_input)).encode(), "my/type"
assert can_handle_result.mime_type.charset == "ascii"
return "".join(reversed(response_input)).encode(encoding=can_handle_result.mime_type.charset), "my/type"

ResponseSerializerService.register_response_serializer(MyResponseSerializer())
response = Response("Test")
ResponseSerializerService.validate_and_serialize_response_body(
response, HttpAccept.from_accept_string("my/type")
response, HttpAccept.from_accept_string("my/type; charset=ascii")
)

self.assertEqual(b'tseT', response.content)
Expand Down

0 comments on commit 949c9ac

Please sign in to comment.