Source code for flump.view

from flask import request, make_response
from flask.views import MethodView
from werkzeug.exceptions import PreconditionFailed, PreconditionRequired

from .methods import Delete, GetMany, GetSingle, HttpMethods, Patch, Post
from .orm import OrmIntegration
from .pagination import BasePagination
from .fetcher import Fetcher
from .schemas import (EntityData, EntityMetaData, make_data_schema,
                      make_response_schema)
from .web_utils import MIMETYPE


[docs]class FlumpView(Patch, Delete, GetMany, GetSingle, Post): """ A base view from which all views provided to `FlumpBlueprint` must inherit. Classes which inherit from this must override: .. data:: ORM_INTEGRATION A class which inherits from :class:`.orm.OrmIntegration` .. data:: FETCHER A class which inherits from :class:`.fetcher.Fetcher`. They also may override: .. data:: HTTP_METHODS A set containing the HTTP Methods to use. See :class:`.methods.HttpMethods` .. data:: PAGINATOR A paginator to use, the default provides NO pagination. If overridden must inherit from :class:`.paginator.BasePagination` They MUST also provide provide `RESOURCE_NAME` & `SCHEMA` attributes that specify the name of the resource, and the schema to use for serialization/desieralization. They MAY also provide a `VIEW_NAME` attribute that will be used as the name of the flask view. This can be used in `url_for` calls. If not provided, this will default to `RESOURCE_NAME`. """ HTTP_METHODS = HttpMethods.ALL ORM_INTEGRATION = OrmIntegration FETCHER = Fetcher PAGINATOR = BasePagination URL_MAPPING = { HttpMethods.GET: '{}/<entity_id>', HttpMethods.GET_MANY: '{}', HttpMethods.PATCH: '{}/<entity_id>', HttpMethods.POST: '{}', HttpMethods.DELETE: '{}/<entity_id>' }
[docs] def get(self, entity_id=None, **kwargs): """ Handles HTTP GET requests. Dispatches to either :func:`flump.view.FlumpView.get` or :func:`flump.view.FlumpView.get_many` depending on whether an `entity_id` has been provided. :raises werkzeug.exceptions.NotImplemented: If the method requested has not been mixed in. """ if entity_id: return self.get_single(entity_id=entity_id, **kwargs) return self.get_many(**kwargs)
@property def data_schema(self): """ A Schema describing the main jsonapi format for `SCHEMA`. """ return make_data_schema(self.SCHEMA, self._get_sparse_fieldset()) @property def response_schema(self): """ A schema describing the format of a response according to jsonapi. """ return make_response_schema(self.SCHEMA, only=self._get_sparse_fieldset()) @property def fetcher(self): """ Instance cached instantiated version of :data:`.FlumpView.FETCHER`. """ if not getattr(self, '_fetcher', None): self._fetcher = self.FETCHER() return self._fetcher @property def paginator(self): """ Instance cached instantiated version of :data:`.FlumpView.PAGINATOR`. """ if not getattr(self, '_paginator', None): self._paginator = self.PAGINATOR(self.fetcher) return self._paginator @property def orm_integration(self): """ Instance cached instantiated version of :data:`.FlumpView.ORM_INTEGRATION`. """ if not getattr(self, '_orm_integration', None): self._orm_integration = self.ORM_INTEGRATION() return self._orm_integration def _get_sparse_fieldset(self): """ Returns a list of fields which have been requested to be returned. """ requested_fields = request.args.get( 'fields[{}]'.format(self.RESOURCE_NAME) ) if requested_fields: requested_fields = set(requested_fields.split(',')) return requested_fields def _verify_etag(self, entity): """ Verifies that the given etag is valid, if not raises a PreconditionFailed. """ if not request.headers.get('If-Match'): raise PreconditionRequired if not self._etag_matches(entity): raise PreconditionFailed def _get_etag(self, entity): """ :returns: String of the etag for the given entity. """ return str(entity.etag) def _etag_matches(self, entity): """ :returns: Boolean indicating whether the etag is valid. """ return any(i in request.if_match for i in (self._get_etag(entity), '*')) # noqa def _build_entity_data(self, entity): ''' Builds an EntityData struct for an entity. ''' return EntityData(entity.id, self.RESOURCE_NAME, entity, EntityMetaData(self._get_etag(entity)))
def _add_content_type(response): response = make_response(response) response.headers['Content-Type'] = MIMETYPE return response
[docs]class _FlumpMethodView(MethodView): """ :func:`flask.Blueprint.add_url_rule` does not allow us to pass instantiated instances of :class:`MethodView` as the view_func, so instead we store the instantiated instance of our route handlers on this class, and pass args and kwargs through to the view methods manually. """ def __init__(self, flump_view): self.flump_view = flump_view def get(self, *args, **kwargs): return _add_content_type(self.flump_view.get(*args, **kwargs)) def post(self, *args, **kwargs): return _add_content_type(self.flump_view.post(*args, **kwargs)) def delete(self, *args, **kwargs): return self.flump_view.delete(*args, **kwargs) def patch(self, *args, **kwargs): return _add_content_type(self.flump_view.patch(*args, **kwargs))