diff options
Diffstat (limited to 'libs/flask_restx/resource.py')
-rw-r--r-- | libs/flask_restx/resource.py | 93 |
1 files changed, 93 insertions, 0 deletions
diff --git a/libs/flask_restx/resource.py b/libs/flask_restx/resource.py new file mode 100644 index 000000000..4d0b40328 --- /dev/null +++ b/libs/flask_restx/resource.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from flask import request +from flask.views import MethodView +from werkzeug import __version__ as werkzeug_version + +if werkzeug_version.split('.')[0] >= '2': + from werkzeug.wrappers import Response as BaseResponse +else: + from werkzeug.wrappers import BaseResponse + +from .model import ModelBase + +from .utils import unpack + + +class Resource(MethodView): + """ + Represents an abstract RESTX resource. + + Concrete resources should extend from this class + and expose methods for each supported HTTP method. + If a resource is invoked with an unsupported HTTP method, + the API will return a response with status 405 Method Not Allowed. + Otherwise the appropriate method is called and passed all arguments + from the url rule used when adding the resource to an Api instance. + See :meth:`~flask_restx.Api.add_resource` for details. + """ + + representations = None + method_decorators = [] + + def __init__(self, api=None, *args, **kwargs): + self.api = api + + 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) + + self.validate_payload(meth) + + resp = meth(*args, **kwargs) + + if isinstance(resp, BaseResponse): + return resp + + representations = self.representations or {} + + 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 __validate_payload(self, expect, collection=False): + """ + :param ModelBase expect: the expected model for the input payload + :param bool collection: False if a single object of a resource is + expected, True if a collection of objects of a resource is expected. + """ + # TODO: proper content negotiation + data = request.get_json() + if collection: + data = data if isinstance(data, list) else [data] + for obj in data: + expect.validate(obj, self.api.refresolver, self.api.format_checker) + else: + expect.validate(data, self.api.refresolver, self.api.format_checker) + + def validate_payload(self, func): + """Perform a payload validation on expected model if necessary""" + if getattr(func, "__apidoc__", False) is not False: + doc = func.__apidoc__ + validate = doc.get("validate", None) + validate = validate if validate is not None else self.api._validate + if validate: + for expect in doc.get("expect", []): + # TODO: handle third party handlers + if isinstance(expect, list) and len(expect) == 1: + if isinstance(expect[0], ModelBase): + self.__validate_payload(expect[0], collection=True) + if isinstance(expect, ModelBase): + self.__validate_payload(expect, collection=False) |