123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293 |
- # -*- 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)
|