Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/connexion/decorators/response.py: 29%
101 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:12 +0000
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:12 +0000
1import abc
2import collections.abc
3import functools
4import logging
5import types
6import typing as t
7from enum import Enum
9from connexion.context import operation
10from connexion.datastructures import NoContent
11from connexion.exceptions import NonConformingResponseHeaders
12from connexion.frameworks.abstract import Framework
13from connexion.lifecycle import ConnexionResponse
14from connexion.utils import is_json_mimetype
16logger = logging.getLogger(__name__)
19class BaseResponseDecorator:
20 def __init__(self, *, framework: t.Type[Framework], jsonifier):
21 self.framework = framework
22 self.jsonifier = jsonifier
24 @abc.abstractmethod
25 def __call__(self, function: t.Callable) -> t.Callable:
26 raise NotImplementedError
28 def build_framework_response(self, handler_response):
29 data, status_code, headers = self._unpack_handler_response(handler_response)
30 content_type = self._deduct_content_type(data, headers)
31 if not self.framework.is_framework_response(data):
32 data, status_code = self._prepare_body_and_status_code(
33 data, status_code=status_code, mimetype=content_type
34 )
35 return self.framework.build_response(
36 data, content_type=content_type, status_code=status_code, headers=headers
37 )
39 @staticmethod
40 def _deduct_content_type(data: t.Any, headers: dict) -> str:
41 """Deduct the response content type from the returned data, headers and operation spec.
43 :param data: Response data
44 :param headers: Headers returned by the handler.
46 :return: Deducted content type
48 :raises: NonConformingResponseHeaders if content type cannot be deducted.
49 """
50 content_type = headers.get("Content-Type")
52 # TODO: don't default
53 produces = list(set(operation.produces))
54 if data is not None and not produces:
55 produces = ["application/json"]
57 if content_type:
58 if content_type not in produces:
59 raise NonConformingResponseHeaders(
60 f"Returned content type ({content_type}) is not defined in operation spec "
61 f"({operation.produces})."
62 )
63 else:
64 if not produces:
65 # Produces can be empty/ for empty responses
66 pass
67 elif len(produces) == 1:
68 content_type = produces[0]
69 elif isinstance(data, str) and "text/plain" in produces:
70 content_type = "text/plain"
71 elif (
72 isinstance(data, bytes)
73 or isinstance(data, (types.GeneratorType, collections.abc.Iterator))
74 ) and "application/octet-stream" in produces:
75 content_type = "application/octet-stream"
76 else:
77 raise NonConformingResponseHeaders(
78 "Multiple response content types are defined in the operation spec, but the "
79 "handler response did not specify which one to return."
80 )
82 return content_type
84 def _prepare_body_and_status_code(
85 self, data, *, status_code: int = None, mimetype: str
86 ) -> tuple:
87 if data is NoContent:
88 data = None
90 if status_code is None:
91 if data is None:
92 status_code = 204
93 else:
94 status_code = 200
96 if data is not None:
97 body = self._serialize_data(data, mimetype)
98 else:
99 body = data
101 return body, status_code
103 def _serialize_data(self, data: t.Any, mimetype: str) -> t.Any:
104 if is_json_mimetype(mimetype):
105 return self.jsonifier.dumps(data)
106 return data
108 @staticmethod
109 def _unpack_handler_response(
110 handler_response: t.Union[str, bytes, dict, list, tuple]
111 ) -> t.Tuple[t.Union[str, bytes, dict, list, None], t.Optional[int], dict]:
112 """Unpack the handler response into data, status_code and headers.
114 :param handler_response: The response returned from the handler function if it was not a
115 response class.
117 :return: A tuple of data, status_code and headers
118 """
119 data, status_code, headers = None, None, {}
121 if not isinstance(handler_response, tuple):
122 data = handler_response
124 elif len(handler_response) == 1:
125 (data,) = handler_response
127 elif len(handler_response) == 2:
128 data, status_code_or_headers = handler_response
129 if isinstance(status_code_or_headers, int):
130 status_code = status_code_or_headers
131 elif isinstance(status_code_or_headers, Enum) and isinstance(
132 status_code_or_headers.value, int
133 ):
134 status_code = status_code_or_headers.value
135 else:
136 headers = status_code_or_headers
138 elif len(handler_response) == 3:
139 data, status_code, headers = handler_response
141 else:
142 raise TypeError(
143 "The view function did not return a valid response tuple."
144 " The tuple must have the form (body), (body, status, headers),"
145 " (body, status), or (body, headers)."
146 )
148 return data, status_code, headers
151class SyncResponseDecorator(BaseResponseDecorator):
152 def __call__(self, function: t.Callable) -> t.Callable:
153 @functools.wraps(function)
154 def wrapper(*args, **kwargs):
155 """
156 This method converts a handler response to a framework response.
157 The handler response can be a ConnexionResponse, a framework response, a tuple or an
158 object.
159 """
160 handler_response = function(*args, **kwargs)
161 if self.framework.is_framework_response(handler_response):
162 return handler_response
163 elif isinstance(handler_response, ConnexionResponse):
164 return self.framework.connexion_to_framework_response(handler_response)
165 else:
166 return self.build_framework_response(handler_response)
168 return wrapper
171class AsyncResponseDecorator(BaseResponseDecorator):
172 def __call__(self, function: t.Callable) -> t.Callable:
173 @functools.wraps(function)
174 async def wrapper(*args, **kwargs):
175 """
176 This method converts a handler response to a framework response.
177 The handler response can be a ConnexionResponse, a framework response, a tuple or an
178 object.
179 """
180 handler_response = await function(*args, **kwargs)
181 if self.framework.is_framework_response(handler_response):
182 return handler_response
183 elif isinstance(handler_response, ConnexionResponse):
184 return self.framework.connexion_to_framework_response(handler_response)
185 else:
186 return self.build_framework_response(handler_response)
188 return wrapper