aboutsummaryrefslogtreecommitdiffhomepage
path: root/libs/flask_restx/resource.py
diff options
context:
space:
mode:
Diffstat (limited to 'libs/flask_restx/resource.py')
-rw-r--r--libs/flask_restx/resource.py93
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)