Skip to content
This repository has been archived by the owner on May 11, 2021. It is now read-only.

Commit

Permalink
Use Marshmallow for serialization
Browse files Browse the repository at this point in the history
Our use of Flask-Restful's marshalling functionality is hackish. There
exists significant support for moving Flask-Restful's internal
marshalling to Marshmallow [1], so we should switch to that.
Flask-Marshmallow is used for convenience functions (e.g. `jsonify`).

This changeset is strictly a port from Flask-Restful's builtin
marshalling mechanism to Marshmallow. TODOs and FIXMEs still exist and
should be addressed in a separate change.

Flask-Marshmallow relies on changes to `flask.jsonify` made in Flask
0.11, so our version must be upgraded in order to use it. 0.11.1 is the
latest stable release of Flask.

[1] flask-restful/flask-restful#335
  • Loading branch information
jonafato committed Jul 4, 2016
1 parent afebc2a commit 8b9aebd
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 112 deletions.
3 changes: 3 additions & 0 deletions pygotham/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from pygotham import factory

from .core import marshmallow

__all__ = ('create_app',)


Expand All @@ -11,4 +13,5 @@ def create_app(settings_override=None):
:param settings_override: a ``dict`` of settings to override.
"""
app = factory.create_app(__name__, __path__, settings_override)
marshmallow.init_app(app)
return app
5 changes: 5 additions & 0 deletions pygotham/api/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Core API application components."""

from flask_marshmallow import Marshmallow

marshmallow = Marshmallow()
13 changes: 7 additions & 6 deletions pygotham/api/events.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Event-related API endpoints."""

from flask import Blueprint
from flask_restful import Api, Resource, marshal_with
from flask_restful import Api, Resource

from pygotham.events.models import Event

from .fields import event_fields
from .schema import EventSchema


blueprint = Blueprint(
Expand All @@ -17,17 +17,18 @@
class EventListResource(Resource):
"""Return all available event data."""

@marshal_with(event_fields)
def get(self):
"""Event list."""
return Event.query.filter_by(active=True).all()
schema = EventSchema(many=True)
return schema.jsonify(Event.query.filter_by(active=True).all())


@api.resource('/<int:event_id>/')
class EventResource(Resource):
"""Return event core data."""

@marshal_with(event_fields)
def get(self, event_id):
"""Event details."""
return Event.query.filter_by(active=True, id=event_id).first_or_404()
schema = EventSchema()
return schema.jsonify(
Event.query.filter_by(active=True, id=event_id).first_or_404())
100 changes: 0 additions & 100 deletions pygotham/api/fields.py

This file was deleted.

8 changes: 4 additions & 4 deletions pygotham/api/schedule.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Schedule-related API endpoints."""

from flask import Blueprint
from flask_restful import Api, Resource, marshal_with
from flask_restful import Api, Resource

from pygotham.events.models import Event
from pygotham.talks.models import Talk

from .fields import talk_fields
from .schema import TalkSchema

blueprint = Blueprint(
'schedule',
Expand All @@ -21,9 +21,9 @@
class TalkResource(Resource):
"""Represents talks and their place on the schedule."""

@marshal_with(talk_fields)
def get(self, event_id):
"""Return a list of accepted talks."""
event = Event.query.get_or_404(event_id)
talks = Talk.query.filter_by(event=event, status='accepted').all()
return talks
schema = TalkSchema(many=True)
return schema.jsonify(talks)
87 changes: 87 additions & 0 deletions pygotham/api/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""Fieldsets rendered via Flask-Restful."""

from pygotham.models import Event, User, Talk

from .core import marshmallow

__all__ = ('EventSchema', 'UserSchema', 'TalkSchema')


class EventSchema(marshmallow.Schema):
"""Serialization rules for Event objects."""

class Meta:
model = Event
fields = ('id', 'begins', 'ends', 'name', 'registration_url', 'slug')


class UserSchema(marshmallow.Schema):
"""Serialization rules for User objects."""

class Meta:
model = User
additional = ('id', 'bio', 'name')

email = marshmallow.Function(lambda user: '<redacted>')
picture_url = marshmallow.Function(lambda user: None)
twitter_id = marshmallow.Function(lambda user: user.twitter_handle)


class TalkSchema(marshmallow.Schema):
"""Serialization rules for Talk objects."""

class Meta:
model = Talk
additional = ('id', 'description')

conf_key = marshmallow.Function(lambda talk: talk.id)
duration = marshmallow.Function(lambda talk: talk.duration.duration)
language = marshmallow.Function(lambda talk: 'English')
# FIXME: What version are the talks to be licensed under?
license = marshmallow.Function(lambda talk: 'Creative Commons')
priority = marshmallow.Method('get_recording_priority')
released = marshmallow.Function(lambda talk: talk.recording_release)
# TODO: Replace this with a nested SlotSchema.
room = marshmallow.Method('get_room')
room_alias = marshmallow.Method('get_room')
# TODO: Replace this with a nested SlotSchema.
start = marshmallow.Method('get_start_time')
summary = marshmallow.Function(lambda talk: talk.description)
tags = marshmallow.Function(lambda talk: [])
title = marshmallow.Function(lambda talk: talk.name)
user = marshmallow.Nested(UserSchema)

def get_recording_priority(self, talk):
"""Get the numerical recording priority for a talk.
Args:
talk (pygotham.talks.models.Talk): The talk.
"""
# HACK: Generate the recording priority based on recording
# release. We probably won't have any 5s, but this is about as
# correct as the mapping can be at the moment.
priority_mapping = {True: 9, False: 0, None: 5}
return priority_mapping[talk.recording_release]

def get_room(self, talk):
"""Get a one line representation of a talk's scheduled room.
In most cases, talks are in one room. Occasionally, however, a
talk may span multiple rooms. When that happens, return a
descriptive string combining room names.
Args:
talk (pygotham.talks.models.Talk): The talk.
"""
return ' & '.join(room.name for room in talk.presentation.slot.rooms)

def get_start_time(self, talk):
"""Return an IOS8601 formatted start time.
Args:
talk (pygotham.talks.models.Talk): The talk.
"""
return '{:%Y-%m-%d}T{:%H:%M:%S}'.format(
talk.presentation.slot.day.date,
talk.presentation.slot.start,
)
1 change: 1 addition & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Flask-Admin
Flask-Copilot
Flask-Login==0.2.11
Flask-Mail
Flask-Marshmallow
Flask-Migrate
Flask-Principal
Flask-RESTful
Expand Down
6 changes: 4 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,31 @@ Flask-Admin==1.4.0
flask-copilot==0.2.0
Flask-Login==0.2.11 # via flask-security
Flask-Mail==0.9.1 # via flask-security
Flask-Marshmallow==0.7.0
Flask-Migrate==1.7.0
Flask-Principal==0.4.0 # via flask-security
Flask-RESTful==0.3.5
Flask-Script==2.0.5 # via flask-migrate
Flask-Security==1.7.4
Flask-SQLAlchemy==2.1 # via flask-migrate
Flask-WTF==0.12 # via flask-security
Flask==0.10.1 # via flask-admin, flask-copilot, flask-login, flask-mail, flask-migrate, flask-principal, flask-restful, flask-script, flask-security, flask-sqlalchemy, flask-wtf
Flask==0.11.1 # via flask-admin, flask-copilot, flask-login, flask-mail, flask-marshmallow, flask-migrate, flask-principal, flask-restful, flask-script, flask-security, flask-sqlalchemy, flask-wtf
html5lib==0.9999999 # via bleach
infinity==1.3 # via intervals
intervals==0.6.0 # via wtforms-components
itsdangerous==0.24 # via flask, flask-security
Jinja2==2.8 # via flask
Mako==1.0.3 # via alembic
MarkupSafe==0.23 # via jinja2, mako
marshmallow==2.8.0 # via flask-marshmallow
passlib==1.6.5 # via flask-security
psycopg2==2.6.1
python-dateutil==2.4.2 # via aniso8601, arrow
python-editor==0.5 # via alembic
python-slugify==1.2.0
pytz==2015.7 # via flask-restful
raven==5.10.2
six==1.10.0 # via bleach, flask-restful, html5lib, python-dateutil, sqlalchemy-utils, validators, wtforms-alchemy, wtforms-components
six==1.10.0 # via bleach, flask-marshmallow, flask-restful, html5lib, python-dateutil, sqlalchemy-utils, validators, wtforms-alchemy, wtforms-components
sortedcontainers==1.4.4 # via flask-copilot
SQLAlchemy-Utils==0.30.0 # via wtforms-alchemy, wtforms-components
SQLAlchemy==0.9.10 # via alembic, flask-sqlalchemy, sqlalchemy-utils, wtforms-alchemy, wtforms-components
Expand Down

0 comments on commit 8b9aebd

Please sign in to comment.