示例#1
0
    def to_operation_dict(self) -> OperationSpecType:
        """Generate the openapi spec part of this endpoint.

        The result needs to be added to the `apispec` instance manually.
        """
        assert self.func is not None, "This object must be used in a decorator environment."
        assert self.operation_id is not None, "This object must be used in a decorator environment."

        module_obj = import_string(self.func.__module__)

        response_headers: Dict[str, OpenAPIParameter] = {}
        content_type_header = to_openapi([CONTENT_TYPE], 'header')[0]
        del content_type_header['in']
        response_headers[content_type_header.pop('name')] = content_type_header

        if self.etag in ('output', 'both'):
            etag_header = to_openapi([ETAG_HEADER_PARAM], 'header')[0]
            del etag_header['in']
            response_headers[etag_header.pop('name')] = etag_header

        responses: ResponseType = {}

        if 404 in self._expected_status_codes:
            responses['404'] = self._path_item(
                404, 'The requested object has not been found.')

        if 422 in self._expected_status_codes:
            responses['422'] = self._path_item(
                422, 'The request could not be processed.')

        if 405 in self._expected_status_codes:
            responses['405'] = _path_item(
                405, 'Method not allowed: This request is only allowed '
                'with other HTTP methods')

        if 409 in self._expected_status_codes:
            responses['409'] = self._path_item(
                409,
                'The request is in conflict with the stored resource',
            )

        if 415 in self._expected_status_codes:
            responses['415'] = self._path_item(
                415, 'The submitted content-type is not supported.')

        if 302 in self._expected_status_codes:
            responses['302'] = self._path_item(
                302,
                'Either the resource has moved or has not yet completed. Please see this '
                'resource for further information.',
            )

        if 400 in self._expected_status_codes:
            responses['400'] = self._path_item(
                400, 'Parameter or validation failure')

        # We don't(!) support any endpoint without an output schema.
        # Just define one!
        if 200 in self._expected_status_codes:
            responses['200'] = self._path_item(
                200,
                'The operation was done successfully.',
                content={self.content_type: {
                    'schema': self.response_schema
                }},
                headers=response_headers,
            )

        if 204 in self._expected_status_codes:
            responses['204'] = self._path_item(
                204, 'Operation done successfully. No further output.')

        if 412 in self._expected_status_codes:
            responses['412'] = self._path_item(
                412,
                "The value of the If-Match header doesn't match the object's ETag.",
            )

        if 428 in self._expected_status_codes:
            responses['428'] = self._path_item(
                428, 'The required If-Match header is missing.')

        docstring_name = _docstring_name(module_obj.__doc__)
        tag_obj: OpenAPITag = {
            'name': docstring_name,
            'x-displayName': docstring_name,
        }
        docstring_desc = _docstring_description(module_obj.__doc__)
        if docstring_desc:
            tag_obj['description'] = docstring_desc
        _add_tag(tag_obj, tag_group=self.tag_group)

        operation_spec: OperationSpecType = {
            'operationId': self.operation_id,
            'tags': [docstring_name],
            'description': '',
        }

        header_params: List[RawParameter] = []
        query_params: Sequence[
            RawParameter] = self.query_params if self.query_params is not None else []
        path_params: Sequence[
            RawParameter] = self.path_params if self.path_params is not None else []

        if self.etag in ('input', 'both'):
            header_params.append(ETAG_IF_MATCH_HEADER)

        if self.request_schema:
            header_params.append(CONTENT_TYPE)

        # While we define the parameters separately to be able to use them for validation, the
        # OpenAPI spec expects them to be listed in on place, so here we bunch them together.
        operation_spec['parameters'] = coalesce_schemas([
            ('header', header_params),
            ('query', query_params),
            ('path', path_params),
        ])

        operation_spec['responses'] = responses

        if self.request_schema is not None:
            operation_spec['requestBody'] = {
                'required': True,
                'content': {
                    'application/json': {
                        'schema': self.request_schema,
                    }
                }
            }

        operation_spec['x-codeSamples'] = code_samples(
            self,
            header_params=header_params,
            path_params=path_params,
            query_params=query_params,
        )

        # If we don't have any parameters we remove the empty list, so the spec will not have it.
        if not operation_spec['parameters']:
            del operation_spec['parameters']

        docstring_name = _docstring_name(self.func.__doc__)
        if docstring_name:
            operation_spec['summary'] = docstring_name
        else:
            raise RuntimeError(
                f"Please put a docstring onto {self.operation_id}")
        docstring_desc = _docstring_description(self.func.__doc__)
        if docstring_desc:
            operation_spec['description'] = docstring_desc

        apispec.utils.deepupdate(operation_spec, self.options)

        return {self.method: operation_spec}  # type: ignore[misc]
示例#2
0
    def to_operation_dict(self) -> OperationSpecType:
        """Generate the openapi spec part of this endpoint.

        The result needs to be added to the `apispec` instance manually.
        """
        assert self.func is not None, "This object must be used in a decorator environment."
        assert self.operation_id is not None, "This object must be used in a decorator environment."

        module_obj = import_string(self.func.__module__)

        response_headers: Dict[str, OpenAPIParameter] = {}
        content_type_header = to_openapi([CONTENT_TYPE], "header")[0]
        del content_type_header["in"]
        response_headers[content_type_header.pop("name")] = content_type_header

        if self.etag in ("output", "both"):
            etag_header = to_openapi([ETAG_HEADER_PARAM], "header")[0]
            del etag_header["in"]
            response_headers[etag_header.pop("name")] = etag_header

        responses: ResponseType = {}

        responses["406"] = self._path_item(
            406, "The requests accept headers can not be satisfied.")

        if 401 in self._expected_status_codes:
            responses["401"] = self._path_item(
                401, "The user is not authorized to do this request.")

        if self.tag_group == "Setup":
            responses["403"] = self._path_item(
                403, "Configuration via WATO is disabled.")
        if self.tag_group == "Checkmk Internal" and 403 in self._expected_status_codes:
            responses["403"] = self._path_item(
                403,
                "You have insufficient permissions for this operation.",
            )

        if 404 in self._expected_status_codes:
            responses["404"] = self._path_item(
                404, "The requested object has not been found.")

        if 422 in self._expected_status_codes:
            responses["422"] = self._path_item(
                422, "The request could not be processed.")

        if 423 in self._expected_status_codes:
            responses["423"] = self._path_item(
                423, "This resource is currently locked.")

        if 405 in self._expected_status_codes:
            responses["405"] = self._path_item(
                405,
                "Method not allowed: This request is only allowed with other HTTP methods"
            )

        if 409 in self._expected_status_codes:
            responses["409"] = self._path_item(
                409,
                "The request is in conflict with the stored resource.",
            )

        if 415 in self._expected_status_codes:
            responses["415"] = self._path_item(
                415, "The submitted content-type is not supported.")

        if 302 in self._expected_status_codes:
            responses["302"] = self._path_item(
                302,
                "Either the resource has moved or has not yet completed. Please see this "
                "resource for further information.",
            )

        if 400 in self._expected_status_codes:
            responses["400"] = self._path_item(
                400, "Parameter or validation failure.")

        # We don't(!) support any endpoint without an output schema.
        # Just define one!
        if 200 in self._expected_status_codes:
            if self.response_schema:
                content: ContentObject
                content = {self.content_type: {"schema": self.response_schema}}
            elif self.content_type == "application/octet-stream" or self.content_type.startswith(
                    "image/"):
                content = {
                    self.content_type: {
                        "schema": {
                            "type": "string",
                            "format": "binary",
                        }
                    }
                }
            else:
                raise ValueError(
                    f"Unknown content-type: {self.content_type} Please add condition."
                )
            responses["200"] = self._path_item(
                200,
                "The operation was done successfully.",
                content=content,
                headers=response_headers,
            )

        if 204 in self._expected_status_codes:
            responses["204"] = self._path_item(
                204, "Operation done successfully. No further output.")

        if 412 in self._expected_status_codes:
            responses["412"] = self._path_item(
                412,
                "The value of the If-Match header doesn't match the object's ETag.",
            )

        if 428 in self._expected_status_codes:
            responses["428"] = self._path_item(
                428, "The required If-Match header is missing.")

        docstring_name = _docstring_name(module_obj.__doc__)
        tag_obj: OpenAPITag = {
            "name": docstring_name,
            "x-displayName": docstring_name,
        }
        docstring_desc = _docstring_description(module_obj.__doc__)
        if docstring_desc:
            tag_obj["description"] = docstring_desc
        _add_tag(tag_obj, tag_group=self.tag_group)

        operation_spec: OperationSpecType = {
            "operationId": self.operation_id,
            "tags": [docstring_name],
            "description": "",
        }

        header_params: List[RawParameter] = []
        query_params: Sequence[RawParameter] = (
            self.query_params if self.query_params is not None else [])
        path_params: Sequence[RawParameter] = (
            self.path_params if self.path_params is not None else [])

        if active_config.rest_api_etag_locking and self.etag in ("input",
                                                                 "both"):
            header_params.append(ETAG_IF_MATCH_HEADER)

        if self.request_schema:
            header_params.append(CONTENT_TYPE)

        # While we define the parameters separately to be able to use them for validation, the
        # OpenAPI spec expects them to be listed in on place, so here we bunch them together.
        operation_spec["parameters"] = coalesce_schemas([
            ("header", header_params),
            ("query", query_params),
            ("path", path_params),
        ])

        operation_spec["responses"] = responses

        if self.request_schema is not None:
            operation_spec["requestBody"] = {
                "required": True,
                "content": {
                    "application/json": {
                        "schema": self.request_schema,
                    }
                },
            }

        operation_spec["x-codeSamples"] = code_samples(
            self,
            header_params=header_params,
            path_params=path_params,
            query_params=query_params,
        )

        # If we don't have any parameters we remove the empty list, so the spec will not have it.
        if not operation_spec["parameters"]:
            del operation_spec["parameters"]

        try:
            docstring_name = _docstring_name(self.func.__doc__)
        except ValueError as exc:
            raise ValueError(
                f"Function {module_obj.__name__}:{self.func.__name__} has no docstring."
            ) from exc

        if docstring_name:
            operation_spec["summary"] = docstring_name
        else:
            raise RuntimeError(
                f"Please put a docstring onto {self.operation_id}")

        docstring_desc = _docstring_description(self.func.__doc__)
        if docstring_desc:
            operation_spec["description"] = docstring_desc

        if self.permissions_required is not None:
            # Check that all the names are known to the system.
            for perm in self.permissions_required.iter_perms():
                if perm not in permission_registry:
                    # NOTE:
                    #   See rest_api.py. dynamic_permission() have to be loaded before request
                    #   for this to work reliably.
                    raise RuntimeError(
                        f'Permission "{perm}" is not registered in the permission_registry.'
                    )

            # Write permission documentation in openapi spec.
            if description := _permission_descriptions(
                    self.permissions_required, self.permissions_description):
                operation_spec.setdefault("description", "")
                if not operation_spec["description"]:
                    operation_spec["description"] += "\n\n"
                operation_spec["description"] += description
示例#3
0
    def to_operation_dict(self) -> OperationSpecType:
        """Generate the openapi spec part of this endpoint.

        The result needs to be added to the `apispec` instance manually.
        """
        assert self.func is not None, "This object must be used in a decorator environment."
        assert self.operation_id is not None, "This object must be used in a decorator environment."

        module_obj = import_string(self.func.__module__)

        response_headers: Dict[str, OpenAPIParameter] = {}
        content_type_header = to_openapi([CONTENT_TYPE], "header")[0]
        del content_type_header["in"]
        response_headers[content_type_header.pop("name")] = content_type_header

        if self.etag in ("output", "both"):
            etag_header = to_openapi([ETAG_HEADER_PARAM], "header")[0]
            del etag_header["in"]
            response_headers[etag_header.pop("name")] = etag_header

        responses: ResponseType = {}

        if self.tag_group == "Setup":
            responses["403"] = self._path_item(
                403, "Configuration via WATO is disabled")

        if 404 in self._expected_status_codes:
            responses["404"] = self._path_item(
                404, "The requested object has not been found.")

        if 422 in self._expected_status_codes:
            responses["422"] = self._path_item(
                422, "The request could not be processed.")

        if 405 in self._expected_status_codes:
            responses["405"] = _path_item(
                405, "Method not allowed: This request is only allowed "
                "with other HTTP methods")

        if 409 in self._expected_status_codes:
            responses["409"] = self._path_item(
                409,
                "The request is in conflict with the stored resource.",
            )

        if 415 in self._expected_status_codes:
            responses["415"] = self._path_item(
                415, "The submitted content-type is not supported.")

        if 302 in self._expected_status_codes:
            responses["302"] = self._path_item(
                302,
                "Either the resource has moved or has not yet completed. Please see this "
                "resource for further information.",
            )

        if 400 in self._expected_status_codes:
            responses["400"] = self._path_item(
                400, "Parameter or validation failure.")

        # We don't(!) support any endpoint without an output schema.
        # Just define one!
        if 200 in self._expected_status_codes:
            if self.response_schema:
                content: ContentObject
                content = {self.content_type: {"schema": self.response_schema}}
            elif self.content_type == "application/octet-stream" or self.content_type.startswith(
                    "image/"):
                content = {
                    self.content_type: {
                        "schema": {
                            "type": "string",
                            "format": "binary",
                        }
                    }
                }
            else:
                raise ValueError(
                    f"Unknown content-type: {self.content_type} Please add condition."
                )
            responses["200"] = self._path_item(
                200,
                "The operation was done successfully.",
                content=content,
                headers=response_headers,
            )

        if 204 in self._expected_status_codes:
            responses["204"] = self._path_item(
                204, "Operation done successfully. No further output.")

        if 412 in self._expected_status_codes:
            responses["412"] = self._path_item(
                412,
                "The value of the If-Match header doesn't match the object's ETag.",
            )

        if 428 in self._expected_status_codes:
            responses["428"] = self._path_item(
                428, "The required If-Match header is missing.")

        docstring_name = _docstring_name(module_obj.__doc__)
        tag_obj: OpenAPITag = {
            "name": docstring_name,
            "x-displayName": docstring_name,
        }
        docstring_desc = _docstring_description(module_obj.__doc__)
        if docstring_desc:
            tag_obj["description"] = docstring_desc
        _add_tag(tag_obj, tag_group=self.tag_group)

        operation_spec: OperationSpecType = {
            "operationId": self.operation_id,
            "tags": [docstring_name],
            "description": "",
        }

        header_params: List[RawParameter] = []
        query_params: Sequence[RawParameter] = (
            self.query_params if self.query_params is not None else [])
        path_params: Sequence[RawParameter] = (
            self.path_params if self.path_params is not None else [])

        if config.rest_api_etag_locking and self.etag in ("input", "both"):
            header_params.append(ETAG_IF_MATCH_HEADER)

        if self.request_schema:
            header_params.append(CONTENT_TYPE)

        # While we define the parameters separately to be able to use them for validation, the
        # OpenAPI spec expects them to be listed in on place, so here we bunch them together.
        operation_spec["parameters"] = coalesce_schemas([
            ("header", header_params),
            ("query", query_params),
            ("path", path_params),
        ])

        operation_spec["responses"] = responses

        if self.request_schema is not None:
            operation_spec["requestBody"] = {
                "required": True,
                "content": {
                    "application/json": {
                        "schema": self.request_schema,
                    }
                },
            }

        operation_spec["x-codeSamples"] = code_samples(
            self,
            header_params=header_params,
            path_params=path_params,
            query_params=query_params,
        )

        # If we don't have any parameters we remove the empty list, so the spec will not have it.
        if not operation_spec["parameters"]:
            del operation_spec["parameters"]

        docstring_name = _docstring_name(self.func.__doc__)
        if docstring_name:
            operation_spec["summary"] = docstring_name
        else:
            raise RuntimeError(
                f"Please put a docstring onto {self.operation_id}")
        docstring_desc = _docstring_description(self.func.__doc__)
        if docstring_desc:
            operation_spec["description"] = docstring_desc

        apispec.utils.deepupdate(operation_spec, self.options)

        return {self.method: operation_spec}  # type: ignore[misc]
示例#4
0
    def to_operation_dict(self) -> OperationSpecType:
        """Generate the openapi spec part of this endpoint.

        The result needs to be added to the `apispec` instance manually.
        """
        assert self.func is not None, "This object must be used in a decorator environment."
        assert self.operation_id is not None, "This object must be used in a decorator environment."

        module_obj = import_string(self.func.__module__)
        module_name = module_obj.__name__

        headers: Dict[str, OpenAPIParameter] = {}
        if self.etag in ('output', 'both'):
            etag_header = to_openapi([ETAG_HEADER_PARAM], 'header')[0]
            del etag_header['in']
            headers[etag_header.pop('name')] = etag_header

        responses: ResponseType = {}

        # We don't(!) support any endpoint without an output schema.
        # Just define one!
        if self.response_schema is not None:
            responses['200'] = {
                'content': {
                    self.content_type: {
                        'schema': self.response_schema
                    },
                },
                'description': apispec.utils.dedent(self.response_schema.__doc__ or ''),
                'headers': headers,
            }

        if self.will_do_redirects:
            responses['302'] = {
                'description':
                    ('Either the resource has moved or has not yet completed. Please see this '
                     'resource for further information.')
            }

        # Actually, iff you don't want to give out anything, then we don't need a schema.
        if self.output_empty:
            responses['204'] = {
                'description': 'Operation done successfully. No further output.',
                'headers': headers,
            }

        tag_obj: OpenAPITag = {
            'name': module_name,
        }
        docstring_name = _docstring_name(module_obj.__doc__)
        if docstring_name:
            tag_obj['x-displayName'] = docstring_name
        docstring_desc = _docstring_description(module_obj.__doc__)
        if docstring_desc:
            tag_obj['description'] = docstring_desc
        _add_tag(tag_obj, tag_group=self.tag_group)

        operation_spec: OperationSpecType = {
            'operationId': self.operation_id,
            'tags': [module_name],
            'description': '',
            'responses': {
                'default': {
                    'description': 'Any unsuccessful or unexpected result.',
                    'content': {
                        'application/problem+json': {
                            'schema': self.error_schema,
                        }
                    }
                }
            },
        }

        header_params: List[RawParameter] = []
        query_params: Sequence[
            RawParameter] = self.query_params if self.query_params is not None else []
        path_params: Sequence[
            RawParameter] = self.path_params if self.path_params is not None else []

        if self.etag in ('input', 'both'):
            header_params.append(ETAG_IF_MATCH_HEADER)

        operation_spec['parameters'] = coalesce_schemas([
            ('header', header_params),
            ('query', query_params),
            ('path', path_params),
        ])

        operation_spec['responses'].update(responses)

        if self.request_schema is not None:
            operation_spec['requestBody'] = {
                'required': self.request_body_required,
                'content': {
                    'application/json': {
                        'schema': self.request_schema,
                    }
                }
            }

        operation_spec['x-codeSamples'] = code_samples(self)

        # If we don't have any parameters we remove the empty list, so the spec will not have it.
        if not operation_spec['parameters']:
            del operation_spec['parameters']

        docstring_name = _docstring_name(self.func.__doc__)
        if docstring_name:
            operation_spec['summary'] = docstring_name
        else:
            raise RuntimeError(f"Please put a docstring onto {self.operation_id}")
        docstring_desc = _docstring_description(self.func.__doc__)
        if docstring_desc:
            operation_spec['description'] = docstring_desc

        apispec.utils.deepupdate(operation_spec, self.options)

        return {self.method: operation_spec}  # type: ignore[misc]
示例#5
0
    def _add_api_spec(func):
        module_obj = import_string(func.__module__)
        module_name = module_obj.__name__
        operation_id = func.__module__ + "." + func.__name__

        if not output_empty and response_schema is None:
            raise ValueError(
                "%s: 'response_schema' required when output is to be sent!" %
                operation_id)

        if output_empty and response_schema:
            raise ValueError(
                "%s: On empty output 'output_schema' may not be used." %
                operation_id)

        # We don't(!) support any endpoint without an output schema.
        # Just define one!
        if response_schema is not None:
            path_item = {
                '200': {
                    'content': {
                        content_type: {
                            'schema': response_schema
                        },
                    },
                    'description':
                    apispec.utils.dedent(response_schema.__doc__ or ''),
                }
            }

        # Actually, iff you don't want to give out anything, then we don't need a schema.
        if output_empty:
            path_item = {
                '204': {
                    'description':
                    'Operation done successfully. No further output.'
                }
            }

        tag_obj = {'name': module_name}
        tag_obj.update(
            _docstring_keys(module_obj.__doc__, 'x-displayName',
                            'description'))
        _add_tag(tag_obj, tag_group='Endpoints')

        operation_spec = {
            'operationId': operation_id,
            'tags': [module_name],
            'description': '',
            'responses': {
                'default': {
                    'description': 'Any unsuccessful or unexpected result.',
                    'content': {
                        'application/problem+json': {
                            'schema': error_schema,
                        }
                    }
                }
            },
            'parameters': [],
        }

        if param_names:
            operation_spec['parameters'].extend(parameters)

        if etag in ('input', 'both'):
            operation_spec['parameters'].append(ETAG_IF_MATCH_HEADER)

        if etag in ('output', 'both'):
            # We can't put this completely in a schema because 'headers' is a map. We therefore
            # have to duplicate it every time.

            # NOTE: Be aware that this block only works under the assumption that only one(!)
            # http_status defined `operation_spec`. If this assumption no longer holds this block
            # needs to be refactored.
            only_key = list(path_item.keys())[0]
            path_item[only_key].setdefault('headers', {})
            path_item[only_key]['headers'].update(ETAG_HEADER_PARAM)

        operation_spec['responses'].update(path_item)

        if request_schema is not None:
            tag = _tag_from_schema(request_schema)
            _add_tag(tag, tag_group='Request Schemas')

            operation_spec['requestBody'] = {
                'required': request_body_required,
                'content': {
                    'application/json': {
                        'schema': request_schema,
                    }
                }
            }
            operation_spec['x-code-samples'] = code_samples(
                path, method, request_schema)

        # If we don't have any parameters we remove the empty list, so the spec will not have it.
        if not operation_spec['parameters']:
            del operation_spec['parameters']

        operation_spec.update(
            _docstring_keys(func.__doc__, 'summary', 'description'))
        apispec.utils.deepupdate(operation_spec, options)

        add_operation(path, method, operation_spec)

        if response_schema is None and request_schema is None:
            return func

        return wrap_with_validation(func, request_schema, response_schema)
示例#6
0
    def _add_api_spec(func):
        module_obj = import_string(func.__module__)
        module_name = module_obj.__name__
        operation_id = func.__module__ + "." + func.__name__

        ENDPOINT_REGISTRY.add_endpoint(
            module_name,
            name,
            method,
            path,
            find_all_parameters(primitive_parameters),
        )

        if not output_empty and response_schema is None:
            raise ValueError(f"{operation_id}: 'response_schema' required when "
                             f"output will be sent!")

        if output_empty and response_schema:
            raise ValueError(f"{operation_id}: On empty output 'output_schema' may not be used.")

        headers: Dict[str, PrimitiveParameter] = {}
        if etag in ('output', 'both'):
            headers.update(ETAG_HEADER_PARAM.header_dict())

        responses: ResponseType = {}

        # We don't(!) support any endpoint without an output schema.
        # Just define one!
        if response_schema is not None:
            responses['200'] = {
                'content': {
                    content_type: {
                        'schema': response_schema
                    },
                },
                'description': apispec.utils.dedent(response_schema.__doc__ or ''),
                'headers': headers,
            }

        if will_do_redirects:
            responses['302'] = {
                'description':
                    ('Either the resource has moved or has not yet completed. Please see this '
                     'resource for further information.')
            }

        # Actually, iff you don't want to give out anything, then we don't need a schema.
        if output_empty:
            responses['204'] = {
                'description': 'Operation done successfully. No further output.',
                'headers': headers,
            }

        tag_obj: OpenAPITag = {
            'name': module_name,
        }
        docstring_name = _docstring_name(module_obj.__doc__)
        if docstring_name:
            tag_obj['x-displayName'] = docstring_name
        docstring_desc = _docstring_description(module_obj.__doc__)
        if docstring_desc:
            tag_obj['description'] = docstring_desc
        _add_tag(tag_obj, tag_group='Endpoints')

        operation_spec: OperationSpecType = {
            'operationId': operation_id,
            'tags': [module_name],
            'description': '',
            'responses': {
                'default': {
                    'description': 'Any unsuccessful or unexpected result.',
                    'content': {
                        'application/problem+json': {
                            'schema': error_schema,
                        }
                    }
                }
            },
            'parameters': primitive_parameters,
        }

        if etag in ('input', 'both'):
            operation_spec['parameters'].append(ETAG_IF_MATCH_HEADER.to_dict())

        operation_spec['responses'].update(responses)

        if request_schema is not None:
            tag = _tag_from_schema(request_schema)
            _add_tag(tag, tag_group='Request Schemas')

            operation_spec['requestBody'] = {
                'required': request_body_required,
                'content': {
                    'application/json': {
                        'schema': request_schema,
                    }
                }
            }

        operation_spec['x-codeSamples'] = code_samples(path, method, request_schema, operation_spec)

        # If we don't have any parameters we remove the empty list, so the spec will not have it.
        if not operation_spec['parameters']:
            del operation_spec['parameters']

        docstring_name = _docstring_name(func.__doc__)
        if docstring_name:
            operation_spec['summary'] = docstring_name
        docstring_desc = _docstring_description(func.__doc__)
        if docstring_desc:
            operation_spec['description'] = docstring_desc

        apispec.utils.deepupdate(operation_spec, options)

        if func not in _SEEN_ENDPOINTS:
            # NOTE:
            # Only add the endpoint to the spec if not already done. We don't want
            # to add it multiple times. While this shouldn't be happening, doctest
            # sometimes complains that it would be happening. Not sure why. Anyways,
            # it's not a big deal as long as the SPEC is written correctly.
            SPEC.path(path=path, operations={method.lower(): operation_spec})
            _SEEN_ENDPOINTS.add(func)

        return wrap_with_validation(func, request_schema, response_schema)
示例#7
0
    def to_operation_dict(self) -> OperationSpecType:
        """Generate the openapi spec part of this endpoint.

        The result needs to be added to the `apispec` instance manually.
        """
        assert self.func is not None, "This object must be used in a decorator environment."
        assert self.operation_id is not None, "This object must be used in a decorator environment."

        module_obj = import_string(self.func.__module__)
        module_name = module_obj.__name__

        headers: Dict[str, PrimitiveParameter] = {}
        if self.etag in ('output', 'both'):
            headers.update(ETAG_HEADER_PARAM.header_dict())

        responses: ResponseType = {}

        # We don't(!) support any endpoint without an output schema.
        # Just define one!
        if self.response_schema is not None:
            responses['200'] = {
                'content': {
                    self.content_type: {
                        'schema': self.response_schema
                    },
                },
                'description':
                apispec.utils.dedent(self.response_schema.__doc__ or ''),
                'headers':
                headers,
            }

        if self.will_do_redirects:
            responses['302'] = {
                'description':
                ('Either the resource has moved or has not yet completed. Please see this '
                 'resource for further information.')
            }

        # Actually, iff you don't want to give out anything, then we don't need a schema.
        if self.output_empty:
            responses['204'] = {
                'description':
                'Operation done successfully. No further output.',
                'headers': headers,
            }

        tag_obj: OpenAPITag = {
            'name': module_name,
        }
        docstring_name = _docstring_name(module_obj.__doc__)
        if docstring_name:
            tag_obj['x-displayName'] = docstring_name
        docstring_desc = _docstring_description(module_obj.__doc__)
        if docstring_desc:
            tag_obj['description'] = docstring_desc
        _add_tag(tag_obj, tag_group='Endpoints')

        operation_spec: OperationSpecType = {
            'operationId': self.operation_id,
            'tags': [module_name],
            'description': '',
            'responses': {
                'default': {
                    'description': 'Any unsuccessful or unexpected result.',
                    'content': {
                        'application/problem+json': {
                            'schema': self.error_schema,
                        }
                    }
                }
            },
            'parameters': _reduce_to_primitives(self.parameters),
        }

        if self.etag in ('input', 'both'):
            operation_spec['parameters'].append(ETAG_IF_MATCH_HEADER.to_dict())

        operation_spec['responses'].update(responses)

        if self.request_schema is not None:
            tag = _tag_from_schema(self.request_schema)
            _add_tag(tag, tag_group='Request Schemas')

            operation_spec['requestBody'] = {
                'required': self.request_body_required,
                'content': {
                    'application/json': {
                        'schema': self.request_schema,
                    }
                }
            }

        operation_spec['x-codeSamples'] = code_samples(
            self.path,
            self.method,
            self.request_schema,
            operation_spec,
        )

        # If we don't have any parameters we remove the empty list, so the spec will not have it.
        if not operation_spec['parameters']:
            del operation_spec['parameters']

        docstring_name = _docstring_name(self.func.__doc__)
        if docstring_name:
            operation_spec['summary'] = docstring_name
        docstring_desc = _docstring_description(self.func.__doc__)
        if docstring_desc:
            operation_spec['description'] = docstring_desc

        apispec.utils.deepupdate(operation_spec, self.options)

        return operation_spec