Files
connexion/connexion/operations/abstract.py
Robbe Sneyders 0857710147 Only instantiate specification once (#1819)
Fixes #1801 

I had to make quite a few additional changes to satisfy mypy.
2023-11-30 23:59:26 +01:00

251 lines
6.8 KiB
Python

"""
This module defines an AbstractOperation class which implements an abstract Operation interface
and functionality shared between Swagger 2 and OpenAPI 3 specifications.
"""
import abc
import logging
from connexion.utils import all_json
logger = logging.getLogger("connexion.operations.abstract")
DEFAULT_MIMETYPE = "application/json"
class AbstractOperation(metaclass=abc.ABCMeta):
"""
An API routes requests to an Operation by a (path, method) pair.
The operation uses a resolver to resolve its handler function.
We use the provided spec to do a bunch of heavy lifting before
(and after) we call security_schemes handler.
The registered handler function ends up looking something like::
@secure_endpoint
@validate_inputs
@deserialize_function_inputs
@serialize_function_outputs
@validate_outputs
def user_provided_handler_function(important, stuff):
if important:
serious_business(stuff)
"""
def __init__(
self,
method,
path,
operation,
resolver,
app_security=None,
security_schemes=None,
randomize_endpoint=None,
uri_parser_class=None,
):
"""
:param method: HTTP method
:type method: str
:param path:
:type path: str
:param operation: swagger operation object
:type operation: dict
:param resolver: Callable that maps operationID to a function
:param app_security: list of security rules the application uses by default
:type app_security: list
:param security_schemes: `Security Definitions Object
<https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#security-definitions-object>`_
:type security_schemes: dict
:param randomize_endpoint: number of random characters to append to operation name
:type randomize_endpoint: integer
:param uri_parser_class: class to use for uri parsing
:type uri_parser_class: AbstractURIParser
"""
self._method = method
self._path = path
self._operation = operation
self._resolver = resolver
self._security = operation.get("security", app_security)
self._security_schemes = security_schemes
self._uri_parser_class = uri_parser_class
self._randomize_endpoint = randomize_endpoint
self._operation_id = self._operation.get("operationId")
self._resolution = resolver.resolve(self)
self._operation_id = self._resolution.operation_id
self._responses = self._operation.get("responses", {})
@classmethod
@abc.abstractmethod
def from_spec(cls, spec, *args, path, method, resolver, **kwargs):
pass
@property
def method(self):
"""
The HTTP method for this operation (ex. GET, POST)
"""
return self._method
@property
def request_body(self):
"""The request body for this operation"""
@property
def is_request_body_defined(self) -> bool:
"""Whether the request body is defined for this operation"""
return self.request_body != {}
@property
def path(self):
"""
The path of the operation, relative to the API base path
"""
return self._path
@property
def security(self):
return self._security
@property
def security_schemes(self):
return self._security_schemes
@property
def responses(self):
"""
Returns the responses for this operation
"""
return self._responses
@property
def operation_id(self):
"""
The operation id used to identify the operation internally to the app
"""
return self._operation_id
@property
def randomize_endpoint(self):
"""
number of random digits to generate and append to the operation_id.
"""
return self._randomize_endpoint
@property
def router_controller(self):
"""
The router controller to use (python module where handler functions live)
"""
return self._router_controller
@property
@abc.abstractmethod
def parameters(self):
"""
Returns the parameters for this operation
"""
@property
@abc.abstractmethod
def produces(self):
"""
Content-Types that the operation produces
"""
@property
@abc.abstractmethod
def consumes(self):
"""
Content-Types that the operation consumes
"""
@abc.abstractmethod
def body_name(self, content_type: str) -> str:
"""
Name of the body in the spec.
"""
@abc.abstractmethod
def body_schema(self, content_type: str = None) -> dict:
"""
The body schema definition for this operation.
"""
@abc.abstractmethod
def body_definition(self, content_type: str = None) -> dict:
"""
The body definition for this operation.
:rtype: dict
"""
def response_definition(self, status_code=None, content_type=None):
"""
response definition for this endpoint
"""
response_definition = self.responses.get(
str(status_code), self.responses.get("default", {})
)
return response_definition
@abc.abstractmethod
def response_schema(self, status_code=None, content_type=None):
"""
response schema for this endpoint
"""
@abc.abstractmethod
def example_response(self, status_code=None, content_type=None):
"""
Returns an example from the spec
"""
@abc.abstractmethod
def get_path_parameter_types(self):
"""
Returns the types for parameters in the path
"""
@abc.abstractmethod
def with_definitions(self, schema):
"""
Returns the given schema, but with the definitions from the spec
attached. This allows any remaining references to be resolved by a
validator (for example).
"""
def get_mimetype(self):
"""
If the endpoint has no 'produces' then the default is
'application/json'.
:rtype str
"""
# TODO: don't default
if all_json(self.produces):
try:
return self.produces[0]
except IndexError:
return DEFAULT_MIMETYPE
elif len(self.produces) == 1:
return self.produces[0]
else:
return DEFAULT_MIMETYPE
@property
def uri_parser_class(self):
"""
The uri parser class for this operation.
"""
return self._uri_parser_class
@property
def function(self):
"""
Resolved function.
:rtype: types.FunctionType
"""
return self._resolution.function