1"""
2This module defines an AbstractOperation class which implements an abstract Operation interface
3and functionality shared between Swagger 2 and OpenAPI 3 specifications.
4"""
5
6import abc
7import logging
8import typing as t
9
10from connexion.utils import all_json
11
12logger = logging.getLogger("connexion.operations.abstract")
13
14DEFAULT_MIMETYPE = "application/json"
15
16
17class AbstractOperation(metaclass=abc.ABCMeta):
18
19 """
20 An API routes requests to an Operation by a (path, method) pair.
21 The operation uses a resolver to resolve its handler function.
22 We use the provided spec to do a bunch of heavy lifting before
23 (and after) we call security_schemes handler.
24 The registered handler function ends up looking something like::
25
26 @secure_endpoint
27 @validate_inputs
28 @deserialize_function_inputs
29 @serialize_function_outputs
30 @validate_outputs
31 def user_provided_handler_function(important, stuff):
32 if important:
33 serious_business(stuff)
34 """
35
36 def __init__(
37 self,
38 method,
39 path,
40 operation,
41 resolver,
42 app_security=None,
43 security_schemes=None,
44 randomize_endpoint=None,
45 uri_parser_class=None,
46 ):
47 """
48 :param method: HTTP method
49 :type method: str
50 :param path:
51 :type path: str
52 :param operation: swagger operation object
53 :type operation: dict
54 :param resolver: Callable that maps operationID to a function
55 :param app_security: list of security rules the application uses by default
56 :type app_security: list
57 :param security_schemes: `Security Definitions Object
58 <https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#security-definitions-object>`_
59 :type security_schemes: dict
60 :param randomize_endpoint: number of random characters to append to operation name
61 :type randomize_endpoint: integer
62 :param uri_parser_class: class to use for uri parsing
63 :type uri_parser_class: AbstractURIParser
64 """
65 self._method = method
66 self._path = path
67 self._operation = operation
68 self._resolver = resolver
69 self._security = operation.get("security", app_security)
70 self._security_schemes = security_schemes
71 self._uri_parser_class = uri_parser_class
72 self._randomize_endpoint = randomize_endpoint
73 self._operation_id = self._operation.get("operationId")
74
75 self._resolution = resolver.resolve(self)
76 self._operation_id = self._resolution.operation_id
77
78 self._responses = self._operation.get("responses", {})
79
80 @classmethod
81 @abc.abstractmethod
82 def from_spec(cls, spec, *args, path, method, resolver, **kwargs):
83 pass
84
85 @property
86 def method(self):
87 """
88 The HTTP method for this operation (ex. GET, POST)
89 """
90 return self._method
91
92 @property
93 def request_body(self):
94 """The request body for this operation"""
95
96 @property
97 def is_request_body_defined(self) -> bool:
98 """Whether the request body is defined for this operation"""
99 return self.request_body != {}
100
101 @property
102 def path(self):
103 """
104 The path of the operation, relative to the API base path
105 """
106 return self._path
107
108 @property
109 def security(self):
110 return self._security
111
112 @property
113 def security_schemes(self):
114 return self._security_schemes
115
116 @property
117 def responses(self):
118 """
119 Returns the responses for this operation
120 """
121 return self._responses
122
123 @property
124 def operation_id(self):
125 """
126 The operation id used to identify the operation internally to the app
127 """
128 return self._operation_id
129
130 @property
131 def randomize_endpoint(self):
132 """
133 number of random digits to generate and append to the operation_id.
134 """
135 return self._randomize_endpoint
136
137 @property
138 def router_controller(self):
139 """
140 The router controller to use (python module where handler functions live)
141 """
142 return self._router_controller
143
144 @property
145 @abc.abstractmethod
146 def parameters(self):
147 """
148 Returns the parameters for this operation
149 """
150
151 @property
152 @abc.abstractmethod
153 def produces(self):
154 """
155 Content-Types that the operation produces
156 """
157
158 @property
159 @abc.abstractmethod
160 def consumes(self):
161 """
162 Content-Types that the operation consumes
163 """
164
165 @abc.abstractmethod
166 def body_name(self, content_type: str) -> str:
167 """
168 Name of the body in the spec.
169 """
170
171 @abc.abstractmethod
172 def body_schema(self, content_type: t.Optional[str] = None) -> dict:
173 """
174 The body schema definition for this operation.
175 """
176
177 @abc.abstractmethod
178 def body_definition(self, content_type: t.Optional[str] = None) -> dict:
179 """
180 The body definition for this operation.
181 :rtype: dict
182 """
183
184 def response_definition(self, status_code=None, content_type=None):
185 """
186 response definition for this endpoint
187 """
188 response_definition = self.responses.get(
189 str(status_code), self.responses.get("default", {})
190 )
191 return response_definition
192
193 @abc.abstractmethod
194 def response_schema(self, status_code=None, content_type=None):
195 """
196 response schema for this endpoint
197 """
198
199 @abc.abstractmethod
200 def example_response(self, status_code=None, content_type=None):
201 """
202 Returns an example from the spec
203 """
204
205 @abc.abstractmethod
206 def get_path_parameter_types(self):
207 """
208 Returns the types for parameters in the path
209 """
210
211 @abc.abstractmethod
212 def with_definitions(self, schema):
213 """
214 Returns the given schema, but with the definitions from the spec
215 attached. This allows any remaining references to be resolved by a
216 validator (for example).
217 """
218
219 def get_mimetype(self):
220 """
221 If the endpoint has no 'produces' then the default is
222 'application/json'.
223
224 :rtype str
225 """
226 # TODO: don't default
227 if all_json(self.produces):
228 try:
229 return self.produces[0]
230 except IndexError:
231 return DEFAULT_MIMETYPE
232 elif len(self.produces) == 1:
233 return self.produces[0]
234 else:
235 return DEFAULT_MIMETYPE
236
237 @property
238 def uri_parser_class(self):
239 """
240 The uri parser class for this operation.
241 """
242 return self._uri_parser_class
243
244 @property
245 def function(self):
246 """
247 Resolved function.
248
249 :rtype: types.FunctionType
250 """
251 return self._resolution.function