From adc7275da9166a46b4e9e8a07ca85f120637089f Mon Sep 17 00:00:00 2001 From: Carl Henderson Date: Wed, 11 May 2016 18:18:51 +0100 Subject: [PATCH] Include etag in `meta` section of each entities `data` --- flump/methods/get_many.py | 6 ++++-- flump/methods/get_single.py | 5 +++-- flump/methods/patch.py | 10 ++++++---- flump/methods/post.py | 13 ++++++++++--- flump/schemas.py | 12 ++++++++++-- test/methods/test_get_many.py | 19 ++++++++++--------- test/methods/test_get_single.py | 4 +++- test/methods/test_patch.py | 8 +++++--- test/methods/test_post.py | 3 ++- test/test_validators.py | 4 +++- 10 files changed, 56 insertions(+), 28 deletions(-) diff --git a/flump/methods/get_many.py b/flump/methods/get_many.py index 57603fe..421d86a 100644 --- a/flump/methods/get_many.py +++ b/flump/methods/get_many.py @@ -1,6 +1,8 @@ from flask import jsonify, request -from ..schemas import EntityData, make_response_schema, ManyResponseData +from ..schemas import ( + EntityData, make_response_schema, ManyResponseData, EntityMetaData +) class GetMany: @@ -29,7 +31,7 @@ def get_many(self, **kwargs): entities to be returned. """ entities = [ - EntityData(i.id, self.RESOURCE_NAME, i) + EntityData(i.id, self.RESOURCE_NAME, i, EntityMetaData(i.etag)) for i in self.get_many_entities(**kwargs) ] data = self._make_get_many_response(entities, **kwargs) diff --git a/flump/methods/get_single.py b/flump/methods/get_single.py index 4798597..8c80b46 100644 --- a/flump/methods/get_single.py +++ b/flump/methods/get_single.py @@ -2,7 +2,7 @@ from werkzeug.exceptions import NotFound -from ..schemas import EntityData, ResponseData +from ..schemas import EntityData, ResponseData, EntityMetaData class GetSingle: @@ -28,7 +28,8 @@ def get_single(self, entity_id, **kwargs): if self._etag_matches(entity): return '', 304 - entity_data = EntityData(entity.id, self.RESOURCE_NAME, entity) + entity_data = EntityData(entity.id, self.RESOURCE_NAME, + entity, EntityMetaData(entity.etag)) response_data, _ = self.response_schema(strict=True).dump( ResponseData(entity_data, {'self': request.url}) ) diff --git a/flump/methods/patch.py b/flump/methods/patch.py index 8c7c93e..349fd79 100644 --- a/flump/methods/patch.py +++ b/flump/methods/patch.py @@ -2,7 +2,9 @@ from werkzeug.exceptions import NotFound from ..exceptions import FlumpUnprocessableEntity -from ..schemas import ResponseData, make_entity_schema, make_data_schema +from ..schemas import ( + ResponseData, make_entity_schema, make_data_schema, EntityMetaData +) from ..web_utils import get_json @@ -65,9 +67,9 @@ def patch(self, entity_id, **kwargs): raise FlumpUnprocessableEntity(errors=errors) entity = self.update_entity(entity, entity_data.attributes) - - response_data = ResponseData(entity_data._replace(attributes=entity), - {'self': request.url}) + entity_data = entity_data._replace(attributes=entity, + meta=EntityMetaData(entity.etag)) + response_data = ResponseData(entity_data, {'self': request.url}) data, _ = self.response_schema(strict=True).dump(response_data) response = jsonify(data) diff --git a/flump/methods/post.py b/flump/methods/post.py index e11f72e..fa8254b 100644 --- a/flump/methods/post.py +++ b/flump/methods/post.py @@ -3,7 +3,9 @@ from ..exceptions import FlumpUnprocessableEntity -from ..schemas import ResponseData, make_entity_schema, make_data_schema +from ..schemas import ( + ResponseData, make_entity_schema, make_data_schema, EntityMetaData +) from ..web_utils import url_for, get_json @@ -62,10 +64,15 @@ def post(self, **kwargs): entity_id=entity_data.attributes.id, _method='GET', **kwargs) schema = self.response_schema(strict=True) - response_data = ResponseData(entity_data, {'self': url}) + + etag = entity_data.attributes.etag + response_data = ResponseData( + entity_data._replace(meta=EntityMetaData(etag)), + {'self': url} + ) data, _ = schema.dump(response_data) response = jsonify(data) response.headers['Location'] = url - response.set_etag(str(entity_data.attributes.etag)) + response.set_etag(str(etag)) return response, 201 diff --git a/flump/schemas.py b/flump/schemas.py index af06cdb..852ae77 100644 --- a/flump/schemas.py +++ b/flump/schemas.py @@ -6,13 +6,19 @@ from .exceptions import FlumpUnprocessableEntity -EntityData = namedtuple('EntityData', ('id', 'type', 'attributes')) +EntityData = namedtuple('EntityData', ('id', 'type', 'attributes', 'meta')) ResponseData = namedtuple('ResponseData', ('data', 'links')) +EntityMetaData = namedtuple('EntityMetaData', ('etag')) + ManyResponseData = namedtuple('ManyResponseData', ('data', 'links', 'meta')) +class EntityMetaSchema(Schema): + etag = fields.Str(dump_only=True) + + def make_data_schema( resource_schema, only=None, partial=False, id_required=False ): @@ -37,6 +43,7 @@ class JsonApiSchema(Schema): type = fields.Str(required=True) attributes = fields.Nested(resource_schema, required=True, only=only, partial=partial) + meta = fields.Nested(EntityMetaSchema, dump_only=True) @post_load def to_entity_data(self, data): @@ -45,7 +52,8 @@ def to_entity_data(self, data): namedtuple format. When loading we do not have an ID so this will be None. """ - return EntityData(data.get('id'), data['type'], data['attributes']) + return EntityData(data.get('id'), data['type'], + data['attributes'], None) @pre_dump def add_id_to_schema(self, entity_data): diff --git a/test/methods/test_get_many.py b/test/methods/test_get_many.py index 630dee8..6c8de7b 100644 --- a/test/methods/test_get_many.py +++ b/test/methods/test_get_many.py @@ -1,3 +1,4 @@ +from mock import ANY import pytest from flump.web_utils import url_for @@ -20,15 +21,15 @@ def test_get_many(self, flask_client): 'data': [ { 'attributes': {'name': 'Carl', 'age': 26}, - 'id': '1', 'type': 'user' + 'id': '1', 'type': 'user', 'meta': {'etag': ANY} }, { 'attributes': {'name': 'Carl', 'age': 26}, - 'id': '2', 'type': 'user' + 'id': '2', 'type': 'user', 'meta': {'etag': ANY} }, { 'attributes': {'name': 'Carl', 'age': 26}, - 'id': '3', 'type': 'user' + 'id': '3', 'type': 'user', 'meta': {'etag': ANY} } ], 'links': {'self': 'http://localhost/tester/user/'} @@ -80,11 +81,11 @@ def test_get_many(self, flask_client): 'data': [ { 'attributes': {'name': 'Carl', 'age': 26}, - 'id': '1', 'type': 'user' + 'id': '1', 'type': 'user', 'meta': {'etag': ANY} }, { 'attributes': {'name': 'Carl', 'age': 26}, - 'id': '2', 'type': 'user' + 'id': '2', 'type': 'user', 'meta': {'etag': ANY} } ], 'links': { @@ -104,7 +105,7 @@ def test_get_many(self, flask_client): 'data': [ { 'attributes': {'name': 'Carl', 'age': 26}, - 'id': '3', 'type': 'user' + 'id': '3', 'type': 'user', 'meta': {'etag': ANY} } ], 'links': { @@ -133,15 +134,15 @@ def test_get_many_uses_query_string(self, flask_client): 'data': [ { 'attributes': {'name': 'Carl', 'age': 26}, - 'id': '4', 'type': 'user' + 'id': '4', 'type': 'user', 'meta': {'etag': ANY} }, { 'attributes': {'name': 'Carl', 'age': 26}, - 'id': '5', 'type': 'user' + 'id': '5', 'type': 'user', 'meta': {'etag': ANY} }, { 'attributes': {'name': 'Carl', 'age': 26}, - 'id': '6', 'type': 'user' + 'id': '6', 'type': 'user', 'meta': {'etag': ANY} } ], 'links': { diff --git a/test/methods/test_get_single.py b/test/methods/test_get_single.py index ad87ad0..58eec57 100644 --- a/test/methods/test_get_single.py +++ b/test/methods/test_get_single.py @@ -1,3 +1,5 @@ +from mock import ANY + from ..helpers import create_user, get_user @@ -8,7 +10,7 @@ def test_get(flask_client): assert response.json == { 'data': { 'attributes': {'name': 'Carl', 'age': 26}, - 'id': '1', 'type': 'user' + 'id': '1', 'type': 'user', 'meta': {'etag': ANY} }, 'links': {'self': 'http://localhost/tester/user/1'} } diff --git a/test/methods/test_patch.py b/test/methods/test_patch.py index 6c362d4..0e6d915 100644 --- a/test/methods/test_patch.py +++ b/test/methods/test_patch.py @@ -1,3 +1,5 @@ +from mock import ANY + from ..helpers import create_user, patch_user @@ -12,7 +14,7 @@ def test_patch(flask_client): assert response.json == { 'data': { 'attributes': {'name': 'Carly', 'age': 27}, - 'id': '1', 'type': 'user' + 'id': '1', 'type': 'user', 'meta': {'etag': ANY} }, 'links': {'self': 'http://localhost/tester/user/1'} } @@ -44,7 +46,7 @@ def test_patch_updates_only_specified_field(flask_client): assert response.json == { 'data': { 'attributes': {'name': 'Carly', 'age': 26}, - 'id': '1', 'type': 'user' + 'id': '1', 'type': 'user', 'meta': {'etag': ANY} }, 'links': {'self': 'http://localhost/tester/user/1'} } @@ -72,7 +74,7 @@ def test_patch_works_with_wildcard_etag(flask_client): assert response.json == { 'data': { 'attributes': {'name': 'Carly', 'age': 27}, - 'id': '1', 'type': 'user' + 'id': '1', 'type': 'user', 'meta': {'etag': ANY} }, 'links': {'self': 'http://localhost/tester/user/1'} } diff --git a/test/methods/test_post.py b/test/methods/test_post.py index b7f9295..d7e9f61 100644 --- a/test/methods/test_post.py +++ b/test/methods/test_post.py @@ -1,4 +1,5 @@ from flump.web_utils import url_for +from mock import ANY from ..helpers import create_user @@ -9,7 +10,7 @@ def test_post(flask_client): assert response.json == { 'data': { 'attributes': {'name': 'Carl', 'age': 26}, - 'type': 'user', 'id': '1' + 'type': 'user', 'id': '1', 'meta': {'etag': ANY} }, 'links': {'self': 'http://localhost/tester/user/1'} } diff --git a/test/test_validators.py b/test/test_validators.py index 2d982e5..de292c5 100644 --- a/test/test_validators.py +++ b/test/test_validators.py @@ -1,4 +1,5 @@ from marshmallow import fields +from mock import ANY import pytest from flump.validators import Immutable @@ -51,7 +52,8 @@ def test_patch_does_not_enforce_name_being_present(self, flask_client): 'data': { 'attributes': {'age': 99, 'name': 'Carl'}, 'id': '1', - 'type': 'user' + 'type': 'user', + 'meta': {'etag': ANY} }, 'links': {'self': 'http://localhost/tester/user/1'} }