Skip to content

Commit

Permalink
Merge branch 'sqlalchemy-support' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
sloria committed May 1, 2015
2 parents 6fa4b6b + e33d821 commit 7916b59
Show file tree
Hide file tree
Showing 11 changed files with 298 additions and 55 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Changelog

Features:

- Add Flask-SQLAlchemy/marshmallow-sqlalchemy support via the ``ModelSchema`` class.
- ``Schema.jsonify`` now takes the same arguments as ``marshmallow.Schema.dump``. Additional keyword arguments are passed to ``flask.jsonify``.


Expand Down
29 changes: 13 additions & 16 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ Flask-Marshmallow
Flask + marshmallow for beautiful APIs
======================================

Flask-Marshmallow is a thin integration layer for `Flask`_ (a Python web framework) and `marshmallow`_ (an object serialization/deserialization library) that adds additional features to marshmallow, including URL and Hyperlinks fields for HATEOAS-ready APIs.
Flask-Marshmallow is a thin integration layer for `Flask`_ (a Python web framework) and `marshmallow`_ (an object serialization/deserialization library) that adds additional features to marshmallow, including URL and Hyperlinks fields for HATEOAS-ready APIs. It also (optionally) integrates with `Flask-SQLAlchemy <http://flask-sqlalchemy.pocoo.org/>`_.

Get it now
----------
::

pip install flask-marshmallow


Create your app.
Expand Down Expand Up @@ -53,9 +59,6 @@ Define your output format with marshmallow.
'collection': ma.URLFor('authors')
})
user_schema = UserSchema()
users_schema = UserSchema(many=True)
Output the data in your views.

Expand All @@ -66,12 +69,13 @@ Output the data in your views.
all_users = User.all()
result = users_schema.dump(all_users)
return jsonify(result.data)
# OR
# return user_schema.jsonify(all_users)
@app.route('/api/users/<id>')
def user_detail(id):
user = User.get(id)
result = user_schema.dump(user)
return jsonify(result.data)
return user_schema.jsonify(user)
# {
# "email": "fred@queen.com",
# "date_created": "Fri, 25 Apr 2014 06:02:56 -0000",
Expand All @@ -82,22 +86,15 @@ Output the data in your views.
# }
http://flask-marshmallow.readthedocs.org/
=========================================

Learn More
==========

To learn more about marshmallow, check out its `docs <http://marshmallow.readthedocs.org/en/latest/>`_.


Get it now
==========

::

pip install flask-marshmallow


http://flask-marshmallow.readthedocs.org/
=========================================

Project Links
=============
Expand Down
4 changes: 4 additions & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ mock

# Syntax checking
flake8==2.4.0

# Soft requirements
flask-sqlalchemy
marshmallow-sqlalchemy
14 changes: 13 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,19 @@
sys.path.insert(0, os.path.abspath('..'))
import flask_marshmallow
sys.path.append(os.path.abspath("_themes"))
extensions = ['sphinx.ext.autodoc', 'sphinx_issues']
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx_issues'
]

intersphinx_mapping = {
'python': ('http://python.readthedocs.org/en/latest/', None),
'flask': ('http://flask.pocoo.org/docs/latest/', None),
'flask-sqlalchemy': ('http://flask-sqlalchemy.pocoo.org/latest/', None),
'marshmallow': ('http://marshmallow.readthedocs.org/en/latest/', None),
'marshmallow-sqlalchemy': ('http://marshmallow-sqlalchemy.readthedocs.org/en/latest/', None),
}

primary_domain = 'py'
default_role = 'py:obj'
Expand Down
93 changes: 84 additions & 9 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ Flask-Marshmallow: Flask + marshmallow for beautiful APIs
Flask + marshmallow for beautiful APIs
======================================

Flask-Marshmallow is a thin integration layer for `Flask`_ (a Python web framework) and `marshmallow`_ (an object serialization/deserialization library) that adds additional features to marshmallow, including URL and Hyperlinks fields for HATEOAS-ready APIs.
Flask-Marshmallow is a thin integration layer for `Flask`_ (a Python web framework) and `marshmallow`_ (an object serialization/deserialization library) that adds additional features to marshmallow, including URL and Hyperlinks fields for HATEOAS-ready APIs. It also (optionally) integrates with `Flask-SQLAlchemy <http://flask-sqlalchemy.pocoo.org/>`_.

Get it now
----------
::

pip install flask-marshmallow


Create your app.
Expand Down Expand Up @@ -78,30 +84,99 @@ Output the data in your views.
# }
Learn More
==========
To learn more about marshmallow, check out its `docs <http://marshmallow.readthedocs.org/en/latest/>`_.
Optional Flask-SQLAlchemy Integration
-------------------------------------

Flask-Marshmallow includes useful extras for integrating with `Flask-SQLAlchemy <http://flask-sqlalchemy.pocoo.org/>`_ and `marshmallow-sqlalchemy <https://marshmallow-sqlalchemy.readthedocs.org>`_.

Get it now
==========
To enable SQLAlchemy integration, make sure that both Flask-SQLAlchemy and marshmallow-sqlalchemy are installed. ::

::
pip install -U flask-sqlalchemy marshmallow-sqlalchemy

pip install flask-marshmallow
Next, initialize the `SQLAlchemy <flask.ext.sqlalchemy.SQLAlchemy>` and `Marshmallow <flask_marshmallow.Marshmallow>` extensions, in that order.

.. code-block:: python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)
ma = Marshmallow(app)
.. warning::

Flask-SQLAlchemy **must** be initialized before Flask-Marshmallow.


Declare your models like normal.


.. code-block:: python
class Author(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
class Book(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255))
author_id = db.Column(db.Integer, db.ForeignKey('author.id'))
author = db.relationship('Author', backref='books')
Generate marshmallow `Schemas <marshmallow.Schema>` from your models using `ModelSchema <flask_marshmallow.sqla.ModelSchema>`.

.. code-block:: python
class AuthorSchema(ma.ModelSchema):
class Meta:
model = Author
class BookSchema(ma.ModelSchema):
class Meta:
model = Book
You can now use your schema to dump and load your ORM objects.


.. code-block:: python
>>> db.create_all()
>>> author_schema = AuthorSchema()
>>> book_schema = BookSchema()
>>> author = Author(name='Chuck Paluhniuk')
>>> book = Book(title='Fight Club', author=author)
>>> db.session.add(author)
>>> db.session.add(book)
>>> db.session.commit()
>>> author_schema.dump(author).data
{'id': 1, 'name': 'Chuck Paluhniuk', 'books': [1]}
`ModelSchema <flask_marshmallow.sqla.ModelSchema>` is nearly identical in API to `marshmallow_sqlalchemy.ModelSchema` with the following exceptions:

- `ModelSchema <flask_marshmallow.sqla.ModelSchema>` uses the scoped session created by Flask-SQLAlchemy.
- `ModelSchema <flask_marshmallow.sqla.ModelSchema>` subclasses `flask_marshmallow.Schema`, so it includes the `jsonify <flask_marshmallow.Schema.jsonify>` method.


API
===

.. automodule:: flask_marshmallow
:inherited-members:
:members:


.. automodule:: flask_marshmallow.fields
:members:

.. automodule:: flask_marshmallow.sqla
:members:


Useful Links
============
Expand Down
60 changes: 34 additions & 26 deletions flask_marshmallow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,21 @@
:license: MIT, see LICENSE for more details.
"""

from flask import jsonify
from marshmallow import (
Schema as BaseSchema,
fields as base_fields,
exceptions,
pprint
)
from . import fields
from .schema import Schema

try:
import flask_sqlalchemy # flake8: noqa
from . import sqla
except ImportError:
has_sqla = False
else:
has_sqla = True

__version__ = '0.6.0.dev'
__author__ = 'Steven Loria'
Expand Down Expand Up @@ -45,32 +52,13 @@ def _attach_fields(obj):
setattr(obj, attr, getattr(fields, attr))


class Schema(BaseSchema):
"""Base serializer with which to define custom serializers.
http://marshmallow.readthedocs.org/en/latest/api_reference.html#serializer
"""

def jsonify(self, obj, many=False, *args, **kwargs):
"""Return a JSON response containing the serialized data.
:param obj: Object to serialize.
:param bool many: Set to `True` if `obj` should be serialized as a collection.
:param kwargs: Additional keyword arguments passed to `flask.jsonify`.
.. versionchanged:: 0.6.0
Takes the same arguments as `marshmallow.Schema.dump`. Additional
keyword arguments are passed to `flask.jsonify`.
"""
data = self.dump(obj, many=many).data
return jsonify(data, *args, **kwargs)

class Marshmallow(object):
"""Wrapper class that integrates Marshmallow with a Flask application.
To use it, instantiate with an application::
from flask import Flask
app = Flask(__name__)
ma = Marshmallow(app)
Expand All @@ -91,20 +79,40 @@ class Meta:
'collection': ma.URLFor('book_list')
})
In order to integrate with Flask-SQLAlchemy, this extension must by initialized *after*
`flask_sqlalchemy.SQLAlchemy`. ::
db = SQLAlchemy(app)
ma = Marshmallow(app)
This gives you access to `ma.ModelSchema`, which generates a marshmallow
`Schema <marshmallow.Schmea>` based on the passed in model. ::
class AuthorSchema(ma.ModelSchema):
class Meta:
model = Author
:param Flask app: The Flask application object.
"""

def __init__(self, app=None):
if app is not None:
self.init_app(app)

self.Schema = Schema
if has_sqla:
self.ModelSchema = sqla.ModelSchema
_attach_fields(self)
if app is not None:
self.init_app(app)

def init_app(self, app):
"""Initializes the application with the extension.
:param Flask app: The Flask application object.
"""
app.extensions = getattr(app, 'extensions', {})

# If using Flask-SQLAlchemy, attach db.session to ModelSchema
if has_sqla and 'sqlalchemy' in app.extensions:
db = app.extensions['sqlalchemy'].db
self.ModelSchema.OPTIONS_CLASS.session = db.session
app.extensions[EXTENSION_NAME] = self
2 changes: 1 addition & 1 deletion flask_marshmallow/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
Custom, Flask-specific fields. See the following link for a list of all available
fields from the marshmallow library.
See http://marshmallow.readthedocs.org/en/latest/api_reference.html#module-marshmallow.fields
See the `marshmallow.fields` module for information about the available fields.
"""
import re
import sys
Expand Down
24 changes: 24 additions & 0 deletions flask_marshmallow/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
import flask
import marshmallow as ma

class Schema(ma.Schema):
"""Base serializer with which to define custom serializers.
See `marshmallow.Schema` for more details about the `Schema` API.
"""

def jsonify(self, obj, many=False, *args, **kwargs):
"""Return a JSON response containing the serialized data.
:param obj: Object to serialize.
:param bool many: Set to `True` if `obj` should be serialized as a collection.
:param kwargs: Additional keyword arguments passed to `flask.jsonify`.
.. versionchanged:: 0.6.0
Takes the same arguments as `marshmallow.Schema.dump`. Additional
keyword arguments are passed to `flask.jsonify`.
"""
data = self.dump(obj, many=many).data
return flask.jsonify(data, *args, **kwargs)
34 changes: 34 additions & 0 deletions flask_marshmallow/sqla.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
"""Integration with Flask-SQLAlchemy and marshmallow-sqlalchemy."""

import marshmallow_sqlalchemy as msqla
from .schema import Schema

class DummySession(object):
"""Placeholder session object."""
pass

class SchemaOpts(msqla.SchemaOpts):
"""Schema options for `ModelSchema <flask_marshmallow.sqla.ModelSchema>`.
Same as `marshmallow_sqlalchemy.SchemaOpts`, except that we add a
placeholder `DummySession` if ``sqla_session`` is not defined on
class Meta. The actual session from `flask_sqlalchemy` gets bound
in `init_app`.
"""
session = DummySession()

def __init__(self, meta):
if not hasattr(meta, 'sqla_session'):
meta.sqla_session = self.session
super(SchemaOpts, self).__init__(meta)

class ModelSchema(msqla.ModelSchema, Schema):
"""ModelSchema that generates fields based on the
`model` class Meta option, which should be a
``db.Model`` class from `flask_sqlalchemy`. Uses the
scoped session from Flask-SQLAlchemy by default.
See `marshmallow_sqlalchemy.ModelSchema` for more details
on the `ModelSchema` API.
"""
OPTIONS_CLASS = SchemaOpts

0 comments on commit 7916b59

Please sign in to comment.