Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/connexion/resolver.py: 24%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2This module contains resolvers, functions that resolves the user defined view functions
3from the operations defined in the OpenAPI spec.
4"""
6import inspect
7import logging
8import typing as t
10from inflection import camelize
12import connexion.utils as utils
13from connexion.exceptions import ResolverError
15logger = logging.getLogger("connexion.resolver")
18class Resolution:
19 def __init__(self, function, operation_id):
20 """
21 Represents the result of operation resolution
23 :param function: The endpoint function
24 :type function: types.FunctionType
25 """
26 self.function = function
27 self.operation_id = operation_id
30class Resolver:
31 def __init__(self, function_resolver: t.Callable = utils.get_function_from_name):
32 """
33 Standard resolver
35 :param function_resolver: Function that resolves functions using an operationId
36 """
37 self.function_resolver = function_resolver
39 def resolve(self, operation):
40 """
41 Default operation resolver
43 :type operation: connexion.operations.AbstractOperation
44 """
45 operation_id = self.resolve_operation_id(operation)
46 return Resolution(
47 self.resolve_function_from_operation_id(operation_id), operation_id
48 )
50 def resolve_operation_id(self, operation):
51 """
52 Default operationId resolver
54 :type operation: connexion.operations.AbstractOperation
55 """
56 operation_id = operation.operation_id
57 router_controller = operation.router_controller
58 if router_controller is None:
59 return operation_id
60 return f"{router_controller}.{operation_id}"
62 def resolve_function_from_operation_id(self, operation_id):
63 """
64 Invokes the function_resolver
66 :type operation_id: str
67 """
68 try:
69 return self.function_resolver(operation_id)
70 except ImportError as e:
71 msg = f'Cannot resolve operationId "{operation_id}"! Import error was "{str(e)}"'
72 raise ResolverError(msg)
73 except (AttributeError, ValueError) as e:
74 raise ResolverError(str(e))
77class RelativeResolver(Resolver):
78 """
79 Resolves endpoint functions relative to a given root path or module.
80 """
82 def __init__(self, root_path, function_resolver=utils.get_function_from_name):
83 """
84 :param root_path: The root path relative to which an operationId is resolved.
85 Can also be a module. Has the same effect as setting
86 `x-swagger-router-controller` or `x-openapi-router-controller` equal to
87 `root_path` for every operation individually.
88 :type root_path: typing.Union[str, types.ModuleType]
89 :param function_resolver: Function that resolves functions using an operationId
90 :type function_resolver: types.FunctionType
91 """
92 super().__init__(function_resolver=function_resolver)
93 if inspect.ismodule(root_path):
94 self.root_path = root_path.__name__
95 else:
96 self.root_path = root_path
98 def resolve_operation_id(self, operation):
99 """Resolves the operationId relative to the root path, unless
100 x-swagger-router-controller or x-openapi-router-controller is specified.
102 :param operation: The operation to resolve
103 :type operation: connexion.operations.AbstractOperation
104 """
105 operation_id = operation.operation_id
106 router_controller = operation.router_controller
107 if router_controller is None:
108 return f"{self.root_path}.{operation_id}"
109 return f"{router_controller}.{operation_id}"
112class RestyResolver(Resolver):
113 """
114 Resolves endpoint functions using REST semantics (unless overridden by specifying operationId)
115 """
117 def __init__(
118 self, default_module_name: str, *, collection_endpoint_name: str = "search"
119 ):
120 """
121 :param default_module_name: Default module name for operations
122 :param collection_endpoint_name: Name of function to resolve collection endpoints to
123 """
124 super().__init__()
125 self.default_module_name = default_module_name
126 self.collection_endpoint_name = collection_endpoint_name
128 def resolve_operation_id(self, operation):
129 """
130 Resolves the operationId using REST semantics unless explicitly configured in the spec
132 :type operation: connexion.operations.AbstractOperation
133 """
134 if operation.operation_id:
135 return super().resolve_operation_id(operation)
137 return self.resolve_operation_id_using_rest_semantics(operation)
139 def resolve_operation_id_using_rest_semantics(self, operation):
140 """
141 Resolves the operationId using REST semantics
143 :type operation: connexion.operations.AbstractOperation
144 """
146 # Split the path into components delimited by '/'
147 path_components = [c for c in operation.path.split("/") if len(c)]
149 def is_var(component):
150 """True if the path component is a var. eg, '{id}'"""
151 return (component[0] == "{") and (component[-1] == "}")
153 resource_name = ".".join([c for c in path_components if not is_var(c)]).replace(
154 "-", "_"
155 )
157 def get_controller_name():
158 x_router_controller = operation.router_controller
160 name = self.default_module_name
162 if x_router_controller:
163 name = x_router_controller
165 elif resource_name:
166 name += "." + resource_name
168 return name
170 def get_function_name():
171 method = operation.method
173 is_collection_endpoint = (
174 method.lower() == "get"
175 and len(resource_name)
176 and not is_var(path_components[-1])
177 )
179 return (
180 self.collection_endpoint_name
181 if is_collection_endpoint
182 else method.lower()
183 )
185 return f"{get_controller_name()}.{get_function_name()}"
188class MethodResolverBase(RestyResolver):
189 """
190 Resolves endpoint functions based on Flask's MethodView semantics, e.g.
192 .. code-block:: yaml
194 paths:
195 /foo_bar:
196 get:
197 # Implied function call: api.FooBarView().get
199 .. code-block:: python
201 class FooBarView(MethodView):
202 def get(self):
203 return ...
204 def post(self):
205 return ...
207 """
209 _class_arguments_type = t.Dict[
210 str, t.Dict[str, t.Union[t.Iterable, t.Dict[str, t.Any]]]
211 ]
213 def __init__(self, *args, class_arguments: _class_arguments_type = None, **kwargs):
214 """
215 :param args: Arguments passed to :class:`~RestyResolver`
216 :param class_arguments: Arguments to instantiate the View Class in the format below
217 :param kwargs: Keywords arguments passed to :class:`~RestyResolver`
219 .. code-block:: python
221 {
222 "ViewName": {
223 "args": (positional arguments,)
224 "kwargs": {
225 "keyword": "argument"
226 }
227 }
228 }
229 """
230 self.class_arguments = class_arguments or {}
231 super(MethodResolverBase, self).__init__(*args, **kwargs)
232 self.initialized_views: list = []
234 def resolve_operation_id(self, operation):
235 """
236 Resolves the operationId using REST semantics unless explicitly configured in the spec
237 Once resolved with REST semantics the view_name is capitalised and has 'View' added
238 to it so it now matches the Class names of the MethodView
240 :type operation: connexion.operations.AbstractOperation
241 """
242 if operation.operation_id:
243 # If operation_id is defined then use the higher level API to resolve
244 return RestyResolver.resolve_operation_id(self, operation)
246 # Use RestyResolver to get operation_id for us (follow their naming conventions/structure)
247 operation_id = self.resolve_operation_id_using_rest_semantics(operation)
248 module_name, view_base, meth_name = operation_id.rsplit(".", 2)
249 view_name = camelize(view_base) + "View"
251 return f"{module_name}.{view_name}.{meth_name}"
253 def resolve_function_from_operation_id(self, operation_id):
254 """
255 Invokes the function_resolver
257 :type operation_id: str
258 """
260 try:
261 module_name, view_name, meth_name = operation_id.rsplit(".", 2)
262 if operation_id and not view_name.endswith("View"):
263 # If operation_id is not a view then assume it is a standard function
264 return self.function_resolver(operation_id)
266 mod = __import__(module_name, fromlist=[view_name])
267 view_cls = getattr(mod, view_name)
268 # find the view and return it
269 return self.resolve_method_from_class(view_name, meth_name, view_cls)
271 except ImportError as e:
272 msg = 'Cannot resolve operationId "{}"! Import error was "{}"'.format(
273 operation_id, str(e)
274 )
275 raise ResolverError(msg)
276 except (AttributeError, ValueError) as e:
277 raise ResolverError(str(e))
279 def resolve_method_from_class(self, view_name, meth_name, view_cls):
280 """
281 Returns the view function for the given view class.
282 """
283 raise NotImplementedError()
286class MethodResolver(MethodResolverBase):
287 """
288 A generic method resolver that instantiates a class and extracts the method
289 from it, based on the operation id.
290 """
292 def resolve_method_from_class(self, view_name, meth_name, view_cls):
293 view = None
294 for v in self.initialized_views:
295 if v.__class__ == view_cls:
296 view = v
297 break
298 if view is None:
299 # get the args and kwargs for this view
300 cls_arguments = self.class_arguments.get(view_name, {})
301 cls_args = cls_arguments.get("args", ())
302 cls_kwargs = cls_arguments.get("kwargs", {})
303 # instantiate the class with the args and kwargs
304 view = view_cls(*cls_args, **cls_kwargs)
305 self.initialized_views.append(view)
306 # get the method if the class
307 func = getattr(view, meth_name)
308 # Return the method function of the class
309 return func
312class MethodViewResolver(MethodResolverBase):
313 """
314 A specialized method resolver that works with flask's method views.
315 It resolves the method by calling as_view on the class.
316 """
318 def __init__(self, *args, **kwargs):
319 if "collection_endpoint_name" in kwargs:
320 del kwargs["collection_endpoint_name"]
321 # Dispatch of request is done by Flask
322 logger.warning(
323 "collection_endpoint_name is ignored by the MethodViewResolver. "
324 "Requests to a collection endpoint will be routed to .get()"
325 )
326 super().__init__(*args, **kwargs)
328 def resolve_method_from_class(self, view_name, meth_name, view_cls):
329 view = None
330 for v in self.initialized_views:
331 # views returned by <class>.as_view
332 # have the origin class attached as .view_class
333 if v.view_class == view_cls:
334 view = v
335 break
336 if view is None:
337 # get the args and kwargs for this view
338 cls_arguments = self.class_arguments.get(view_name, {})
339 cls_args = cls_arguments.get("args", ())
340 cls_kwargs = cls_arguments.get("kwargs", {})
341 # call as_view to get a view function
342 # that is decorated with the classes
343 # decorator list, if any
344 view = view_cls.as_view(view_name, *cls_args, **cls_kwargs)
345 # add the view to the list of initialized views
346 # in order to call as_view only once
347 self.initialized_views.append(view)
348 # return the class as view function
349 # for each operation so that requests
350 # are dispatched with <class>.dispatch_request,
351 # when calling the view function
352 return view