def _add_oas_resource_definitions(self, resource, path_item): """ add the resource method schema references to the swagger "definitions" :param resource: :param path_item: """ definitions = {} for method in self.get_resource_methods(resource): if not method.upper() in HTTP_METHODS: continue f = getattr(resource, method, None) if not f: continue operation = getattr(f, "__swagger_operation_object", None) if operation: operation, definitions_ = Extractor.extract(operation) path_item[method] = operation definitions.update(definitions_) summary = parse_method_doc(f, operation) if summary: operation["summary"] = summary.split("<br/>")[0] try: validate_definitions_object(definitions) except FRSValidationError: safrs.log.critical("Validation failed for {}".format(definitions)) exit() self._swagger_object["definitions"].update(definitions)
def add_resource(self, resource, *urls, **kwargs): """ This method is partly copied from flask_restful_swagger_2/__init__.py I changed it because we don't need path id examples when there's no {id} in the path. We also have to filter out the unwanted parameters """ # # This function has grown out of proportion and should be refactored, disable lint warning for now # # pylint: disable=too-many-nested-blocks,too-many-statements, too-many-locals # kwargs.pop("relationship", False) # relationship object SAFRS_INSTANCE_SUFFIX = get_config("OBJECT_ID_SUFFIX") + "}" path_item = collections.OrderedDict() definitions = {} resource_methods = kwargs.get("methods", HTTP_METHODS) kwargs.pop("safrs_object", None) is_jsonapi_rpc = kwargs.pop( "jsonapi_rpc", False) # check if the exposed method is a jsonapi_rpc method deprecated = kwargs.pop("deprecated", False) # TBD!! for method in self.get_resource_methods(resource): if deprecated: continue if not method.upper() in resource_methods: continue f = getattr(resource, method, None) if not f: continue operation = getattr(f, "__swagger_operation_object", None) if operation: # operation, definitions_ = self._extract_schemas(operation) operation, definitions_ = Extractor.extract(operation) path_item[method] = operation definitions.update(definitions_) summary = parse_method_doc(f, operation) if summary: operation["summary"] = summary.split("<br/>")[0] try: validate_definitions_object(definitions) except FRSValidationError: safrs.log.critical("Validation failed for {}".format(definitions)) exit() self._swagger_object["definitions"].update(definitions) if path_item: for url in urls: if not url.startswith("/"): raise ValidationError("paths must start with a /") swagger_url = extract_swagger_path(url) # exposing_instance tells us whether we're exposing an instance (as opposed to a collection) exposing_instance = swagger_url.strip("/").endswith( SAFRS_INSTANCE_SUFFIX) for method in self.get_resource_methods(resource): if method == "post" and exposing_instance: # POSTing to an instance isn't jsonapi-compliant (https://jsonapi.org/format/#crud-creating-client-ids) # "A server MUST return 403 Forbidden in response to an # unsupported request to create a resource with a client-generated ID" # the method has already been added before, remove it & continue path_item.pop(method, None) continue method_doc = copy.deepcopy(path_item.get(method)) if not method_doc: continue collection_summary = method_doc.pop( "collection_summary", method_doc.get("summary", None)) if not exposing_instance and collection_summary: method_doc["summary"] = collection_summary parameters = [] for parameter in method_doc.get("parameters", []): object_id = "{%s}" % parameter.get("name") if method == "get": # Get the jsonapi included resources, ie the exposed relationships param = resource.get_swagger_include() parameters.append(param) # Get the jsonapi fields[], ie the exposed attributes/columns param = resource.get_swagger_fields() parameters.append(param) # # Add the sort, filter parameters to the swagger doc when retrieving a collection # if method == "get" and not (exposing_instance or is_jsonapi_rpc): # limit parameter specifies the number of items to return parameters += default_paging_parameters() param = resource.get_swagger_sort() parameters.append(param) parameters += list(resource.get_swagger_filters()) if not (parameter.get("in") == "path" and object_id not in swagger_url ) and parameter not in parameters: # Only if a path param is in path url then we add the param parameters.append(parameter) unique_params = OrderedDict() # rm duplicates for param in parameters: unique_params[param["name"]] = param method_doc["parameters"] = list(unique_params.values()) method_doc["operationId"] = self.get_operation_id( path_item.get(method).get("summary", "")) path_item[method] = method_doc instance_schema = method_doc.get("responses", {}).get("200", {}) if instance_schema and exposing_instance and method_doc[ "responses"]["200"].get("schema", None): method_doc["responses"]["200"][ "schema"] = resource.SAFRSObject.swagger_models[ "instance"].reference() # add this later method_doc["responses"]["200"]["schema"] = {} try: validate_path_item_object(path_item) except FRSValidationError as exc: safrs.log.exception(exc) safrs.log.critical( "Validation failed for {}".format(path_item)) exit() self._swagger_object["paths"][swagger_url] = path_item # Check whether we manage to convert to json try: json.dumps(self._swagger_object) except Exception: safrs.log.critical("Json encoding failed for") # safrs.log.debug(self._swagger_object) # disable API methods that were not set by the SAFRSObject for http_method in HTTP_METHODS: hm = http_method.lower() if hm not in self.get_resource_methods(resource): setattr(resource, hm, lambda x: ({}, HTTPStatus.METHOD_NOT_ALLOWED)) # pylint: disable=bad-super-call super(FRSApiBase, self).add_resource(resource, *urls, **kwargs)
def add_resource(self, resource, *urls, **kwargs): ''' This method is partly copied from flask_restful_swagger_2/__init__.py I changed it because we don't need path id examples when there's no {id} in the path. We filter out the unwanted parameters ''' SAFRS_INSTANCE_SUFFIX = get_config('OBJECT_ID_SUFFIX') + '}' path_item = {} definitions = {} resource_methods = kwargs.get('methods', HTTP_METHODS) safrs_object = kwargs.pop('safrs_object', None) for method in [m.lower() for m in resource.methods]: if not method.upper() in resource_methods: continue f = getattr(resource, method, None) if not f: continue operation = getattr(f, '__swagger_operation_object', None) if operation: #operation, definitions_ = self._extract_schemas(operation) operation, definitions_ = Extractor.extract(operation) path_item[method] = operation definitions.update(definitions_) summary = parse_method_doc(f, operation) if summary: operation['summary'] = summary.split('<br/>')[0] validate_definitions_object(definitions) self._swagger_object['definitions'].update(definitions) if path_item: validate_path_item_object(path_item) for url in urls: if not url.startswith('/'): raise ValidationError('paths must start with a /') swagger_url = extract_swagger_path(url) for method in [m.lower() for m in resource.methods]: if method == 'post' and swagger_url.strip('/').endswith(SAFRS_INSTANCE_SUFFIX): # POSTing to an instance isn't jsonapi-compliant (https://jsonapi.org/format/#crud-creating-client-ids) # "A server MUST return 403 Forbidden in response to an # unsupported request to create a resource with a client-generated ID" # the method has already been added before, remove it & continue path_item.pop(method,None) continue method_doc = copy.deepcopy(path_item.get(method)) if not method_doc: continue filtered_parameters = [] for parameter in method_doc.get('parameters', []): object_id = '{%s}'%parameter.get('name') if method == 'get' and not swagger_url.endswith(SAFRS_INSTANCE_SUFFIX): # limit parameter specifies the number of items to return for param in default_paging_parameters(): if param not in filtered_parameters: filtered_parameters.append(param) param = {'default': ','.join([rel.key for rel in self.safrs_object.__mapper__.relationships]), 'type': 'string', 'name': 'include', 'in': 'query', 'format' : 'string', 'required' : False, 'description' : 'Related relationships to include (csv)'} if param not in filtered_parameters: filtered_parameters.append(param) param = {'default': ','.join(self.safrs_object._s_jsonapi_attrs), 'type': 'string', 'name': 'fields[{}]'.format(self.safrs_object._s_type), 'in': 'query', 'format' : 'string', 'required' : False, 'description' : 'Related relationships to include (csv)'} if param not in filtered_parameters: filtered_parameters.append(param) param = {'default': ','.join(self.safrs_object._s_jsonapi_attrs), 'type': 'string', 'name': 'sort', 'in': 'query', 'format' : 'string', 'required' : False, 'description' : 'Sort order'} if param not in filtered_parameters: filtered_parameters.append(param) for column_name in self.safrs_object._s_column_names: param = {'default': '', 'type': 'string', 'name': 'filter[{}]'.format(column_name), 'in': 'query', 'format' : 'string', 'required' : False, 'description' : '{} attribute filter (csv)'.format(column_name)} if param not in filtered_parameters: filtered_parameters.append(param) if not (parameter.get('in') == 'path' and not object_id in swagger_url): # Only if a path param is in path url then we add the param filtered_parameters.append(parameter) method_doc['parameters'] = filtered_parameters method_doc['operationId'] = self.get_operation_id(path_item.get(method).get('summary', '')) path_item[method] = method_doc if method == 'get' and not swagger_url.endswith(SAFRS_INSTANCE_SUFFIX): # If no {id} was provided, we return a list of all the objects try: #pylint: disable=invalid-formatstring method_doc['description'] += ' list (See GET /{{} for details)'.format(SAFRS_INSTANCE_SUFFIX) method_doc['responses']['200']['schema'] = '' except: pass self._swagger_object['paths'][swagger_url] = path_item super(FRSApiBase, self).add_resource(resource, *urls, **kwargs)
def add_resource(self, resource, *urls, **kwargs): ''' This method is partly copied from flask_restful_swagger_2/__init__.py I changed it because we don't need path id examples when there's no {id} in the path. We filter out the unwanted parameters ''' from flask_restful_swagger_2 import validate_definitions_object, parse_method_doc from flask_restful_swagger_2 import validate_path_item_object, extract_swagger_path, Extractor path_item = {} definitions = {} resource_methods = kwargs.get( 'methods', ['GET', 'PUT', 'POST', 'DELETE', 'PATCH']) for method in [m.lower() for m in resource.methods]: if not method.upper() in resource_methods: continue f = getattr(resource, method, None) if not f: continue operation = getattr(f, '__swagger_operation_object', None) if operation: #operation, definitions_ = self._extract_schemas(operation) operation, definitions_ = Extractor.extract(operation) path_item[method] = operation definitions.update(definitions_) summary = parse_method_doc(f, operation) if summary: operation['summary'] = summary validate_definitions_object(definitions) self._swagger_object['definitions'].update(definitions) if path_item: validate_path_item_object(path_item) for url in urls: if not url.startswith('/'): raise ValidationError('paths must start with a /') swagger_url = extract_swagger_path(url) for method in [m.lower() for m in resource.methods]: method_doc = copy.deepcopy(path_item.get(method)) if not method_doc: continue filtered_parameters = [] for parameter in method_doc.get('parameters', []): object_id = '{%s}' % parameter.get('name') if method == 'get' and not swagger_url.endswith( SAFRS_INSTANCE_SUFFIX): # limit parameter specifies the number of items to return param = { 'default': 0, # The 0 isn't rendered though 'type': 'integer', 'name': 'page[offset]', 'in': 'query', 'format': 'int64', 'required': False, 'description': 'Page offset' } if not param in filtered_parameters: filtered_parameters.append(param) param = { 'default': 10, 'type': 'integer', 'name': 'page[limit]', 'in': 'query', 'format': 'int64', 'required': False, 'description': 'max number of items' } if not param in filtered_parameters: filtered_parameters.append(param) param = { 'default': '', 'type': 'string', 'name': 'include', 'in': 'query', 'format': 'int64', 'required': False, 'description': 'related objects to include' } if not param in filtered_parameters: filtered_parameters.append(param) param = { 'default': "", 'type': 'string', 'name': 'fields[{}]'.format(parameter.get('name')), 'in': 'query', 'format': 'int64', 'required': False, 'description': 'fields' } if not param in filtered_parameters: filtered_parameters.append(param) if not (parameter.get('in') == 'path' and not object_id in swagger_url): # Only if a path param is in path url then we add the param filtered_parameters.append(parameter) #log.debug(method_doc) method_doc['parameters'] = filtered_parameters path_item[method] = method_doc if method == 'get' and not swagger_url.endswith( SAFRS_INSTANCE_SUFFIX): # If no {id} was provided, we return a list of all the objects try: method_doc[ 'description'] += ' list (See GET /{{} for details)'.format( SAFRS_INSTANCE_SUFFIX) method_doc['responses']['200']['schema'] = '' except: pass self._swagger_object['paths'][swagger_url] = path_item '''self._swagger_object['securityDefinitions'] = { "api_key": { "type": "apiKey", "name": "api_key", "in": "query" }} self._swagger_object['security'] = [ "api_key" ]''' super(FRSApiBase, self).add_resource(resource, *urls, **kwargs)
def add_resource(self, resource, *urls, **kwargs): ''' This method is partly copied from flask_restful_swagger_2/__init__.py I changed it because we don't need path id examples when there's no {id} in the path. We filter out the unwanted parameters ''' from flask_restful_swagger_2 import validate_definitions_object, parse_method_doc from flask_restful_swagger_2 import validate_path_item_object from flask_restful_swagger_2 import extract_swagger_path, Extractor path_item = {} definitions = {} resource_methods = kwargs.get('methods', ['GET', 'PUT', 'POST', 'DELETE', 'PATCH']) safrs_object = kwargs.get('safrs_object', None) if safrs_object: del kwargs['safrs_object'] for method in [m.lower() for m in resource.methods]: if not method.upper() in resource_methods: continue f = getattr(resource, method, None) if not f: continue operation = getattr(f, '__swagger_operation_object', None) if operation: #operation, definitions_ = self._extract_schemas(operation) operation, definitions_ = Extractor.extract(operation) path_item[method] = operation definitions.update(definitions_) summary = parse_method_doc(f, operation) if summary: operation['summary'] = summary.split('<br/>')[0] validate_definitions_object(definitions) self._swagger_object['definitions'].update(definitions) if path_item: validate_path_item_object(path_item) for url in urls: if not url.startswith('/'): raise ValidationError('paths must start with a /') swagger_url = extract_swagger_path(url) for method in [m.lower() for m in resource.methods]: method_doc = copy.deepcopy(path_item.get(method)) if not method_doc: continue filtered_parameters = [] for parameter in method_doc.get('parameters', []): object_id = '{%s}'%parameter.get('name') if method == 'get' and not swagger_url.endswith(SAFRS_INSTANCE_SUFFIX): # limit parameter specifies the number of items to return for param in default_paging_parameters(): if param not in filtered_parameters: filtered_parameters.append(param) param = {\ 'default': ','.join([rel.key for rel in self.safrs_object.__mapper__.relationships]),\ 'type': 'string',\ 'name': 'include',\ 'in': 'query',\ 'format' : 'string',\ 'required' : False,\ 'description' : 'Related relationships to include (csv)'\ } if param not in filtered_parameters: filtered_parameters.append(param) param = {'default': "",\ 'type': 'string',\ 'name': 'fields[{}]'.format(self.safrs_object._s_type),\ 'in': 'query',\ 'format' : 'int64',\ 'required' : False,\ 'description' : 'Fields to be selected (csv)'} if param not in filtered_parameters: filtered_parameters.append(param) param = {'default': ','.join(self.safrs_object._s_jsonapi_attrs),\ 'type': 'string',\ 'name': 'sort',\ 'in': 'query',\ 'format' : 'string',\ 'required' : False,\ 'description' : 'Sort order'} if param not in filtered_parameters: filtered_parameters.append(param) for column_name in self.safrs_object._s_column_names: param = {'default': "",\ 'type': 'string',\ 'name': 'filter[{}]'.format(column_name),\ 'in': 'query',\ 'format' : 'string',\ 'required' : False,\ 'description' : '{} attribute filter (csv)'.format(column_name)} if param not in filtered_parameters: filtered_parameters.append(param) if not (parameter.get('in') == 'path' and not object_id in swagger_url): # Only if a path param is in path url then we add the param filtered_parameters.append(parameter) method_doc['parameters'] = filtered_parameters method_doc['operationId'] = self.get_operation_id(path_item.get(method).get('summary')) path_item[method] = method_doc if method == 'get' and not swagger_url.endswith(SAFRS_INSTANCE_SUFFIX): # If no {id} was provided, we return a list of all the objects try: method_doc['description'] += ' list (See GET /{{} for details)'.\ format(SAFRS_INSTANCE_SUFFIX) method_doc['responses']['200']['schema'] = '' except: pass self._swagger_object['paths'][swagger_url] = path_item super(FRSApiBase, self).add_resource(resource, *urls, **kwargs)
def add_resource(self, resource, *urls, **kwargs): """ This method is partly copied from flask_restful_swagger_2/__init__.py I changed it because we don't need path id examples when there's no {id} in the path. We filter out the unwanted parameters """ relationship = kwargs.pop("relationship", False) # relationship object SAFRS_INSTANCE_SUFFIX = get_config("OBJECT_ID_SUFFIX") + "}" path_item = {} definitions = {} resource_methods = kwargs.get("methods", HTTP_METHODS) kwargs.pop("safrs_object", None) is_jsonapi_rpc = kwargs.pop("jsonapi_rpc", False) # check if the exposed method is a jsonapi_rpc method for method in [m.lower() for m in resource.methods]: if not method.upper() in resource_methods: continue f = getattr(resource, method, None) if not f: continue operation = getattr(f, "__swagger_operation_object", None) if operation: # operation, definitions_ = self._extract_schemas(operation) operation, definitions_ = Extractor.extract(operation) path_item[method] = operation definitions.update(definitions_) summary = parse_method_doc(f, operation) if summary: operation["summary"] = summary.split("<br/>")[0] validate_definitions_object(definitions) self._swagger_object["definitions"].update(definitions) if path_item: validate_path_item_object(path_item) for url in urls: if not url.startswith("/"): raise ValidationError("paths must start with a /") swagger_url = extract_swagger_path(url) for method in [m.lower() for m in resource.methods]: if method == "post" and swagger_url.strip("/").endswith(SAFRS_INSTANCE_SUFFIX): # POSTing to an instance isn't jsonapi-compliant (https://jsonapi.org/format/#crud-creating-client-ids) # "A server MUST return 403 Forbidden in response to an # unsupported request to create a resource with a client-generated ID" # the method has already been added before, remove it & continue path_item.pop(method, None) continue method_doc = copy.deepcopy(path_item.get(method)) if not method_doc: continue filtered_parameters = [] for parameter in method_doc.get("parameters", []): object_id = "{%s}" % parameter.get("name") if method == "get" and relationship: default_include = ",".join( [rel.key for rel in relationship.mapper.class_.__mapper__.relationships] ) param = { "default": default_include, "type": "string", "name": "include", "in": "query", "format": "string", "required": False, "description": "Related relationships to include (csv)", } if param not in filtered_parameters: filtered_parameters.append(param) if method == "get" and not swagger_url.endswith(SAFRS_INSTANCE_SUFFIX) and not is_jsonapi_rpc: # limit parameter specifies the number of items to return for param in default_paging_parameters(): if param not in filtered_parameters: filtered_parameters.append(param) default_include = ",".join([rel.key for rel in self.safrs_object.__mapper__.relationships]) param = { "default": default_include, "type": "string", "name": "include", "in": "query", "format": "string", "required": False, "description": "Related relationships to include (csv)", } if param not in filtered_parameters: filtered_parameters.append(param) param = { "default": ",".join(self.safrs_object._s_jsonapi_attrs), "type": "string", "name": "fields[{}]".format(self.safrs_object._s_type), "in": "query", "format": "string", "required": False, "description": "Related relationships to include (csv)", } if param not in filtered_parameters: filtered_parameters.append(param) sort_attrs = self.safrs_object._s_jsonapi_attrs param = { "default": ",".join(sort_attrs), "type": "string", "name": "sort", "in": "query", "format": "string", "required": False, "description": "Sort order", } if param not in filtered_parameters: filtered_parameters.append(param) for column_name in self.safrs_object._s_column_names: param = { "default": "", "type": "string", "name": "filter[{}]".format(column_name), "in": "query", "format": "string", "required": False, "description": "{} attribute filter (csv)".format(column_name), } if param not in filtered_parameters: filtered_parameters.append(param) param = { "default": "", "type": "string", "name": "filter", "in": "query", "format": "string", "required": False, "description": "Custom filter", } if param not in filtered_parameters: filtered_parameters.append(param) if not (parameter.get("in") == "path" and not object_id in swagger_url) and parameter not in filtered_parameters: # Only if a path param is in path url then we add the param filtered_parameters.append(parameter) method_doc["parameters"] = filtered_parameters method_doc["operationId"] = self.get_operation_id(path_item.get(method).get("summary", "")) path_item[method] = method_doc if method == "get" and not swagger_url.endswith(SAFRS_INSTANCE_SUFFIX): # If no {id} was provided, we return a list of all the objects # pylint: disable=invalid-formatstring try: method_doc["description"] += " list (See GET /{{} for details)".format( SAFRS_INSTANCE_SUFFIX ) method_doc["responses"]["200"]["schema"] = "" except: pass self._swagger_object["paths"][swagger_url] = path_item super(FRSApiBase, self).add_resource(resource, *urls, **kwargs)