def marshal(data, fields, envelope=None): """Takes raw data (in the form of a dict, list, object) and a dict of fields to output and filters the data based on those fields. :param data: the actual object(s) from which the fields are taken from :param fields: a dict of whose keys will make up the final serialized response output :param envelope: optional key that will be used to envelop the serialized response >>> from flask_restful import fields, marshal >>> data = { 'a': 100, 'b': 'foo' } >>> mfields = { 'a': fields.Raw } >>> marshal(data, mfields) OrderedDict([('a', 100)]) >>> marshal(data, mfields, envelope='data') OrderedDict([('data', OrderedDict([('a', 100)]))]) """ def make(cls): if isinstance(cls, type): return cls() return cls if isinstance(data, (list, tuple)): return (OrderedDict([(envelope, [marshal(d, fields) for d in data])]) if envelope else [marshal(d, fields) for d in data]) items = ((k, marshal(data, v) if isinstance(v, dict) else make(v).output( k, data)) for k, v in fields.items()) return OrderedDict([(envelope, OrderedDict(items)) ]) if envelope else OrderedDict(items)
def test_list_of_nested(self): obj = {'list': [{'a': 1, 'b': 1}, {'a': 2, 'b': 1}, {'a': 3, 'b': 1}]} field = fields.List(fields.Nested({'a': fields.Integer})) self.assertEqual([ OrderedDict([('a', 1)]), OrderedDict([('a', 2)]), OrderedDict([('a', 3)]) ], field.output('list', obj))
def test_list_of_raw(self): obj = {'list': [{'a': 1, 'b': 1}, {'a': 2, 'b': 1}, {'a': 3, 'b': 1}]} field = fields.List(fields.Raw) self.assertEquals([OrderedDict([('a', 1), ('b', 1), ]), OrderedDict([('a', 2), ('b', 1), ]), OrderedDict([('a', 3), ('b', 1), ])], field.output('list', obj)) obj = {'list': [1, 2, 'a']} field = fields.List(fields.Raw) self.assertEquals([1, 2, 'a'], field.output('list', obj))
def __init__(self, app=None, prefix='', default_mediatype='application/json', decorators=None, catch_all_404s=False, serve_challenge_on_401=False, url_part_order='bae', errors=None): self.representations = OrderedDict(DEFAULT_REPRESENTATIONS) self.urls = {} self.prefix = prefix self.default_mediatype = default_mediatype self.decorators = decorators if decorators else [] self.catch_all_404s = catch_all_404s self.serve_challenge_on_401 = serve_challenge_on_401 self.url_part_order = url_part_order self.errors = errors or {} self.blueprint_setup = None self.endpoints = set() self.resources = [] self.app = None self.blueprint = None if app is not None: self.app = app self.init_app(app)
def dispatch_request(self, *args, **kwargs): # Taken from flask #noinspection PyUnresolvedReferences meth = getattr(self, request.method.lower(), None) if meth is None and request.method == 'HEAD': meth = getattr(self, 'get', None) assert meth is not None, 'Unimplemented method %r' % request.method if isinstance(self.method_decorators, Mapping): decorators = self.method_decorators.get(request.method.lower(), []) else: decorators = self.method_decorators for decorator in decorators: meth = decorator(meth) resp = meth(*args, **kwargs) if isinstance(resp, ResponseBase): # There may be a better way to test return resp representations = self.representations or OrderedDict() #noinspection PyUnresolvedReferences mediatype = request.accept_mimetypes.best_match(representations, default=None) if mediatype in representations: data, code, headers = unpack(resp) resp = representations[mediatype](data, code, headers) resp.headers['Content-Type'] = mediatype return resp return resp
def dispatch_request(self, *args, **kwargs): """Overridden to support list and create method names, and improved decorator handling for methods """ method = self._get_method_for_request() try: resp = method(*args, **kwargs) except Exception: if apm.is_initialized: apm.capture_exception() resp = ({ 'message': 'Server Error' }, HTTPStatus.INTERNAL_SERVER_ERROR, {}) traceback.print_exc() if isinstance(resp, Response): return resp representations = self.representations or OrderedDict() mediatype = request.accept_mimetypes.best_match(representations, default=None) if mediatype in representations: data, code, headers = unpack(resp) resp = representations[mediatype](data, code, headers) resp.headers['Content-Type'] = mediatype return resp return resp
def dispatch_request(self, *args, **kwargs): meth = getattr(type(self), request.method.lower(), None) if meth is None and request.method == "HEAD": meth = getattr(type(self), "get", None) assert meth is not None, "Unimplemented method %r" % request.method if isinstance(self.method_decorators, Mapping): decorators = self.method_decorators.get( "*", []) + self.method_decorators.get(request.method.lower(), []) else: decorators = self.method_decorators for decorator in decorators: meth = decorator(meth) resp = meth(self, *args, **kwargs) if isinstance(resp, Response): return resp representations = self.representations or OrderedDict() mediatype = request.accept_mimetypes.best_match(representations, default=None) if mediatype in representations: data, code, headers = unpack(resp) resp = representations[mediatype](data, code, headers) resp.headers["Content-Type"] = mediatype return resp return resp
def __init__( self, app, host="localhost", port=5000, prefix="", description="SAFRSAPI", json_encoder=SAFRSJSONEncoder, swaggerui_blueprint=True, **kwargs, ): """ http://jsonapi.org/format/#content-negotiation-servers Servers MUST send all JSON:API data in response documents with the header Content-Type: application/vnd.api+json without any media type parameters. Servers MUST respond with a 415 Unsupported Media Type status code if a request specifies the header Content-Type: application/vnd.api+json with any media type parameters. Servers MUST respond with a 406 Not Acceptable status code if a request’s Accept header contains the JSON:API media type and all instances of that media type are modified with media type parameters. """ self._custom_swagger = kwargs.pop("custom_swagger", {}) kwargs["default_mediatype"] = "application/vnd.api+json" safrs.SAFRS(app, prefix=prefix, json_encoder=json_encoder, swaggerui_blueprint=swaggerui_blueprint, **kwargs) # the host shown in the swagger ui # this host may be different from the hostname of the server and # sometimes we don't want to show the port (eg when proxied) # in that case the port may be None if port: host = f"{host}:{port}" super().__init__( app, api_spec_url=kwargs.pop("api_spec_url", "/swagger"), host=host, description=description, prefix=prefix, base_path=prefix, **kwargs, ) self.init_app(app) self.representations = OrderedDict(DEFAULT_REPRESENTATIONS) self.update_spec()
def marshal(data, fields, envelope=None, strip_none=False): """Takes raw data (in the form of a dict, list, object) and a dict of fields to output and filters the data based on those fields. :param data: the actual object(s) from which the fields are taken from :param fields: a dict of whose keys will make up the final serialized response output :param envelope: optional key that will be used to envelop the serialized response :param strip_none: optional key that will strip serialized None data. Default: False >>> from flask_restful import fields, marshal >>> data = { 'a': 100, 'b': 'foo' } >>> mfields = { 'a': fields.Raw } >>> marshal(data, mfields) OrderedDict([('a', 100)]) >>> marshal(data, mfields, envelope='data') OrderedDict([('data', OrderedDict([('a', 100)]))]) """ def make(cls): if isinstance(cls, type): return cls() return cls if isinstance(data, (list, tuple)): return (OrderedDict([(envelope, [marshal(d, fields, strip_none=strip_none) for d in data])]) if envelope else [marshal(d, fields, strip_none=strip_none) for d in data]) if isinstance(data, (float, int, str)): return (OrderedDict([(envelope, data)]) if envelope else data) items = ((k, marshal(data, v, strip_none=strip_none) if isinstance(v, dict) else make(v).output(k, data)) for k, v in fields.items()) if strip_none == True or (current_app and current_app.config.get("RESTFUL_MARSHAL_STRIP_NONE", False) == True): # strip None values newItems = OrderedDict() allItems = OrderedDict(items) for k in allItems: if allItems[k] is not None: newItems[k] = allItems[k] items = newItems else: items = OrderedDict(items) return (OrderedDict([(envelope, items)]) if envelope else items)
def __init__(self, *args, **kwargs): """ http://jsonapi.org/format/#content-negotiation-servers Servers MUST send all JSON:API data in response documents with the header Content-Type: application/vnd.api+json without any media type parameters. Servers MUST respond with a 415 Unsupported Media Type status code if a request specifies the header Content-Type: application/vnd.api+json with any media type parameters. Servers MUST respond with a 406 Not Acceptable status code if a request’s Accept header contains the JSON:API media type and all instances of that media type are modified with media type parameters. """ custom_swagger = kwargs.pop("custom_swagger", {}) kwargs["default_mediatype"] = "application/vnd.api+json" super(Api, self).__init__(*args, **kwargs) self.representations = OrderedDict(DEFAULT_REPRESENTATIONS) self.update_spec(custom_swagger)
def dispatch_request(self, *args, **kwargs): """Overridden to support list and create method names, and improved decorator handling for methods """ method = self._get_method_for_request() resp = method(*args, **kwargs) if isinstance(resp, Response): return resp representations = self.representations or OrderedDict() mediatype = request.accept_mimetypes.best_match(representations, default=None) if mediatype in representations: data, code, headers = unpack(resp) resp = representations[mediatype](data, code, headers) resp.headers['Content-Type'] = mediatype return resp return resp
def _add_oas_req_params(self, resource, path_item, method, exposing_instance, is_jsonapi_rpc, swagger_url): """ Add the request parameters to the swagger (filter, sort) """ method_doc = path_item[method] 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 # only required for collections though 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()) path_item[method] = method_doc
def dispatch_request(self, *args, **kwargs): # Taken from flask meth = getattr(self, request.method.lower(), None) if meth is None and request.method == 'HEAD': meth = getattr(self, 'get', None) assert meth is not None, 'Unimplemented method %r' % request.method for decorator in self.method_decorators: meth = decorator(meth) try: increment(total_requests) except Exception as exc: pass # this is where actual method starts resp = meth(*args, **kwargs) # this is where actual method ends if isinstance(resp, ResponseBase): # There may be a better way to test return resp representations = self.representations or OrderedDict() mediatype = request.accept_mimetypes.best_match(representations, default=None) if mediatype in representations: data, code, headers = unpack(resp) resp = representations[mediatype](data, code, headers) resp.headers['Content-Type'] = mediatype return resp return resp
# -*- coding:utf-8 -*- from app.utils.api import RestfulApi from . import app from .controllers import Case from flask_restful.utils import OrderedDict from flask_restful.representations.json import output_json RESOURCES = ([Case, '/api/success_case', '/api/success_case/<string:action>'], ) api = RestfulApi(app, default_mediatype='application/json; charset=UTF-8') api.representations = OrderedDict([('application/json; charset=UTF-8', output_json)]) api.add_resources(RESOURCES)
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)