def code_samples(endpoint, header_params, path_params, query_params) -> List[CodeSample]: """Create a list of rendered code sample Objects These are not specified by OpenAPI but are specific to ReDoc.""" env = _jinja_environment() return [{ 'label': example.label, 'lang': example.lang, 'source': env.get_template(example.label).render( hostname='localhost', site=omd_site(), username='******', password='******', endpoint=endpoint, path_params=to_openapi(path_params, 'path'), query_params=to_openapi(query_params, 'query'), header_params=to_openapi(header_params, 'header'), request_endpoint=endpoint.path, request_method=endpoint.method, request_schema=_get_schema(endpoint.request_schema), request_schema_multiple=_schema_is_multiple( endpoint.request_schema), ).strip(), } for example in CODE_EXAMPLES]
def code_samples( endpoint, header_params, path_params, query_params, ) -> List[CodeSample]: """Create a list of rendered code sample Objects These are not specified by OpenAPI but are specific to ReDoc. Examples: >>> class Endpoint: ... path = 'foo' ... method = 'get' ... content_type = 'application/json' ... request_schema = _get_schema('CreateHost') ... does_redirects = False >>> _endpoint = Endpoint() >>> import os >>> from unittest import mock >>> with mock.patch.dict(os.environ, {"OMD_SITE": "NO_SITE"}): ... samples = code_samples(_endpoint, [], [], []) >>> assert len(samples) """ env = _jinja_environment() return [{ 'label': example.label, 'lang': example.lang, 'source': env.get_template(example.label).render( hostname='localhost', site=omd_site(), username='******', password='******', endpoint=endpoint, path_params=to_openapi(path_params, 'path'), query_params=to_openapi(query_params, 'query'), header_params=to_openapi(header_params, 'header'), request_endpoint=endpoint.path, request_method=endpoint.method, request_schema=_get_schema(endpoint.request_schema), request_schema_multiple=_schema_is_multiple( endpoint.request_schema), ).strip(), } for example in CODE_EXAMPLES]
def code_samples( endpoint, header_params, path_params, query_params, ) -> List[CodeSample]: """Create a list of rendered code sample Objects These are not specified by OpenAPI but are specific to ReDoc. Examples: >>> class Endpoint: # doctest: +SKIP ... path = 'foo' ... method = 'get' ... content_type = 'application/json' ... request_schema = _get_schema('CreateHost') ... does_redirects = False >>> endpoint = Endpoint() # doctest: +SKIP >>> samples = code_samples(endpoint, [], [], []) # doctest: +SKIP """ env = _jinja_environment() return [ { "label": example.label, "lang": example.lang, "source": env.get_template(example.label) .render( hostname="localhost", site=omd_site(), username="******", password="******", endpoint=endpoint, path_params=to_openapi(path_params, "path"), query_params=to_openapi(query_params, "query"), header_params=to_openapi(header_params, "header"), request_endpoint=endpoint.path, request_method=endpoint.method, request_schema=_get_schema(endpoint.request_schema), request_schema_multiple=_schema_is_multiple(endpoint.request_schema), ) .strip(), } for example in CODE_EXAMPLES ]
def default_path(self): replace = {} if self.path_params is not None: parameters = to_openapi(self.path_params, 'path') for param in parameters: name = param['name'] replace[name] = f"<string:{name}>" try: path = self.path.format(**replace) except KeyError: raise AttributeError(f"Endpoint {self.path} has unspecified path parameters. " f"Specified: {replace}") return path
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
plugins.ValueTypedDictMarshmallowPlugin(), apispec_oneofschema.MarshmallowPlugin(), ], **options, ) SPEC = make_spec(options=OPTIONS) for sec_scheme_name, sec_scheme_spec in SECURITY_SCHEMES.items(): SPEC.components.security_scheme(sec_scheme_name, sec_scheme_spec) # All the supported response headers by the spec. # response_headers = { # 'Allow', # 'Cache-Control', # 'Last-Modified', # 'Warning', # 'Content-Type', # } for header_name, field in ACCEPT_HEADER.items(): SPEC.components.parameter( header_name, 'header', to_openapi([{ header_name: field }], 'header')[0], ) ErrorType = Literal['ignore', 'raise']
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]
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]
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]
plugins=[ marshmallow.MarshmallowPlugin(), plugins.ValueTypedDictMarshmallowPlugin(), apispec_oneofschema.MarshmallowPlugin(), ], **options, ) SPEC = make_spec(options=OPTIONS) for sec_scheme_name, sec_scheme_spec in SECURITY_SCHEMES.items(): SPEC.components.security_scheme(sec_scheme_name, sec_scheme_spec) # All the supported response headers by the spec. # response_headers = { # 'Allow', # 'Cache-Control', # 'Last-Modified', # 'Warning', # 'Content-Type', # } for header_name, field in ACCEPT_HEADER.items(): SPEC.components.parameter( header_name, "header", to_openapi([{header_name: field}], "header")[0], ) ErrorType = Literal["ignore", "raise"]