Skip to content

Commit

Permalink
Merge pull request ColtonProvias#35 from rockstar/lint
Browse files Browse the repository at this point in the history
Cleanup all existing lint. Add lint to default tox runs.
  • Loading branch information
Anderycks authored Feb 22, 2017
2 parents 8eac67b + 68709dc commit 6b4f4f7
Show file tree
Hide file tree
Showing 16 changed files with 293 additions and 259 deletions.
12 changes: 6 additions & 6 deletions sqlalchemy_jsonapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from .constants import Endpoint, Method
from .serializer import (ALL_PERMISSIONS, INTERACTIVE_PERMISSIONS, JSONAPI,
AttributeActions, Permissions, RelationshipActions,
attr_descriptor, permission_test,
relationship_descriptor)
from .constants import Endpoint, Method # NOQA
from .serializer import ( # NOQA
ALL_PERMISSIONS, INTERACTIVE_PERMISSIONS, JSONAPI, AttributeActions,
Permissions, RelationshipActions, attr_descriptor, permission_test,
relationship_descriptor)

try:
from .flaskext import FlaskJSONAPI
except ImportError:
FlaskJSONAPI = None
FlaskJSONAPI = None
2 changes: 1 addition & 1 deletion sqlalchemy_jsonapi/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ class Endpoint(Enum):
COLLECTION = '/<api_type>'
RESOURCE = '/<api_type>/<obj_id>'
RELATED = '/<api_type>/<obj_id>/<relationship>'
RELATIONSHIP = '/<api_type>/<obj_id>/relationships/<relationship>'
RELATIONSHIP = '/<api_type>/<obj_id>/relationships/<relationship>'
6 changes: 5 additions & 1 deletion sqlalchemy_jsonapi/flaskext.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def default(self, value):
return str(value)
return json.JSONEncoder.default(self, value)


#: The views to generate
views = [
(Method.GET, Endpoint.COLLECTION), (Method.GET, Endpoint.RESOURCE),
Expand Down Expand Up @@ -163,7 +164,10 @@ 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
Expand Down
69 changes: 43 additions & 26 deletions sqlalchemy_jsonapi/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@
Colton J. Provias
MIT License
"""

from collections import MutableMapping
from enum import Enum
from inflection import pluralize, dasherize, parameterize, tableize, underscore

from inflection import dasherize, 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,
Expand Down Expand Up @@ -128,6 +126,7 @@ def __call__(self, fn):
fn.__jsonapi_check_permission__ |= set(self.permission)
return fn


#: More consistent name for the decorators
permission_test = PermissionTest

Expand Down Expand Up @@ -227,7 +226,8 @@ 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())
Expand All @@ -236,8 +236,10 @@ 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__ = {x: dasherize(underscore(x)) for x in model_keys}
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):

Expand Down Expand Up @@ -292,8 +294,10 @@ 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),
'related': '{}/{}/{}/{}'.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)
}

def _get_relationship(self, resource, rel_key, permission):
Expand Down Expand Up @@ -359,7 +363,9 @@ 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

Expand All @@ -384,23 +390,27 @@ 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) # NOQA
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 # NOQA

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():
Expand All @@ -418,20 +428,21 @@ 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 # NOQA

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) # NOQA
except PermissionDeniedError:
continue

Expand Down Expand Up @@ -756,7 +767,7 @@ def get_relationship(self, session, query, api_type, obj_id, rel_key):
RelationshipActions.GET)(resource)

if relationship.direction == MANYTOONE:
if related == None:
if related is None:
response.data['data'] = None
else:
try:
Expand Down Expand Up @@ -801,7 +812,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'] != None:
and json_data['data'] is not None:
raise ValidationError('Provided data must be a hash.')

related = getattr(resource, relationship.key)
Expand All @@ -811,7 +822,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'] == None:
if json_data['data'] is None:
setter(resource, None)
else:
to_relate = self._fetch_resource(
Expand Down Expand Up @@ -909,7 +920,9 @@ 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:
Expand All @@ -920,7 +933,7 @@ def patch_resource(self, session, json_data, api_type, obj_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]]) # NOQA
session.commit()
except IntegrityError as e:
session.rollback()
Expand Down Expand Up @@ -960,7 +973,9 @@ 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(
Expand Down Expand Up @@ -1036,7 +1051,9 @@ 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:
Expand Down
4 changes: 3 additions & 1 deletion sqlalchemy_jsonapi/tests/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
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 FlaskJSONAPI, Permissions, permission_test, Method, Endpoint, INTERACTIVE_PERMISSIONS
from sqlalchemy_jsonapi import (
FlaskJSONAPI, Permissions, permission_test, Method, Endpoint,
INTERACTIVE_PERMISSIONS)
from sqlalchemy_utils import EmailType, PasswordType, Timestamp, UUIDType

app = Flask(__name__)
Expand Down
25 changes: 11 additions & 14 deletions sqlalchemy_jsonapi/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,21 +87,19 @@ def user(session):

@pytest.fixture
def post(user, session):
new_post = BlogPost(author=user,
title=fake.sentence(),
content=fake.paragraph(),
is_published=True)
new_post = BlogPost(
author=user, title=fake.sentence(), content=fake.paragraph(),
is_published=True)
session.add(new_post)
session.commit()
return new_post


@pytest.fixture
def unpublished_post(user, session):
new_post = BlogPost(author=user,
title=fake.sentence(),
content=fake.paragraph(),
is_published=False)
new_post = BlogPost(
author=user, title=fake.sentence(), content=fake.paragraph(),
is_published=False)
session.add(new_post)
session.commit()
return new_post
Expand All @@ -110,13 +108,12 @@ def unpublished_post(user, session):
@pytest.fixture
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())
new_post = BlogPost(
author=user, title=fake.sentence(), content=fake.paragraph(),
is_published=fake.boolean())
session.add(new_post)
new_post.comments.append(BlogComment(author=user,
content=fake.paragraph()))
new_post.comments.append(
BlogComment(author=user, content=fake.paragraph()))
session.commit()


Expand Down
34 changes: 20 additions & 14 deletions sqlalchemy_jsonapi/tests/test_collection_get.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from sqlalchemy_jsonapi.errors import (
BadRequestError, NotAnAttributeError, NotSortableError)
BadRequestError, NotSortableError)


def test_200_with_no_querystring(bunch_of_posts, client):
Expand All @@ -14,39 +14,44 @@ def test_200_with_single_included_model(bunch_of_posts, client):
assert response.json_data['included'][0]['type'] == 'users'


def test_200_with_including_model_and_including_inbetween(bunch_of_posts,
client):
response = client.get('/api/blog-comments/?include=post.author').validate(200)
def test_200_with_including_model_and_including_inbetween(
bunch_of_posts, client):
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_multiple_fields(bunch_of_posts, client):
response = client.get('/api/blog-posts/?fields[blog-posts]=title,content,is-published').validate(
response = client.get(
'/api/blog-posts/?fields[blog-posts]=title,content,is-published').validate( # NOQA
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


def test_200_with_single_field_across_a_relationship(bunch_of_posts, client):
response = client.get(
'/api/blog-posts/?fields[blog-posts]=title,content&fields[blog-comments]=author&include=comments').validate(
'/api/blog-posts/?fields[blog-posts]=title,content&fields[blog-comments]=author&include=comments').validate( # NOQA
200)
for item in response.json_data['data']:
assert {'title', 'content'} == set(item['attributes'].keys())
Expand Down Expand Up @@ -85,19 +90,20 @@ 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):
Expand Down
Loading

0 comments on commit 6b4f4f7

Please sign in to comment.