Quickstart¶
It’s time to write your first advanced REST API. This guide assumes you have a working understanding of Flask, and that you have already installed both Flask and Flask-COMBO-JSONAPI. If not, then follow the steps in the Installation section.
In this section you will learn basic usage of Flask-COMBO-JSONAPI around a small tutorial that uses the SQLAlchemy data layer. This tutorial shows you an example of a person and their computers.
Advanced example¶
An example of Flask-COMBO-JSONAPI API looks like this:
from flask import Flask
from marshmallow import pre_load
from flask_combo_jsonapi import Api, ResourceDetail, ResourceList, ResourceRelationship
from flask_combo_jsonapi.data_layers.alchemy import SqlalchemyDataLayer
from flask_combo_jsonapi.exceptions import ObjectNotFound
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm.exc import NoResultFound
from marshmallow_jsonapi.flask import Schema, Relationship
from marshmallow_jsonapi import fields
# Create the Flask application
app = Flask(__name__)
app.config['DEBUG'] = True
# Initialize SQLAlchemy
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/api.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# Create data storage
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
email = db.Column(db.String)
password = db.Column(db.String)
class Computer(db.Model):
id = db.Column(db.Integer, primary_key=True)
serial = db.Column(db.String)
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
person = db.relationship('Person', backref=db.backref('computers'))
db.create_all()
# Create logical data abstraction (same as data storage for this first example)
class PersonSchema(Schema):
class Meta:
type_ = 'person'
self_view = 'person_detail'
self_view_kwargs = {'id': '<id>'}
self_view_many = 'person_list'
id = fields.Integer(as_string=True, dump_only=True)
name = fields.String(required=True)
email = fields.Email(load_only=True)
display_name = fields.Function(lambda obj: "{} <{}>".format(obj.name.upper(), obj.email))
computers = Relationship(
self_view='person_computers',
self_view_kwargs={'id': '<id>'},
related_view='computer_list',
related_view_kwargs={'id': '<id>'},
many=True,
schema='ComputerSchema',
type_='computer',
)
@pre_load
def remove_id_before_deserializing(self, data, **kwargs):
"""
We don't want to allow editing ID on POST / PATCH
Related issues:
https://github.com/AdCombo/flask-combo-jsonapi/issues/34
https://github.com/miLibris/flask-rest-jsonapi/issues/193
"""
if 'id' in data:
del data['id']
return data
class ComputerSchema(Schema):
class Meta:
type_ = 'computer'
self_view = 'computer_detail'
self_view_kwargs = {'id': '<id>'}
id = fields.Integer(as_string=True, dump_only=True)
serial = fields.String(required=True)
owner = Relationship(
attribute='person',
self_view='computer_person',
self_view_kwargs={'id': '<id>'},
related_view='person_detail',
related_view_kwargs={'computer_id': '<id>'},
schema='PersonSchema',
type_='person',
)
@pre_load
def remove_id_before_deserializing(self, data, **kwargs):
"""
We don't want to allow editing ID on POST / PATCH
Related issues:
https://github.com/AdCombo/flask-combo-jsonapi/issues/34
https://github.com/miLibris/flask-rest-jsonapi/issues/193
"""
if 'id' in data:
del data['id']
return data
# Create resource managers
class PersonList(ResourceList):
schema = PersonSchema
data_layer = {
'session': db.session,
'model': Person,
}
class PersonDetailSqlalchemyDataLayer(SqlalchemyDataLayer):
def before_get_object(self, view_kwargs):
if not view_kwargs.get('computer_id'):
return
try:
computer = self.session.query(Computer).filter_by(
id=view_kwargs['computer_id'],
).one()
except NoResultFound:
raise ObjectNotFound(
"Computer: {} not found".format(view_kwargs['computer_id']),
source={'parameter': 'computer_id'},
)
else:
if computer.person is not None:
view_kwargs['id'] = computer.person.id
else:
view_kwargs['id'] = None
class PersonDetail(ResourceDetail):
schema = PersonSchema
data_layer = {
'session': db.session,
'model': Person,
'class': PersonDetailSqlalchemyDataLayer,
}
class PersonRelationship(ResourceRelationship):
schema = PersonSchema
data_layer = {
'session': db.session,
'model': Person
}
class RelatedComputersSqlalchemyDataLayer(SqlalchemyDataLayer):
def query(self, view_kwargs):
query_ = self.session.query(Computer)
if view_kwargs.get('id') is not None:
try:
self.session.query(Person).filter_by(id=view_kwargs['id']).one()
except NoResultFound:
raise ObjectNotFound(
"Person: {} not found".format(view_kwargs['id']),
source={'parameter': 'id'},
)
else:
query_ = query_.join(Person).filter(Person.id == view_kwargs['id'])
return query_
def before_create_object(self, data, view_kwargs):
if view_kwargs.get('id') is not None:
person = self.session.query(Person).filter_by(id=view_kwargs['id']).one()
data['person_id'] = person.id
class ComputerList(ResourceList):
schema = ComputerSchema
data_layer = {
'session': db.session,
'model': Computer,
'class': RelatedComputersSqlalchemyDataLayer,
}
class ComputerDetail(ResourceDetail):
schema = ComputerSchema
data_layer = {
'session': db.session,
'model': Computer,
}
class ComputerRelationship(ResourceRelationship):
schema = ComputerSchema
data_layer = {
'session': db.session,
'model': Computer,
}
# Create endpoints
api = Api(app)
api.route(PersonList, 'person_list', '/persons')
api.route(PersonDetail, 'person_detail', '/persons/<int:id>', '/computers/<int:computer_id>/owner')
api.route(PersonRelationship, 'person_computers', '/persons/<int:id>/relationships/computers')
api.route(ComputerList, 'computer_list', '/computers', '/persons/<int:id>/computers')
api.route(ComputerDetail, 'computer_detail', '/computers/<int:id>')
api.route(ComputerRelationship, 'computer_person', '/computers/<int:id>/relationships/owner')
if __name__ == '__main__':
# Start application
app.run(debug=True)
This example provides the following API:
Warning
In this example Flask-SQLAlchemy is used, so you’ll need to install it before running this example.
$ pip install flask_sqlalchemy
Save this file as api.py and run it using your Python interpreter. Note that we’ve enabled Flask debugging mode to provide code reloading and better error messages.
$ python api.py
* Running on http://127.0.0.1:5000/
* Restarting with reloader
Warning
Debug mode should never be used in a production environment!
Classical CRUD operations¶
Create object¶
Request:
POST /computers HTTP/1.1
Content-Type: application/vnd.api+json
{
"data": {
"type": "computer",
"attributes": {
"serial": "Amstrad"
}
}
}
Response:
HTTP/1.1 201 Created
Content-Type: application/vnd.api+json
{
"data": {
"attributes": {
"serial": "Amstrad"
},
"id": "1",
"links": {
"self": "/computers/1"
},
"relationships": {
"owner": {
"links": {
"related": "/computers/1/owner",
"self": "/computers/1/relationships/owner"
}
}
},
"type": "computer"
},
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "/computers/1"
}
}
List objects¶
Request:
GET /computers HTTP/1.1
Content-Type: application/vnd.api+json
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": [
{
"attributes": {
"serial": "Amstrad"
},
"id": "1",
"links": {
"self": "/computers/1"
},
"relationships": {
"owner": {
"links": {
"related": "/computers/1/owner",
"self": "/computers/1/relationships/owner"
}
}
},
"type": "computer"
}
],
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "http://localhost:5000/computers"
},
"meta": {
"count": 1
}
}
Update object¶
Request:
PATCH /computers/1 HTTP/1.1
Content-Type: application/vnd.api+json
{
"data": {
"type": "computer",
"id": "1",
"attributes": {
"serial": "New Amstrad"
}
}
}
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": {
"attributes": {
"serial": "New Amstrad"
},
"id": "1",
"links": {
"self": "/computers/1"
},
"relationships": {
"owner": {
"links": {
"related": "/computers/1/owner",
"self": "/computers/1/relationships/owner"
}
}
},
"type": "computer"
},
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "/computers/1"
}
}
Delete object¶
Request:
DELETE /computers/1 HTTP/1.1
Content-Type: application/vnd.api+json
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"jsonapi": {
"version": "1.0"
},
"meta": {
"message": "Object successfully deleted"
}
}
Relationships¶
Update object and his relationships¶
Now John sell his Halo (id=2) and buys a new computer named Nestor (id=3). So we want to link this new computer to John. John have also made a mistake in his email so let’s update these 2 things in the same time.
Request:
PATCH /persons/1?include=computers HTTP/1.1
Content-Type: application/vnd.api+json
{
"data": {
"type": "person",
"id": "1",
"attributes": {
"email": "john@example.com"
},
"relationships": {
"computers": {
"data": [
{
"type": "computer",
"id": "3"
}
]
}
}
}
}
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": {
"attributes": {
"display_name": "JOHN <john@example.com>",
"name": "John"
},
"id": "1",
"links": {
"self": "/persons/1"
},
"relationships": {
"computers": {
"data": [
{
"id": "3",
"type": "computer"
}
],
"links": {
"related": "/persons/1/computers",
"self": "/persons/1/relationships/computers"
}
}
},
"type": "person"
},
"included": [
{
"attributes": {
"serial": "Nestor"
},
"id": "3",
"links": {
"self": "/computers/3"
},
"relationships": {
"owner": {
"links": {
"related": "/computers/3/owner",
"self": "/computers/3/relationships/owner"
}
}
},
"type": "computer"
}
],
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "/persons/1"
}
}
Create relationship¶
Now John buys a new computer named Commodore (id=4) so let’s link it to John.
Request:
POST /persons/1/relationships/computers HTTP/1.1
Content-Type: application/vnd.api+json
{
"data": [
{
"type": "computer",
"id": "4"
}
]
}
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"jsonapi": {
"version": "1.0"
},
"meta": {
"message": "Relationship successfully created"
}
}
Check person’s computers without loading actual person¶
Request:
GET /persons/1/computers HTTP/1.1
Content-Type: application/vnd.api+json
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": [
{
"attributes": {
"serial": "Nestor"
},
"id": "3",
"links": {
"self": "/computers/3"
},
"relationships": {
"owner": {
"links": {
"related": "/computers/3/owner",
"self": "/computers/3/relationships/owner"
}
}
},
"type": "computer"
},
{
"attributes": {
"serial": "Commodore"
},
"id": "4",
"links": {
"self": "/computers/4"
},
"relationships": {
"owner": {
"links": {
"related": "/computers/4/owner",
"self": "/computers/4/relationships/owner"
}
}
},
"type": "computer"
}
],
"jsonapi": {
"version": "1.0"
},
"links": {
"first": "http://localhost:5000/computers",
"last": "http://localhost:5000/computers?page%5Bnumber%5D=2",
"next": "http://localhost:5000/computers?page%5Bnumber%5D=2",
"self": "http://localhost:5000/computers"
},
"meta": {
"count": 2
}
}
Delete relationship¶
Now John sells his old Nestor computer, so let’s unlink it from John.
Request:
DELETE /persons/1/relationships/computers HTTP/1.1
Content-Type: application/vnd.api+json
{
"data": [
{
"type": "computer",
"id": "3"
}
]
}
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"jsonapi": {
"version": "1.0"
},
"meta": {
"message": "Relationship successfully updated"
}
}
If you want to see more examples visit JSON API 1.0 specification