Skip to content

Commit

Permalink
Merge b57ffb3 into 3c3bf73
Browse files Browse the repository at this point in the history
  • Loading branch information
jfinkels committed Sep 7, 2016
2 parents 3c3bf73 + b57ffb3 commit d3104b3
Show file tree
Hide file tree
Showing 32 changed files with 339 additions and 191 deletions.
25 changes: 22 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ addons:
# operator). Travis claims that version 9.4 is installed by default, but it
# claims that && is unknown unless this addon is here.
postgresql: "9.4"
apt:
packages:
# These are build dependencies for the documentation; LaTeX is required
# for rendering math. These are the required packages for Ubuntu 12.04,
# Travis' current build environment!
- texlive-latex-base
- texlive-latex-extra
- dvipng


before_install:
Expand All @@ -32,14 +40,25 @@ before_install:
else
export REQUIREMENTS=requirements-test-cpython.txt
fi
- pip install --upgrade pip
install:
# Upgrade pip in case the installed version is out of date.
- pip install --upgrade pip
# Install the test requirements.
- pip install -r $REQUIREMENTS
- pip install coveralls
# Install the requirements specific to Travis tests.
- pip install -r requirements-doc.txt
- pip install flake8 coveralls
# Install Flask-Restless so that it is available for the documentation build.
- python setup.py install

script:
coverage run --source=flask_restless setup.py test
# Code style checks.
- find flask_restless/ tests/ examples/ setup.py -type f -name \*.py -print0 | xargs -0 flake8
# Documentation build checks.
- sphinx-build -n -W docs/ build/sphinx/html/
# Unit tests.
- coverage run --source=flask_restless setup.py test

after_success:
coveralls
2 changes: 2 additions & 0 deletions examples/quickstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = flask_sqlalchemy.SQLAlchemy(app)


# Create your Flask-SQLALchemy models as usual but with the following
# restriction: they must have an __init__ method that accepts keyword
# arguments for all columns (the constructor in
Expand All @@ -18,6 +19,7 @@ class Person(db.Model):
name = db.Column(db.Unicode)
birth_date = db.Column(db.Date)


class Article(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.Unicode)
Expand Down
4 changes: 2 additions & 2 deletions examples/server_configurations/authentication/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
import os
import os.path

from flask import Flask, render_template, redirect, url_for
from flask import flash, Flask, render_template, redirect, url_for
from flask_login import current_user, login_user, LoginManager, UserMixin
from flask_restless import APIManager, ProcessingException, NO_CHANGE
from flask_restless import APIManager, ProcessingException
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import PasswordField, SubmitField, TextField, Form

Expand Down
8 changes: 4 additions & 4 deletions examples/server_configurations/custom_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@
from marshmallow_jsonapi import fields
from marshmallow_jsonapi import Schema

## Flask application and database configuration ##
# Flask application and database configuration

app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

## Flask-SQLAlchemy model definitions ##
# Flask-SQLAlchemy model definitions #


class Person(db.Model):
Expand All @@ -81,7 +81,7 @@ class Article(db.Model):
author = db.relationship(Person, backref=db.backref('articles'))


## Marshmallow schema definitions ##
# Marshmallow schema definitions #

class PersonSchema(Schema):

Expand Down Expand Up @@ -134,7 +134,7 @@ def make_article(self, data):
return Article(**data)


## Serializer and deserializer classes ##
# Serializer and deserializer classes #

class MarshmallowSerializer(DefaultSerializer):

Expand Down
1 change: 1 addition & 0 deletions examples/server_configurations/separate_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = flask_sqlalchemy.SQLAlchemy(app)


# Create your Flask-SQLALchemy models as usual but with the following two
# (reasonable) restrictions:
# 1. They must have a primary key column of type sqlalchemy.Integer or
Expand Down
31 changes: 25 additions & 6 deletions flask_restless/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@
SQLAlchemy models via the JSON API protocol.
"""
#: The current version of this extension.
#:
#: This should be the same as the version specified in the :file:`setup.py`
#: file.
__version__ = '1.0.0b2-dev'

# The following names are available as part of the public API for
# Flask-Restless. End users of this package can import these names by doing
# ``from flask_restless import APIManager``, for example.
Expand All @@ -38,3 +32,28 @@
from .serialization import simple_serialize_many
from .views import CONTENT_TYPE
from .views import ProcessingException

#: The current version of this extension.
#:
#: This should be the same as the version specified in the :file:`setup.py`
#: file.
__version__ = '1.0.0b2-dev'

__all__ = [
'APIManager',
'collection_name',
'CONTENT_TYPE',
'DefaultDeserializer',
'DefaultSerializer',
'DeserializationException',
'IllegalArgumentError',
'model_for',
'MultipleExceptions',
'primary_key_for',
'ProcessingException',
'SerializationException',
'serializer_for',
'simple_serialize',
'simple_serialize_many',
'url_for',
]
4 changes: 2 additions & 2 deletions flask_restless/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
expect, for example, a SQLAlchemy model actually receive a SQLAlchemy
model.
.. _SQLAlchemy inspection API: https://docs.sqlalchemy.org/en/latest/core/inspection.html
.. _SQLAlchemy inspection API:
https://docs.sqlalchemy.org/en/latest/core/inspection.html
"""
import datetime
Expand Down Expand Up @@ -381,7 +382,6 @@ def string_to_datetime(model, fieldname, value):
return value



def get_model(instance):
"""Returns the model class of which the specified object is an instance."""
return type(instance)
Expand Down
2 changes: 1 addition & 1 deletion flask_restless/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
DEFAULT_URL_PREFIX = '/api'

if sys.version_info < (3, ):
STRING_TYPES = (str, unicode)
STRING_TYPES = (str, unicode) # noqa
else:
STRING_TYPES = (str, )

Expand Down
8 changes: 8 additions & 0 deletions flask_restless/search/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,11 @@
from .drivers import create_filters
from .drivers import search
from .drivers import search_relationship

__all__ = [
'create_filters',
'FilterCreationError',
'FilterParsingError',
'search',
'search_relationship',
]
13 changes: 13 additions & 0 deletions flask_restless/serialization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,16 @@
from .serializers import simple_serialize_many
from .serializers import simple_relationship_serialize
from .serializers import simple_relationship_serialize_many

__all__ = [
'DefaultDeserializer',
'DefaultSerializer',
'DeserializationException',
'JsonApiDocument',
'MultipleExceptions',
'SerializationException',
'simple_relationship_serialize',
'simple_relationship_serialize_many',
'simple_serialize',
'simple_serialize_many',
]
9 changes: 6 additions & 3 deletions flask_restless/serialization/deserializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ def deserialize(self, document):
**This method is not implemented in this base class; subclasses
must override this method.**
.. _Resource Objects: http://jsonapi.org/format/#document-structure-resource-objects
.. _Resource Objects:
http://jsonapi.org/format/#document-structure-resource-objects
"""
raise NotImplementedError
Expand All @@ -92,7 +93,8 @@ def deserialize_many(self, document):
**This method is not implemented in this base class; subclasses
must override this method.**
.. _Resource Objects: http://jsonapi.org/format/#document-structure-resource-objects
.. _Resource Objects:
http://jsonapi.org/format/#document-structure-resource-objects
"""
raise NotImplementedError
Expand Down Expand Up @@ -203,7 +205,8 @@ def deserialize(self, document):
*Implementation note:* everything in the document other than the
``data`` element is ignored.
.. _Resource Objects: http://jsonapi.org/format/#document-structure-resource-objects
.. _Resource Objects:
http://jsonapi.org/format/#document-structure-resource-objects
"""
if 'data' not in document:
Expand Down
3 changes: 2 additions & 1 deletion flask_restless/serialization/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ def create_relationship(model, instance, relation):
created for the primary model, `model`, or the model of the
relation.
.. _Relationships: http://jsonapi.org/format/#document-resource-object-relationships
.. _Relationships:
http://jsonapi.org/format/#document-resource-object-relationships
"""
result = {}
Expand Down
8 changes: 8 additions & 0 deletions flask_restless/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@
from .resources import API
from .relationships import RelationshipAPI
from .function import FunctionAPI

__all__ = [
'API',
'CONTENT_TYPE',
'FunctionAPI',
'ProcessingException',
'RelationshipAPI',
]
49 changes: 26 additions & 23 deletions flask_restless/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,12 @@ def _is_msie8or9():
our best guess based on the information provided.
"""
if request.user_agent is None or request.user_agent.browser != 'msie' \
or request.user_agent.version is None:
return False
# request.user_agent.version comes as a string, so we have to parse it
version = lambda ua: tuple(int(d) for d in ua.version.split('.'))
return (request.user_agent is not None and
request.user_agent.version is not None and
request.user_agent.browser == 'msie' and
(8, 0) <= version(request.user_agent) < (10, 0))
version = tuple(map(int, request.user_agent.version.split('.')))
return (8, 0) <= version < (10, 0)


def un_camel_case(s):
Expand Down Expand Up @@ -323,7 +323,8 @@ def requires_json_api_accept(func):
def get(self, *args, **kw):
return '...'
.. _Server Responsibilities: http://jsonapi.org/format/#content-negotiation-servers
.. _Server Responsibilities:
http://jsonapi.org/format/#content-negotiation-servers
"""
@wraps(func)
Expand Down Expand Up @@ -916,17 +917,17 @@ class Paginated(object):
>>> for rel, url in paginated.pagination_links.items():
... print(rel, url)
...
first http://example.com/api/person?page[size]=3&page[number]=1
last http://example.com/api/person?page[size]=3&page[number]=4
prev http://example.com/api/person?page[size]=3&page[number]=1
next http://example.com/api/person?page[size]=3&page[number]=3
first http://localhost/api/person?page[size]=3&page[number]=1
last http://localhost/api/person?page[size]=3&page[number]=4
prev http://localhost/api/person?page[size]=3&page[number]=1
next http://localhost/api/person?page[size]=3&page[number]=3
>>> for link in paginated.header_links:
... print(link)
...
<http://example.com/api/person?page[size]=3&page[number]=1>; rel="first"
<http://example.com/api/person?page[size]=3&page[number]=4>; rel="last"
<http://example.com/api/person?page[size]=3&page[number]=1>; rel="prev"
<http://example.com/api/person?page[size]=3&page[number]=3>; rel="next"
<http://localhost/api/person?page[size]=3&page[number]=1>; rel="first"
<http://localhost/api/person?page[size]=3&page[number]=4>; rel="last"
<http://localhost/api/person?page[size]=3&page[number]=1>; rel="prev"
<http://localhost/api/person?page[size]=3&page[number]=3>; rel="next"
"""

Expand Down Expand Up @@ -1333,9 +1334,6 @@ def __init__(self, session, model, preprocessors=None, postprocessors=None,
#: serialization.
self.serializer = serializer

#: A custom serialization function for linkage objects.
#self.serialize_relationship = simple_relationship_serialize

#: A custom deserialization function for primary resources; see
#: :ref:`serialization` for more information.
#:
Expand Down Expand Up @@ -1368,12 +1366,13 @@ def __init__(self, session, model, preprocessors=None, postprocessors=None,
# database integrity errors. However, in order to rollback the session,
# we need to have a session object available to roll back. Therefore we
# need to manually decorate each of the view functions here.
decorate = lambda name, f: setattr(self, name, f(getattr(self, name)))
for method in ['get', 'post', 'patch', 'delete']:
# Check if the subclass has the method before trying to decorate
# it.
if hasattr(self, method):
decorate(method, catch_integrity_errors(self.session))
wrapper = catch_integrity_errors(self.session)
old_method = getattr(self, method)
setattr(self, method, wrapper(old_method))

def collection_processor_type(self, *args, **kw):
"""The suffix for the pre- and postprocessor identifiers for
Expand Down Expand Up @@ -1683,7 +1682,8 @@ def _get_collection_helper(self, resource=None, relation_name=None,
#
# - a collection of primary resources (as in `GET /person`),
# - a to-many relation (as in `GET /person/1/articles`),
# - a to-many relationship (as in `GET /person/1/relationships/articles`)
# - a to-many relationship (as in
# `GET /person/1/relationships/articles`)
#
items = paginated.items
# This covers the relationship object case...
Expand Down Expand Up @@ -1744,9 +1744,11 @@ def _get_collection_helper(self, resource=None, relation_name=None,
# as in `GET /people`, or a to-many relation, as in `GET
# /people/1/comments`.
if resource is None:
links = linker.generate_links(None, None, None, None, False, False)
links = linker.generate_links(None, None, None, None, False,
False)
else:
links = linker.generate_links(resource, None, None, False, False)
links = linker.generate_links(resource, None, None, False,
False)
result['links'].update(links)

# Create the metadata for the response, like headers and
Expand Down Expand Up @@ -1807,7 +1809,8 @@ def resources_to_include(self, instance):
which resources, other than the primary resource or resources, will be
included in a compound document response.
.. _Inclusion of Related Resources: http://jsonapi.org/format/#fetching-includes
.. _Inclusion of Related Resources:
http://jsonapi.org/format/#fetching-includes
"""
# Add any links requested to be included by URL parameters.
Expand Down
1 change: 0 additions & 1 deletion flask_restless/views/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
# License version 3 and under the 3-clause BSD license. For more
# information, see LICENSE.AGPL and LICENSE.BSD.
"""Helper functions for view classes."""
from sqlalchemy.exc import OperationalError
from sqlalchemy.inspection import inspect as sqlalchemy_inspect
from sqlalchemy.sql import func

Expand Down
4 changes: 2 additions & 2 deletions flask_restless/views/relationships.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,8 @@ def patch(self, resource_id, relation_name):
detail = ('No resource of type {0} found'
' with ID {1}').format(type_, id_)
return error_response(404, detail=detail)
if (isinstance(replacement, list)
and any(value is None for value in replacement)):
if isinstance(replacement, list) \
and any(value is None for value in replacement):
not_found = (rel for rel, value in zip(data, replacement)
if value is None)
detail = 'No resource of type {0} found with ID {1}'
Expand Down
Loading

0 comments on commit d3104b3

Please sign in to comment.