From 3c59683f8a2dc7951d3194ebd24b1de37e8421a1 Mon Sep 17 00:00:00 2001 From: Deryck Hodge Date: Wed, 8 Feb 2017 14:35:11 -0800 Subject: [PATCH] Revert "BROKEN: Starting implementation of query params properly" This reverts commit 4dcff7d78f9393152f4ddd2c150ea211f9bb305b. --- CHANGES.md | 3 +- setup.py | 8 +- sqlalchemy_jsonapi/__init__.py | 3 +- sqlalchemy_jsonapi/constants.py | 3 +- sqlalchemy_jsonapi/flaskext.py | 10 +- sqlalchemy_jsonapi/serializer.py | 148 ++++++++---------- sqlalchemy_jsonapi/tests/app.py | 22 +-- sqlalchemy_jsonapi/tests/conftest.py | 24 +-- .../tests/test_collection_get.py | 44 ++---- .../tests/test_collection_post.py | 75 ++------- sqlalchemy_jsonapi/tests/test_related_get.py | 17 +- .../tests/test_relationship_delete.py | 22 ++- .../tests/test_relationship_get.py | 32 ++-- .../tests/test_relationship_patch.py | 52 +++--- .../tests/test_relationship_post.py | 25 ++- .../tests/test_resource_delete.py | 3 - sqlalchemy_jsonapi/tests/test_resource_get.py | 19 +-- .../tests/test_resource_patch.py | 20 +-- sqlalchemy_jsonapi/tests/test_serializer.py | 3 +- sqlalchemy_jsonapi/version.py | 1 - 20 files changed, 205 insertions(+), 329 deletions(-) delete mode 100644 sqlalchemy_jsonapi/version.py diff --git a/CHANGES.md b/CHANGES.md index ad62544..71cd9e0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,9 @@ # SQLAlchemy-JSONAPI Changelog -## 5.0.0 +## 4.0.9 *Unreleased* -* BREAKING: The query data is now expected for all endpoints * Fixed bug during testing in delete_relationship where returned resource was missing data key * Fixed bug during testing in patch_resource where field check was failing diff --git a/setup.py b/setup.py index a7b7806..4f72f56 100644 --- a/setup.py +++ b/setup.py @@ -14,18 +14,14 @@ from setuptools import setup import sys -import ast requirements = ['SQLAlchemy', 'inflection'] -with open('mynewleaf/__version__.py', 'r') as f: - version = ast.parse(f.read()).body[0].value.s - if sys.version_info[0] != 3 or sys.version_info[1] < 4: - requirements.append('enum34') + requirements.append('enum34') setup(name='SQLAlchemy-JSONAPI', - version=version, + version='4.0.9', url='http://github.com/coltonprovias/sqlalchemy-jsonapi', license='MIT', author='Colton J. Provias', diff --git a/sqlalchemy_jsonapi/__init__.py b/sqlalchemy_jsonapi/__init__.py index ad95029..9f9fd7f 100644 --- a/sqlalchemy_jsonapi/__init__.py +++ b/sqlalchemy_jsonapi/__init__.py @@ -3,9 +3,8 @@ AttributeActions, Permissions, RelationshipActions, attr_descriptor, permission_test, relationship_descriptor) -from .version import __version__ try: from .flaskext import FlaskJSONAPI except ImportError: - FlaskJSONAPI = None + FlaskJSONAPI = None \ No newline at end of file diff --git a/sqlalchemy_jsonapi/constants.py b/sqlalchemy_jsonapi/constants.py index b1501a9..c7b519a 100644 --- a/sqlalchemy_jsonapi/constants.py +++ b/sqlalchemy_jsonapi/constants.py @@ -5,6 +5,7 @@ MIT License """ + try: from enum import Enum except ImportError: @@ -26,4 +27,4 @@ class Endpoint(Enum): COLLECTION = '/' RESOURCE = '//' RELATED = '///' - RELATIONSHIP = '///relationships/' + RELATIONSHIP = '///relationships/' \ No newline at end of file diff --git a/sqlalchemy_jsonapi/flaskext.py b/sqlalchemy_jsonapi/flaskext.py index 1e8f6ce..2548d32 100644 --- a/sqlalchemy_jsonapi/flaskext.py +++ b/sqlalchemy_jsonapi/flaskext.py @@ -163,11 +163,7 @@ def _setup_adapter(self, namespace, route_prefix): :param namespace: Prefix for generated endpoints :param route_prefix: Prefix for route patterns """ - self.serializer = JSONAPI(self.sqla.Model, - prefix='{}://{}{}'.format( - self.app.config['PREFERRED_URL_SCHEME'], - self.app.config['SERVER_NAME'], - route_prefix)) + self.serializer = JSONAPI(self.sqla.Model, prefix='{}://{}{}'.format(self.app.config['PREFERRED_URL_SCHEME'], self.app.config['SERVER_NAME'], route_prefix)) for view in views: method, endpoint = view pattern = route_prefix + endpoint.value @@ -224,8 +220,8 @@ def new_view(**kwargs): try: attr = '{}_{}'.format(method.name, endpoint.name).lower() handler = getattr(self.serializer, attr) - handler_chain = list(self._handler_chains.get((kwargs[ - 'api_type'], method, endpoint), [])) + handler_chain = list(self._handler_chains.get(( + kwargs['api_type'], method, endpoint), [])) handler_chain.append(handler) chained_handler = self._call_next(handler_chain) response = chained_handler(*args) diff --git a/sqlalchemy_jsonapi/serializer.py b/sqlalchemy_jsonapi/serializer.py index 78dcaea..16dcef1 100644 --- a/sqlalchemy_jsonapi/serializer.py +++ b/sqlalchemy_jsonapi/serializer.py @@ -5,18 +5,19 @@ MIT License """ +from collections import MutableMapping from enum import Enum -from inflection import dasherize, tableize, underscore +from inflection import pluralize, dasherize, parameterize, tableize, underscore from sqlalchemy.exc import IntegrityError from sqlalchemy.orm.interfaces import MANYTOONE from sqlalchemy.util.langhelpers import iterate_attributes +from pprint import pprint from .errors import (BadRequestError, InvalidTypeForEndpointError, MissingTypeError, NotSortableError, PermissionDeniedError, RelationshipNotFoundError, ResourceNotFoundError, ResourceTypeNotFoundError, ToManyExpectedError, ValidationError) -from .version import __version__ class AttributeActions(Enum): @@ -139,7 +140,7 @@ def __init__(self): self.status_code = 200 self.data = { 'jsonapi': {'version': '1.0'}, - 'meta': {'sqlalchemy_jsonapi_version': __version__} + 'meta': {'sqlalchemy_jsonapi_version': '4.0.9'} } @@ -226,8 +227,7 @@ def __init__(self, base, prefix=''): continue prepped_name = self._api_type_for_model(model) - api_type = getattr(model, '__jsonapi_type_override__', - prepped_name) + api_type = getattr(model, '__jsonapi_type_override__', prepped_name) model_keys = set(model.__mapper__.all_orm_descriptors.keys()) model_keys |= set(model.__mapper__.relationships.keys()) @@ -236,14 +236,8 @@ def __init__(self, base, prefix=''): model.__jsonapi_rel_desc__ = {} model.__jsonapi_permissions__ = {} model.__jsonapi_type__ = api_type - model.__jsonapi_map_to_py__ = { - dasherize(underscore(x)): x - for x in model_keys - } - model.__jsonapi_map_to_api__ = { - v: k - for k, v in model.__jsonapi_map_to_py__.items() - } + model.__jsonapi_map_to_py__ = {dasherize(underscore(x)): x for x in model_keys} + model.__jsonapi_map_to_api__ = {x: dasherize(underscore(x)) for x in model_keys} for prop_name, prop_value in iterate_attributes(model): @@ -298,8 +292,7 @@ def _fetch_model(self, api_type): def _lazy_relationship(self, api_type, obj_id, rel_key): return { - 'self': '{}/{}/{}/relationships/{}'.format(self.prefix, api_type, - obj_id, rel_key), + 'self': '{}/{}/{}/relationships/{}'.format(self.prefix, api_type, obj_id, rel_key), 'related': '{}/{}/{}/{}'.format(self.prefix, api_type, obj_id, rel_key) } @@ -366,15 +359,13 @@ def _render_full_resource(self, instance, include, fields): } attrs_to_ignore = {'__mapper__', 'id'} if api_type in fields.keys(): - local_fields = list(map(( - lambda x: instance.__jsonapi_map_to_py__[x]), fields[ - api_type])) + local_fields = list(map((lambda x: instance.__jsonapi_map_to_py__[x]), fields[api_type])) else: local_fields = orm_desc_keys for key, relationship in instance.__mapper__.relationships.items(): - attrs_to_ignore |= set( - [c.name for c in relationship.local_columns]) | {key} + attrs_to_ignore |= set([c.name for c in relationship.local_columns + ]) | {key} api_key = instance.__jsonapi_map_to_api__[key] @@ -393,29 +384,23 @@ def _render_full_resource(self, instance, include, fields): if api_key in include.keys(): related = desc(instance) if related is not None: - perm = get_permission_test(related, None, - Permissions.VIEW) - if key in local_fields and (related is None or - not perm(related)): + perm = get_permission_test(related, None, Permissions.VIEW) + if key in local_fields and (related is None or not perm(related)): to_ret['relationships'][api_key]['data'] = None continue if key in local_fields: - to_ret['relationships'][api_key][ - 'data'] = self._render_short_instance(related) + to_ret['relationships'][api_key]['data'] = self._render_short_instance(related) new_include = self._parse_include(include[api_key]) - built = self._render_full_resource(related, new_include, - fields) + built = self._render_full_resource(related, new_include, fields) included = built.pop('included') to_ret['included'].update(included) - to_ret['included'][(related.__jsonapi_type__, - related.id)] = built + to_ret['included'][(related.__jsonapi_type__, related.id)] = built else: if key in local_fields: to_ret['relationships'][api_key] = { - 'links': self._lazy_relationship(api_type, instance.id, - api_key), + 'links': self._lazy_relationship(api_type, instance.id, api_key), } if api_key not in include.keys(): @@ -433,23 +418,20 @@ def _render_full_resource(self, instance, include, fields): continue if key in local_fields: - to_ret['relationships'][api_key]['data'].append( - self._render_short_instance(item)) + to_ret['relationships'][api_key]['data'].append(self._render_short_instance(item)) new_include = self._parse_include(include[api_key]) built = self._render_full_resource(item, new_include, fields) included = built.pop('included') to_ret['included'].update(included) - to_ret['included'][(item.__jsonapi_type__, - item.id)] = built + to_ret['included'][(item.__jsonapi_type__, item.id)] = built for key in set(orm_desc_keys) - attrs_to_ignore: try: desc = get_attr_desc(instance, key, AttributeActions.GET) if key in local_fields: - to_ret['attributes'][instance.__jsonapi_map_to_api__[ - key]] = desc(instance) + to_ret['attributes'][instance.__jsonapi_map_to_api__[key]] = desc(instance) except PermissionDeniedError: continue @@ -744,8 +726,8 @@ def get_related(self, session, query, api_type, obj_id, rel_key): for item in related: try: - response.data['data'].append(self._render_full_resource( - item, {}, {})) + response.data['data'].append( + self._render_full_resource(item, {}, {})) except PermissionDeniedError: continue @@ -774,7 +756,7 @@ def get_relationship(self, session, query, api_type, obj_id, rel_key): RelationshipActions.GET)(resource) if relationship.direction == MANYTOONE: - if related is None: + if related == None: response.data['data'] = None else: try: @@ -786,8 +768,8 @@ def get_relationship(self, session, query, api_type, obj_id, rel_key): response.data['data'] = [] for item in related: try: - response.data['data'].append(self._render_short_instance( - item)) + response.data['data'].append( + self._render_short_instance(item)) except PermissionDeniedError: continue @@ -819,7 +801,7 @@ def patch_relationship(self, session, json_data, api_type, obj_id, try: if relationship.direction == MANYTOONE: if not isinstance(json_data['data'], dict)\ - and json_data['data'] is not None: + and json_data['data'] != None: raise ValidationError('Provided data must be a hash.') related = getattr(resource, relationship.key) @@ -829,7 +811,7 @@ def patch_relationship(self, session, json_data, api_type, obj_id, setter = get_rel_desc(resource, relationship.key, RelationshipActions.SET) - if json_data['data'] is None: + if json_data['data'] == None: setter(resource, None) else: to_relate = self._fetch_resource( @@ -906,9 +888,10 @@ def patch_resource(self, session, json_data, api_type, obj_id): - set(resource.__jsonapi_map_to_py__.keys()) if missing_keys: - raise BadRequestError('{} not relationships for {}.{}'.format( - ', '.join(list(missing_keys)), model.__jsonapi_type__, - resource.id)) + raise BadRequestError( + '{} not relationships for {}.{}'.format( + ', '.join(list(missing_keys)), + model.__jsonapi_type__, resource.id)) attrs_to_ignore = {'__mapper__', 'id'} @@ -926,20 +909,18 @@ def patch_resource(self, session, json_data, api_type, obj_id): session, json_data['data']['relationships'][api_key], model.__jsonapi_type__, resource.id, api_key) - data_keys = set(map(( - lambda x: resource.__jsonapi_map_to_py__.get(x, None)), - json_data['data']['attributes'].keys())) + data_keys = set(map((lambda x: resource.__jsonapi_map_to_py__.get(x, None)), json_data['data']['attributes'].keys())) model_keys = set(orm_desc_keys) - attrs_to_ignore if not data_keys <= model_keys: - raise BadRequestError('{} not attributes for {}.{}'.format( - ', '.join(list(data_keys - model_keys)), - model.__jsonapi_type__, resource.id)) + raise BadRequestError( + '{} not attributes for {}.{}'.format( + ', '.join(list(data_keys - model_keys)), + model.__jsonapi_type__, resource.id)) for key in data_keys & model_keys: setter = get_attr_desc(resource, key, AttributeActions.SET) - setter(resource, json_data['data']['attributes'][ - resource.__jsonapi_map_to_api__[key]]) + setter(resource, json_data['data']['attributes'][resource.__jsonapi_map_to_api__[key]]) session.commit() except IntegrityError as e: session.rollback() @@ -950,8 +931,8 @@ def patch_resource(self, session, json_data, api_type, obj_id): except TypeError as e: session.rollback() raise ValidationError('Incompatible data type') - return self.get_resource(session, {}, model.__jsonapi_type__, - resource.id) + return self.get_resource( + session, {}, model.__jsonapi_type__, resource.id) def post_collection(self, session, data, api_type): """ @@ -970,8 +951,8 @@ def post_collection(self, session, data, api_type): raise MissingTypeError() if data['data']['type'] != model.__jsonapi_type__: - raise InvalidTypeForEndpointError(model.__jsonapi_type__, - data['data']['type']) + raise InvalidTypeForEndpointError( + model.__jsonapi_type__, data['data']['type']) resource = model() check_permission(resource, None, Permissions.CREATE) @@ -979,14 +960,13 @@ def post_collection(self, session, data, api_type): data['data'].setdefault('relationships', {}) data['data'].setdefault('attributes', {}) - data_keys = set(map(( - lambda x: resource.__jsonapi_map_to_py__.get(x, None)), data[ - 'data'].get('relationships', {}).keys())) + data_keys = set(map((lambda x: resource.__jsonapi_map_to_py__.get(x, None)), data['data'].get('relationships', {}).keys())) model_keys = set(resource.__mapper__.relationships.keys()) if not data_keys <= model_keys: - raise BadRequestError('{} not relationships for {}'.format( - ', '.join(list(data_keys - model_keys)), - model.__jsonapi_type__)) + raise BadRequestError( + '{} not relationships for {}'.format( + ', '.join(list(data_keys - + model_keys)), model.__jsonapi_type__)) attrs_to_ignore = {'__mapper__', 'id'} @@ -1018,8 +998,8 @@ def post_collection(self, session, data, api_type): setters.append([setter, None]) else: if not isinstance(data_rel, dict): - raise BadRequestError('{} must be a hash'.format( - key)) + raise BadRequestError( + '{} must be a hash'.format(key)) if not {'type', 'id'} == set(data_rel.keys()): raise BadRequestError( '{} must have type and id keys'.format(key)) @@ -1038,8 +1018,8 @@ def post_collection(self, session, data, api_type): setter = get_rel_desc(resource, key, RelationshipActions.APPEND) if not isinstance(data_rel, list): - raise BadRequestError('{} must be an array'.format( - key)) + raise BadRequestError( + '{} must be an array'.format(key)) for item in data_rel: if not {'type', 'id'} in set(item.keys()): raise BadRequestError( @@ -1056,15 +1036,14 @@ def post_collection(self, session, data, api_type): Permissions.CREATE) setters.append([setter, to_relate]) - data_keys = set(map(( - lambda x: resource.__jsonapi_map_to_py__.get(x, None)), data[ - 'data'].get('attributes', {}).keys())) + data_keys = set(map((lambda x: resource.__jsonapi_map_to_py__.get(x, None)), data['data'].get('attributes', {}).keys())) model_keys = set(orm_desc_keys) - attrs_to_ignore if not data_keys <= model_keys: - raise BadRequestError('{} not attributes for {}'.format( - ', '.join(list(data_keys - model_keys)), - model.__jsonapi_type__)) + raise BadRequestError( + '{} not attributes for {}'.format( + ', '.join(list(data_keys - + model_keys)), model.__jsonapi_type__)) with session.no_autoflush: for setter, value in setters: @@ -1088,8 +1067,8 @@ def post_collection(self, session, data, api_type): session.rollback() raise ValidationError('Incompatible data type') session.refresh(resource) - response = self.get_resource(session, {}, model.__jsonapi_type__, - resource.id) + response = self.get_resource( + session, {}, model.__jsonapi_type__, resource.id) response.status_code = 201 return response @@ -1125,13 +1104,14 @@ def post_relationship(self, session, json_data, api_type, obj_id, rel_key): RelationshipActions.APPEND) if not isinstance(json_data['data'], list): - raise BadRequestError('{} must be an array'.format( - relationship.key)) + raise BadRequestError( + '{} must be an array'.format(relationship.key)) for item in json_data['data']: if {'type', 'id'} != set(item.keys()): - raise BadRequestError('{} must have type and id keys' - .format(relationship.key)) + raise BadRequestError( + '{} must have type and id keys' + .format(relationship.key)) to_relate = self._fetch_resource( session, item['type'], item['id'], Permissions.EDIT) @@ -1154,5 +1134,5 @@ def post_relationship(self, session, json_data, api_type, obj_id, rel_key): except KeyError: raise ValidationError('Incompatible type provided') - return self.get_relationship(session, {}, model.__jsonapi_type__, - resource.id, rel_key) + return self.get_relationship( + session, {}, model.__jsonapi_type__, resource.id, rel_key) diff --git a/sqlalchemy_jsonapi/tests/app.py b/sqlalchemy_jsonapi/tests/app.py index ab262ce..386fe75 100644 --- a/sqlalchemy_jsonapi/tests/app.py +++ b/sqlalchemy_jsonapi/tests/app.py @@ -12,9 +12,7 @@ from sqlalchemy import Boolean, Column, ForeignKey, Unicode, UnicodeText from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import backref, relationship, validates -from sqlalchemy_jsonapi import (INTERACTIVE_PERMISSIONS, Endpoint, - FlaskJSONAPI, Method, Permissions, - permission_test) +from sqlalchemy_jsonapi import FlaskJSONAPI, Permissions, permission_test, Method, Endpoint, INTERACTIVE_PERMISSIONS from sqlalchemy_utils import EmailType, PasswordType, Timestamp, UUIDType app = Flask(__name__) @@ -36,9 +34,9 @@ class User(Timestamp, db.Model): id = Column(UUIDType, default=uuid4, primary_key=True) username = Column(Unicode(30), unique=True, nullable=False) email = Column(EmailType, nullable=False) - password = Column( - PasswordType(schemes=['bcrypt']), - nullable=False) + password = Column(PasswordType(schemes=['bcrypt']), + nullable=False, + info={'allow_serialize': False}) is_admin = Column(Boolean, default=False) @hybrid_property @@ -106,7 +104,8 @@ class BlogPost(Timestamp, db.Model): author = relationship('User', lazy='joined', - backref=backref('posts', lazy='dynamic')) + backref=backref('posts', + lazy='dynamic')) @validates('title') def validate_title(self, key, title): @@ -137,7 +136,8 @@ class BlogComment(Timestamp, db.Model): post = relationship('BlogPost', lazy='joined', - backref=backref('comments', lazy='dynamic')) + backref=backref('comments', + lazy='dynamic')) author = relationship('User', lazy='joined', backref=backref('comments', @@ -152,10 +152,12 @@ class Log(Timestamp, db.Model): post = relationship('BlogPost', lazy='joined', - backref=backref('logs', lazy='dynamic')) + backref=backref('logs', + lazy='dynamic')) user = relationship('User', lazy='joined', - backref=backref('logs', lazy='dynamic')) + backref=backref('logs', + lazy='dynamic')) @permission_test(INTERACTIVE_PERMISSIONS) def block_interactive(cls): diff --git a/sqlalchemy_jsonapi/tests/conftest.py b/sqlalchemy_jsonapi/tests/conftest.py index 9e93e06..e028a30 100644 --- a/sqlalchemy_jsonapi/tests/conftest.py +++ b/sqlalchemy_jsonapi/tests/conftest.py @@ -61,8 +61,8 @@ def validate(self, status_code, error=None): if error: assert self.status_code == error.status_code assert self.json_data['errors'][0]['code'] == error.code - assert self.json_data['errors'][0][ - 'status'] == error.status_code + assert self.json_data['errors'][0]['status' + ] == error.status_code return self @@ -88,9 +88,9 @@ def user(session): @pytest.fixture def post(user, session): new_post = BlogPost(author=user, - title=fake.sentence(), - content=fake.paragraph(), - is_published=True) + title=fake.sentence(), + content=fake.paragraph(), + is_published=True) session.add(new_post) session.commit() return new_post @@ -99,9 +99,9 @@ def post(user, session): @pytest.fixture def unpublished_post(user, session): new_post = BlogPost(author=user, - title=fake.sentence(), - content=fake.paragraph(), - is_published=False) + title=fake.sentence(), + content=fake.paragraph(), + is_published=False) session.add(new_post) session.commit() return new_post @@ -111,12 +111,12 @@ def unpublished_post(user, session): def bunch_of_posts(user, session): for x in range(30): new_post = BlogPost(author=user, - title=fake.sentence(), - content=fake.paragraph(), - is_published=fake.boolean()) + title=fake.sentence(), + content=fake.paragraph(), + is_published=fake.boolean()) session.add(new_post) new_post.comments.append(BlogComment(author=user, - content=fake.paragraph())) + content=fake.paragraph())) session.commit() diff --git a/sqlalchemy_jsonapi/tests/test_collection_get.py b/sqlalchemy_jsonapi/tests/test_collection_get.py index a66a7e6..8442eb8 100644 --- a/sqlalchemy_jsonapi/tests/test_collection_get.py +++ b/sqlalchemy_jsonapi/tests/test_collection_get.py @@ -1,10 +1,5 @@ -from sqlalchemy_jsonapi.errors import BadRequestError, NotSortableError - - -# TODO: Ember-style filtering -# TODO: Simple filtering -# TODO: Complex filtering -# TODO: Bad query param +from sqlalchemy_jsonapi.errors import ( + BadRequestError, NotAnAttributeError, NotSortableError) def test_200_with_no_querystring(bunch_of_posts, client): @@ -21,43 +16,31 @@ def test_200_with_single_included_model(bunch_of_posts, client): def test_200_with_including_model_and_including_inbetween(bunch_of_posts, client): - response = client.get('/api/blog-comments/?include=post.author').validate( - 200) + response = client.get('/api/blog-comments/?include=post.author').validate(200) assert response.json_data['data'][0]['type'] == 'blog-comments' for data in response.json_data['included']: assert data['type'] in ['blog-posts', 'users'] def test_200_with_multiple_includes(bunch_of_posts, client): - response = client.get('/api/blog-posts/?include=comments,author').validate( - 200) + response = client.get('/api/blog-posts/?include=comments,author').validate(200) assert response.json_data['data'][0]['type'] == 'blog-posts' for data in response.json_data['included']: assert data['type'] in ['blog-comments', 'users'] def test_200_with_single_field(bunch_of_posts, client): - response = client.get( - '/api/blog-posts/?fields[blog-posts]=title').validate(200) + response = client.get('/api/blog-posts/?fields[blog-posts]=title').validate(200) for item in response.json_data['data']: assert {'title'} == set(item['attributes'].keys()) assert len(item['relationships']) == 0 -def test_200_with_bad_field(bunch_of_posts, client): - response = client.get( - '/api/blog-posts/?fields[blog-posts]=titles').validate(200) - for item in response.json_data['data']: - assert {} == set(item['attributes'].keys()) - assert len(item['relationships']) == 0 - def test_200_with_multiple_fields(bunch_of_posts, client): - response = client.get( - '/api/blog-posts/?fields[blog-posts]=title,content,is-published').validate( - 200) + response = client.get('/api/blog-posts/?fields[blog-posts]=title,content,is-published').validate( + 200) for item in response.json_data['data']: - assert {'title', 'content', 'is-published'} == set(item[ - 'attributes'].keys()) + assert {'title', 'content', 'is-published'} == set(item['attributes'].keys()) assert len(item['relationships']) == 0 @@ -102,20 +85,19 @@ def test_409_when_given_a_missing_field_for_sorting(bunch_of_posts, client): def test_200_paginated_response_by_page(bunch_of_posts, client): - response = client.get( - '/api/blog-posts/?page[number]=2&page[size]=5').validate(200) + response = client.get('/api/blog-posts/?page[number]=2&page[size]=5').validate( + 200) assert len(response.json_data['data']) == 5 def test_200_paginated_response_by_offset(bunch_of_posts, client): - response = client.get( - '/api/blog-posts/?page[offset]=5&page[limit]=5').validate(200) + response = client.get('/api/blog-posts/?page[offset]=5&page[limit]=5').validate( + 200) assert len(response.json_data['data']) == 5 def test_200_when_pagination_is_out_of_range(bunch_of_posts, client): - client.get('/api/blog-posts/?page[offset]=999999&page[limit]=5').validate( - 200) + client.get('/api/blog-posts/?page[offset]=999999&page[limit]=5').validate(200) def test_400_when_provided_crap_data_for_pagination(bunch_of_posts, client): diff --git a/sqlalchemy_jsonapi/tests/test_collection_post.py b/sqlalchemy_jsonapi/tests/test_collection_post.py index d01dab6..8dba8e2 100644 --- a/sqlalchemy_jsonapi/tests/test_collection_post.py +++ b/sqlalchemy_jsonapi/tests/test_collection_post.py @@ -8,7 +8,6 @@ fake = Faker() -# TODO: Bad query param def test_200_resource_creation(client): payload = { @@ -21,10 +20,10 @@ def test_200_resource_creation(client): } } } - response = client.post( - '/api/users/', - data=json.dumps(payload), - content_type='application/vnd.api+json').validate(201) + response = client.post('/api/users/', + data=json.dumps(payload), + content_type='application/vnd.api+json').validate( + 201) assert response.json_data['data']['type'] == 'users' user_id = response.json_data['data']['id'] response = client.get('/api/users/{}/'.format(user_id)).validate(200) @@ -49,68 +48,16 @@ def test_200_resource_creation_with_relationships(user, client): } } } - response = client.post( - '/api/blog-posts/', - data=json.dumps(payload), - content_type='application/vnd.api+json').validate(201) + response = client.post('/api/blog-posts/', + data=json.dumps(payload), + content_type='application/vnd.api+json').validate( + 201) assert response.json_data['data']['type'] == 'blog-posts' - assert len(response.json_data['included']) == 0 post_id = response.json_data['data']['id'] - response = client.get('/api/blog-posts/{}/'.format( - post_id)).validate(200) - -def test_200_resource_creation_with_relationships_and_include(user, client): - payload = { - 'data': { - 'type': 'blog-posts', - 'attributes': { - 'title': 'Some title', - 'content': 'Hello, World!', - 'is-published': True - }, - 'relationships': { - 'author': { - 'data': { - 'type': 'users', - 'id': str(user.id) - } - } - } - } - } - response = client.post( - '/api/blog-posts/?include=author', - data=json.dumps(payload), - content_type='application/vnd.api+json').validate(201) - assert response.json_data['data']['type'] == 'blog-posts' + response = client.get('/api/blog-posts/{}/?include=author'.format(post_id)).validate(200) assert response.json_data['data']['relationships']['author']['data'][ - 'id'] == str(user.id) - assert len(response.json_data['included']) == 1 - for data in response.json_data['included']: - assert data['type'] == 'users' - post_id = response.json_data['data']['id'] - response = client.get('/api/blog-posts/{}/?include=author'.format( - post_id)).validate(200) - -def test_200_resource_creation_with_sparse_fieldset(client): - payload = { - 'data': { - 'type': 'users', - 'attributes': { - 'username': fake.user_name(), - 'email': 'user@example.com', - 'password': 'password' - } - } - } - response = client.post( - '/api/users/?fields[users]=username', - data=json.dumps(payload), - content_type='application/vnd.api+json').validate(201) - assert response.json_data['data']['type'] == 'users' - assert set(response.json_data['data']['attributes'].keys()) == set(['username']) - user_id = response.json_data['data']['id'] - response = client.get('/api/users/{}/'.format(user_id)).validate(200) + 'id' + ] == str(user.id) def test_403_when_access_is_denied(client): diff --git a/sqlalchemy_jsonapi/tests/test_related_get.py b/sqlalchemy_jsonapi/tests/test_related_get.py index 7345174..225d028 100644 --- a/sqlalchemy_jsonapi/tests/test_related_get.py +++ b/sqlalchemy_jsonapi/tests/test_related_get.py @@ -3,19 +3,10 @@ from sqlalchemy_jsonapi.errors import (RelationshipNotFoundError, ResourceNotFoundError) -# TODO: Sparse Fieldsets -# TODO: Related Includes -# TODO: Sorting -# TODO: Pagination -# TODO: Ember-style filtering -# TODO: Simple filtering -# TODO: Complex filtering -# TODO: Bad query param - def test_200_result_of_to_one(post, client): - response = client.get('/api/blog-posts/{}/author/'.format( - post.id)).validate(200) + response = client.get('/api/blog-posts/{}/author/'.format(post.id)).validate( + 200) assert response.json_data['data']['type'] == 'users' @@ -26,8 +17,8 @@ def test_200_collection_of_to_many(comment, client): def test_404_when_relationship_not_found(post, client): - client.get('/api/blog-posts/{}/last_comment/'.format(post.id)).validate( - 404, RelationshipNotFoundError) + client.get('/api/blog-posts/{}/last_comment/'.format( + post.id)).validate(404, RelationshipNotFoundError) def test_404_when_resource_not_found(client): diff --git a/sqlalchemy_jsonapi/tests/test_relationship_delete.py b/sqlalchemy_jsonapi/tests/test_relationship_delete.py index 04b07e1..c7ae5a8 100644 --- a/sqlalchemy_jsonapi/tests/test_relationship_delete.py +++ b/sqlalchemy_jsonapi/tests/test_relationship_delete.py @@ -6,18 +6,12 @@ ResourceNotFoundError, ToManyExpectedError, MissingContentTypeError, ValidationError) -# TODO: Sorting -# TODO: Pagination -# TODO: Ember-style filtering -# TODO: Simple filtering -# TODO: Complex filtering -# TODO: Bad query param - def test_200_on_deletion_from_to_many(comment, client): payload = {'data': [{'type': 'blog-comments', 'id': str(comment.id)}]} response = client.delete( - '/api/blog-posts/{}/relationships/comments/'.format(comment.post.id), + '/api/blog-posts/{}/relationships/comments/'.format( + comment.post.id), data=json.dumps(payload), content_type='application/vnd.api+json').validate(200) for item in response.json_data['data']: @@ -35,26 +29,30 @@ def test_404_on_resource_not_found(client): def test_404_on_relationship_not_found(post, client): - client.delete('/api/blog-posts/{}/relationships/comment/'.format(post.id), + client.delete('/api/blog-posts/{}/relationships/comment/'.format( + post.id), data='{}', content_type='application/vnd.api+json').validate( 404, RelationshipNotFoundError) def test_403_on_permission_denied(user, client): - client.delete('/api/users/{}/relationships/logs/'.format(user.id), + client.delete('/api/users/{}/relationships/logs/'.format( + user.id), data='{"data": []}', content_type='application/vnd.api+json').validate( 403, PermissionDeniedError) def test_409_on_to_one_provided(post, client): - client.delete('/api/blog-posts/{}/relationships/author/'.format(post.id), + client.delete('/api/blog-posts/{}/relationships/author/'.format( + post.id), data='{"data": {}}', content_type='application/vnd.api+json').validate( 409, ValidationError) def test_409_missing_content_type_header(post, client): - client.delete('/api/blog-posts/{}/relationships/comment/'.format(post.id), + client.delete('/api/blog-posts/{}/relationships/comment/'.format( + post.id), data='{}').validate(409, MissingContentTypeError) diff --git a/sqlalchemy_jsonapi/tests/test_relationship_get.py b/sqlalchemy_jsonapi/tests/test_relationship_get.py index ef48592..761784c 100644 --- a/sqlalchemy_jsonapi/tests/test_relationship_get.py +++ b/sqlalchemy_jsonapi/tests/test_relationship_get.py @@ -2,37 +2,35 @@ RelationshipNotFoundError, ResourceNotFoundError, PermissionDeniedError) from uuid import uuid4 -# TODO: Sorting -# TODO: Pagination -# TODO: Ember-style filtering -# TODO: Simple filtering -# TODO: Complex filtering -# TODO: Bad query param - def test_200_on_to_many(post, client): - response = client.get('/api/blog-posts/{}/relationships/comments/'.format( - post.id)).validate(200) + response = client.get( + '/api/blog-posts/{}/relationships/comments/'.format( + post.id)).validate(200) for item in response.json_data['data']: assert {'id', 'type'} == set(item.keys()) def test_200_on_to_one(post, client): - response = client.get('/api/blog-posts/{}/relationships/author/'.format( - post.id)).validate(200) + response = client.get( + '/api/blog-posts/{}/relationships/author/'.format( + post.id)).validate(200) assert response.json_data['data']['type'] == 'users' def test_404_on_resource_not_found(client): - client.get('/api/blog-posts/{}/relationships/comments/'.format(uuid4( - ))).validate(404, ResourceNotFoundError) + client.get( + '/api/blog-posts/{}/relationships/comments/'.format(uuid4())).validate( + 404, ResourceNotFoundError) def test_404_on_relationship_not_found(post, client): - client.get('/api/blog-posts/{}/relationships/comment/'.format( - post.id)).validate(404, RelationshipNotFoundError) + client.get( + '/api/blog-posts/{}/relationships/comment/'.format( + post.id)).validate(404, RelationshipNotFoundError) def test_403_on_permission_denied(unpublished_post, client): - client.get('/api/blog-posts/{}/relationships/comment/'.format( - unpublished_post.id)).validate(403, PermissionDeniedError) + client.get( + '/api/blog-posts/{}/relationships/comment/'.format( + unpublished_post.id)).validate(403, PermissionDeniedError) diff --git a/sqlalchemy_jsonapi/tests/test_relationship_patch.py b/sqlalchemy_jsonapi/tests/test_relationship_patch.py index 9c889ee..d1d8df5 100644 --- a/sqlalchemy_jsonapi/tests/test_relationship_patch.py +++ b/sqlalchemy_jsonapi/tests/test_relationship_patch.py @@ -5,13 +5,6 @@ RelationshipNotFoundError, ResourceNotFoundError, ValidationError) -# TODO: Sorting -# TODO: Pagination -# TODO: Ember-style filtering -# TODO: Simple filtering -# TODO: Complex filtering -# TODO: Bad query param - def test_200_on_to_one_set_to_resource(post, user, client): payload = {'data': {'type': 'users', 'id': str(user.id)}} @@ -33,26 +26,29 @@ def test_200_on_to_one_set_to_null(post, client): def test_200_on_to_many_set_to_resources(post, comment, client): payload = {'data': [{'type': 'blog-comments', 'id': str(comment.id)}]} - response = client.patch( - '/api/blog-posts/{}/relationships/comments/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate(200) + response = client.patch('/api/blog-posts/{}/relationships/comments/'.format( + post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate( + 200) assert response.json_data['data'][0]['id'] == str(comment.id) assert len(response.json_data['data']) == 1 def test_200_on_to_many_set_to_empty(post, client): payload = {'data': []} - response = client.patch( - '/api/blog-posts/{}/relationships/comments/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate(200) + response = client.patch('/api/blog-posts/{}/relationships/comments/'.format( + post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate( + 200) assert len(response.json_data['data']) == 0 def test_409_on_to_one_set_to_empty_list(post, client): payload = {'data': []} - client.patch('/api/blog-posts/{}/relationships/author/'.format(post.id), + client.patch('/api/blog-posts/{}/relationships/author/'.format( + post.id), data=json.dumps(payload), content_type='application/vnd.api+json').validate( 409, ValidationError) @@ -60,21 +56,24 @@ def test_409_on_to_one_set_to_empty_list(post, client): def test_409_on_to_many_set_to_null(post, client): payload = {'data': None} - client.patch('/api/blog-posts/{}/relationships/comments/'.format(post.id), + client.patch('/api/blog-posts/{}/relationships/comments/'.format( + post.id), data=json.dumps(payload), content_type='application/vnd.api+json').validate( 409, ValidationError) def test_404_on_resource_not_found(client): - client.patch('/api/blog-posts/{}/relationships/comments/'.format(uuid4()), + client.patch('/api/blog-posts/{}/relationships/comments/'.format( + uuid4()), data='{}', content_type='application/vnd.api+json').validate( 404, ResourceNotFoundError) def test_404_on_relationship_not_found(client, post): - client.patch('/api/blog-posts/{}/relationships/comment/'.format(post.id), + client.patch('/api/blog-posts/{}/relationships/comment/'.format( + post.id), data='{}', content_type='application/vnd.api+json').validate( 404, RelationshipNotFoundError) @@ -82,7 +81,8 @@ def test_404_on_relationship_not_found(client, post): def test_404_on_related_item_not_found(post, client): payload = {'data': [{'type': 'blog-comments', 'id': str(uuid4())}]} - client.patch('/api/blog-posts/{}/relationships/comments/'.format(post.id), + client.patch('/api/blog-posts/{}/relationships/comments/'.format( + post.id), data=json.dumps(payload), content_type='application/vnd.api+json').validate( 404, ResourceNotFoundError) @@ -90,7 +90,8 @@ def test_404_on_related_item_not_found(post, client): def test_403_on_permission_denied(user, log, client): payload = {'data': {'type': 'users', 'id': str(user.id)}} - client.patch('/api/logs/{}/relationships/user/'.format(log.id), + client.patch('/api/logs/{}/relationships/user/'.format( + log.id), data=json.dumps(payload), content_type='application/vnd.api+json').validate( 403, PermissionDeniedError) @@ -98,7 +99,8 @@ def test_403_on_permission_denied(user, log, client): def test_403_on_permission_denied_on_related(log, user, client): payload = {'data': {'type': 'logs', 'id': str(log.id)}} - client.patch('/api/users/{}/relationships/logs/'.format(user.id), + client.patch('/api/users/{}/relationships/logs/'.format( + user.id), data=json.dumps(payload), content_type='application/vnd.api+json').validate( 403, PermissionDeniedError) @@ -106,7 +108,8 @@ def test_403_on_permission_denied_on_related(log, user, client): def test_409_on_to_one_with_incompatible_model(post, comment, client): payload = {'data': {'type': 'blog-comments', 'id': str(comment.id)}} - client.patch('/api/blog-posts/{}/relationships/author/'.format(post.id), + client.patch('/api/blog-posts/{}/relationships/author/'.format( + post.id), data=json.dumps(payload), content_type='application/vnd.api+json').validate( 409, ValidationError) @@ -114,7 +117,8 @@ def test_409_on_to_one_with_incompatible_model(post, comment, client): def test_409_on_to_many_with_incompatible_model(post, client): payload = {'data': [{'type': 'blog-posts', 'id': str(post.id)}]} - client.patch('/api/blog-posts/{}/relationships/author/'.format(post.id), + client.patch('/api/blog-posts/{}/relationships/author/'.format( + post.id), data=json.dumps(payload), content_type='application/vnd.api+json').validate( 409, ValidationError) diff --git a/sqlalchemy_jsonapi/tests/test_relationship_post.py b/sqlalchemy_jsonapi/tests/test_relationship_post.py index a771932..287fedf 100644 --- a/sqlalchemy_jsonapi/tests/test_relationship_post.py +++ b/sqlalchemy_jsonapi/tests/test_relationship_post.py @@ -3,27 +3,22 @@ from sqlalchemy_jsonapi.errors import ValidationError, ResourceNotFoundError, RelationshipNotFoundError -# TODO: Sorting -# TODO: Pagination -# TODO: Ember-style filtering -# TODO: Simple filtering -# TODO: Complex filtering -# TODO: Bad query param - def test_200_on_to_many(comment, post, client): payload = {'data': [{'type': 'blog-comments', 'id': str(comment.id)}]} - response = client.post( - '/api/blog-posts/{}/relationships/comments/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate(200) + response = client.post('/api/blog-posts/{}/relationships/comments/'.format( + post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate( + 200) assert str(comment.id) in [str(x['id']) for x in response.json_data['data']] def test_409_on_hash_instead_of_array_provided(comment, post, client): payload = {'data': {'type': 'blog-comments', 'id': str(comment.id)}} - client.post('/api/blog-posts/{}/relationships/comments/'.format(post.id), + client.post('/api/blog-posts/{}/relationships/comments/'.format( + post.id), data=json.dumps(payload), content_type='application/vnd.api+json').validate( 409, ValidationError) @@ -31,7 +26,8 @@ def test_409_on_hash_instead_of_array_provided(comment, post, client): def test_409_on_incompatible_model(user, post, client): payload = {'data': [{'type': 'users', 'id': str(user.id)}]} - client.post('/api/blog-posts/{}/relationships/comments/'.format(post.id), + client.post('/api/blog-posts/{}/relationships/comments/'.format( + post.id), data=json.dumps(payload), content_type='application/vnd.api+json').validate( 409, ValidationError) @@ -52,7 +48,8 @@ def test_404_on_resource_not_found(client): def test_404_on_relationship_not_found(post, client): - client.post('/api/blog-posts/{}/relationships/comment/'.format(post.id), + client.post('/api/blog-posts/{}/relationships/comment/'.format( + post.id), data='{}', content_type='application/vnd.api+json').validate( 404, RelationshipNotFoundError) diff --git a/sqlalchemy_jsonapi/tests/test_resource_delete.py b/sqlalchemy_jsonapi/tests/test_resource_delete.py index a291c5e..a48d429 100644 --- a/sqlalchemy_jsonapi/tests/test_resource_delete.py +++ b/sqlalchemy_jsonapi/tests/test_resource_delete.py @@ -4,9 +4,6 @@ PermissionDeniedError, ResourceNotFoundError, ResourceTypeNotFoundError) -# TODO: Bad query param - - def test_200_on_success(comment, client): client.delete('/api/blog-comments/{}/'.format(comment.id)).validate(204) client.get('/api/blog-comments/{}/'.format(comment.id)).validate( diff --git a/sqlalchemy_jsonapi/tests/test_resource_get.py b/sqlalchemy_jsonapi/tests/test_resource_get.py index 30a2e49..4f81414 100644 --- a/sqlalchemy_jsonapi/tests/test_resource_get.py +++ b/sqlalchemy_jsonapi/tests/test_resource_get.py @@ -2,11 +2,6 @@ from uuid import uuid4 -# TODO: Sparse Fieldsets -# TODO: Related Includes -# TODO: Bad query param - - def test_200_without_querystring(post, client): response = client.get('/api/blog-posts/{}/'.format(post.id)).validate(200) assert response.json_data['data']['type'] == 'blog-posts' @@ -47,17 +42,16 @@ def test_200_with_multiple_includes(post, client): def test_200_with_single_field(post, client): - response = client.get( - '/api/blog-posts/{}/?fields[blog-posts]=title'.format( - post.id)).validate(200) + response = client.get('/api/blog-posts/{}/?fields[blog-posts]=title'.format( + post.id)).validate(200) assert {'title'} == set(response.json_data['data']['attributes'].keys()) assert len(response.json_data['data']['relationships']) == 0 def test_200_with_multiple_fields(post, client): - response = client.get( - '/api/blog-posts/{}/?fields[blog-posts]=title,content'.format( - post.id)).validate(200) + response = client.get('/api/blog-posts/{}/?fields[blog-posts]=title,content'.format( + post.id)).validate( + 200) assert {'title', 'content' } == set(response.json_data['data']['attributes'].keys()) assert len(response.json_data['data']['relationships']) == 0 @@ -66,7 +60,8 @@ def test_200_with_multiple_fields(post, client): def test_200_with_single_field_across_a_relationship(post, client): response = client.get( '/api/blog-posts/{}/?fields[blog-posts]=title,content&fields[blog-comments]=author&include=comments'.format( - post.id)).validate(200) + post.id)).validate( + 200) assert {'title', 'content' } == set(response.json_data['data']['attributes'].keys()) assert len(response.json_data['data']['relationships']) == 0 diff --git a/sqlalchemy_jsonapi/tests/test_resource_patch.py b/sqlalchemy_jsonapi/tests/test_resource_patch.py index 5ee3b40..e5cdac1 100644 --- a/sqlalchemy_jsonapi/tests/test_resource_patch.py +++ b/sqlalchemy_jsonapi/tests/test_resource_patch.py @@ -7,11 +7,6 @@ MissingTypeError) -# TODO: Sparse Fieldsets -# TODO: Related Includes -# TODO: Bad query param - - def test_200(client, post, user): payload = { 'data': { @@ -30,14 +25,14 @@ def test_200(client, post, user): } } } - response = client.patch( - '/api/blog-posts/{}/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate(200) + response = client.patch('/api/blog-posts/{}/'.format(post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate( + 200) assert response.json_data['data']['id'] == str(post.id) assert response.json_data['data']['type'] == 'blog-posts' - assert response.json_data['data']['attributes'][ - 'title'] == 'I just lost the game' + assert response.json_data['data']['attributes']['title' + ] == 'I just lost the game' def test_400_missing_type(post, client): @@ -50,7 +45,8 @@ def test_400_missing_type(post, client): def test_404_resource_not_found(client): client.patch('/api/blog-posts/{}/'.format(uuid4()), content_type='application/vnd.api+json', - data='{}').validate(404, ResourceNotFoundError) + data='{}').validate( + 404, ResourceNotFoundError) def test_404_related_resource_not_found(client, post): diff --git a/sqlalchemy_jsonapi/tests/test_serializer.py b/sqlalchemy_jsonapi/tests/test_serializer.py index 38fe038..07060c9 100644 --- a/sqlalchemy_jsonapi/tests/test_serializer.py +++ b/sqlalchemy_jsonapi/tests/test_serializer.py @@ -7,6 +7,5 @@ def test_include_different_types_same_id(session, comment): comment.post.id = comment.author.id = comment.post_id = comment.author_id = new_id session.commit() - r = api.serializer.get_resource(session, {'include': 'post,author'}, - 'blog-comments', comment.id) + r = api.serializer.get_resource(session, {'include': 'post,author'}, 'blog-comments', comment.id) assert len(r.data['included']) == 2 diff --git a/sqlalchemy_jsonapi/version.py b/sqlalchemy_jsonapi/version.py deleted file mode 100644 index a0f6658..0000000 --- a/sqlalchemy_jsonapi/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '5.0.0'