Tutorial

In this tutorial, we will cover a simple blog example with topic, post and author entities.

Note

I don’t include imports on this tutorial for readability but you can see them in examples/full_example.py.

Note

All requests and responses are well formatted for better readability.

Initialize flask application, API and database

app = Flask(__name__)
api = Api(app)
engine = create_engine('sqlite:////tmp/test.db')
Session = sessionmaker(bind=engine)
session = Session()

Define and initialize models

Base = declarative_base()


class Topic(Base):
    __tablename__ = 'topic'

    id = Column(Integer, primary_key=True)
    name = Column(String)


class Post(Base):
    __tablename__ = 'post'

    id = Column(Integer, primary_key=True)
    title = Column(String)
    content = Column(String)
    topic_id = Column(Integer, ForeignKey('topic.id'))
    author_id = Column(Integer, ForeignKey('author.id'))

    topic = relationship("Topic", backref="posts")
    author = relationship("Author", backref="posts")


class Author(Base):
    __tablename__ = 'author'

    id = Column(Integer, primary_key=True)
    name = Column(String)

Base.metadata.create_all(engine)

Define marshamllow-jsonapi schemas

class TopicSchema(Schema):

    class Meta:
        type_ = 'topic'
        self_view = 'topic_detail'
        self_view_kwargs = {'topic_id': '<id>'}
        self_view_many = 'topic_list'

    id = fields.Str(dump_only=True)
    name = fields.Str(required=True)

    posts = Relationship(related_view='post_list',
                         related_view_kwargs={'topic_id': '<id>'},
                         many=True,
                         type_='post')


class PostSchema(Schema):

    class Meta:
        type_ = 'post'
        self_view = 'post_detail'
        self_view_kwargs = {'post_id': '<id>'}

    id = fields.Str(dump_only=True)
    title = fields.Str(required=True)
    content = fields.Str()
    author_name = fields.Function(lambda obj: obj.author.name)
    author_id = fields.Int(required=True)

    topic = Relationship(related_view='topic_detail',
                         related_view_kwargs={'topic_id': '<topic.id>'},
                         type_='topic')

    author = Relationship(related_view='author_detail',
                          related_view_kwargs={'author_id': '<author.id>'},
                          type_='author')


class AuthorSchema(Schema):

    class Meta:
        type_ = 'author'
        self_view = 'author_detail'
        self_view_kwargs = {'author_id': '<id>'}
        self_view_many = 'author_list'

    id = fields.Str(dump_only=True)
    name = fields.Str(required=True)

    posts = Relationship(related_view='post_list',
                         related_view_kwargs={'author_id': '<id>'},
                         many=True,
                         type_='post')

Register resources and routes

def topic_get_base_query(self, **view_kwargs):
    return self.session.query(Topic)

api.list_route('topic_list',
               '/topics',
               resource_type='topic',
               schema=TopicSchema,
               data_layer=SqlalchemyDataLayer,
               data_layer_kwargs={'model': Topic, 'session': session},
               data_layer_additional_functions={'get_base_query': topic_get_base_query})

api.detail_route('topic_detail',
                 '/topics/<int:topic_id>',
                 resource_type='topic',
                 schema=TopicSchema,
                 data_layer=SqlalchemyDataLayer,
                 data_layer_kwargs={'model': Topic, 'session': session, 'id_field': 'id', 'url_param_name': 'topic_id'})


def post_get_base_query(self, **view_kwargs):
    query = self.session.query(Post)

    if view_kwargs.get('topic_id'):
        query = query.join(Topic).filter_by(id=view_kwargs['topic_id'])
    elif view_kwargs.get('author_id'):
        query = query.join(Author).filter_by(id=view_kwargs['author_id'])

    return query


def post_before_create_instance(self, data, **view_kwargs):
    try:
        topic = self.session.query(Topic).filter_by(id=str(view_kwargs['topic_id'])).one()
    except NoResultFound:
        return ErrorFormatter.format_error(['Topic not found']), 404

    data['topic'] = topic

api.list_route('post_list',
               '/topics/<int:topic_id>/posts',
               '/authors/<int:author_id>/posts',
               resource_type='post',
               schema=PostSchema,
               schema_get_kwargs={'exclude': ('author_id', )},
               schema_post_kwargs={'exclude': ('author_name', )},
               data_layer=SqlalchemyDataLayer,
               data_layer_kwargs={'model': Post, 'session': session},
               data_layer_additional_functions={'get_base_query': post_get_base_query,
                                                'before_create_instance': post_before_create_instance},
               endpoint_include_view_kwargs=True)

api.detail_route('post_detail',
                 '/posts/<int:post_id>',
                 resource_type='post',
                 schema=PostSchema,
                 schema_get_kwargs={'exclude': ('author_id', )},
                 data_layer=SqlalchemyDataLayer,
                 data_layer_kwargs={'model': Post, 'session': session, 'id_field': 'id', 'url_param_name': 'post_id'})


def author_get_base_query(self, **view_kwargs):
    return self.session.query(Author)

api.list_route('author_list',
               '/authors',
               resource_type='author',
               schema=AuthorSchema,
               data_layer=SqlalchemyDataLayer,
               data_layer_kwargs={'model': Author, 'session': session},
               data_layer_additional_functions={'get_base_query': author_get_base_query})

api.detail_route('author_detail',
                 '/authors/<int:author_id>',
                 resource_type='author',
                 schema=AuthorSchema,
                 data_layer=SqlalchemyDataLayer,
                 data_layer_kwargs={'model': Author,
                                    'session': session,
                                    'id_field': 'id',
                                    'url_param_name': 'author_id'})

If you want to separate resource configuration from routing, you can do something like that:

def get_base_query(self, **view_kwargs):
    return self.session.query(Topic)


class TopicResourceList(ResourceList):

    class Meta:
        data_layer = {'cls': SqlalchemyDataLayer,
                      'kwargs': {'model': Topic, 'session': sql_db.session},
                      'get_base_query': get_base_query}

    resource_type = 'topic'
    schema = {'cls': TopicSchema}
    endpoint = {'name': 'topic_list'}

api.list_route('topic_list', '/topics', resource_cls=TopicResourceList)

List topics

Request:

$ curl "http://127.0.0.1:5000/topics" -H "Content-Type: application/vnd.api+json"\
  -H "Accept: application/vnd.api+json"

Response:

{
    "data": [],
    "links": {
        "first": "/topics",
        "last": "/topics",
        "self": "/topics"
    }
}

Create topic

Request:

$ curl "http://127.0.0.1:5000/topics" -X POST\
  -H "Content-Type: application/vnd.api+json"\
  -H "Accept: application/vnd.api+json"\
  -d '{
    "data": {
      "type": "topic",
      "attributes": {
        "name": "topic 1"
      }
    }
  }'

Response:

{
    "data": {
        "attributes": {
            "name": "topic 1"
        },
        "id": "1",
        "links": {
            "self": "/topics/1"
        },
        "relationships": {
            "posts": {
                "links": {
                    "related": "/topics/1/posts"
                }
            }
        },
        "type": "topic"
    },
    "links": {
        "self": "/topics/1"
    }
}

Now you can list again topics:

Request:

$ curl "http://127.0.0.1:5000/topics" -H "Content-Type: application/vnd.api+json"\
  -H "Accept: application/vnd.api+json"

Response:

{
    "data": [
        {
            "attributes": {
                "name": "topic 1"
            },
            "id": "1",
            "links": {
                "self": "/topics/1"
            },
            "relationships": {
                "posts": {
                    "links": {
                        "related": "/topics/1/posts"
                    }
                }
            },
            "type": "topic"
        }
    ],
    "links": {
        "first": "/topics",
        "last": "/topics?page%5Bnumber%5D=1",
        "self": "/topics"
    }
}

Update topic

Request:

$ curl "http://127.0.0.1:5000/topics/1" -X PATCH\
  -H "Content-Type: application/vnd.api+json"\
  -H "Accept: application/vnd.api+json"\
  -d '{
    "data": {
      "type": "topic",
      "id": "1",
      "attributes": {
        "name": "topic 1 updated"
      }
    }
  }'

Response:

{
    "data": {
        "attributes": {
            "name": "topic 1 updated"
        },
        "id": "1",
        "links": {
            "self": "/topics/1"
        },
        "relationships": {
            "posts": {
                "links": {
                    "related": "/topics/1/posts"
                }
            }
        },
        "type": "topic"
    },
    "links": {
        "self": "/topics/1"
    }
}

Delete topic

Request:

$ curl "http://127.0.0.1:5000/topics/1" -X DELETE\
  -H "Content-Type: application/vnd.api+json"\
  -H "Accept: application/vnd.api+json"

Create author

Request:

$ curl "http://127.0.0.1:5000/authors" -X POST\
  -H "Content-Type: application/vnd.api+json"\
  -H "Accept: application/vnd.api+json"\
  -d '{
    "data": {
      "type": "author",
      "attributes": {
        "name": "John Smith"
      }
    }
  }'

Response:

{
    "data": {
        "attributes": {
            "name": "John Smith"
        },
        "id": "1",
        "links": {
            "self": "/authors/1"
        },
        "relationships": {
            "posts": {
                "links": {
                    "related": "/authors/1/posts"
                }
            }
        },
        "type": "author"
    },
    "links": {
        "self": "/authors/1"
    }
}

Create post with an author in a topic

Before creating a post, we have to create a topic (because we have deleted the only one previously)

Request:

$ curl "http://127.0.0.1:5000/topics" -X POST\
  -H "Content-Type: application/vnd.api+json"\
  -H "Accept: application/vnd.api+json"\
  -d '{
    "data": {
      "type": "topic",
      "attributes": {
        "name": "topic 1"
      }
    }
  }'

Response:

{
    "data": {
        "attributes": {
            "name": "topic 1"
        },
        "id": "1",
        "links": {
            "self": "/topics/1"
        },
        "relationships": {
            "posts": {
                "links": {
                    "related": "/topics/1/posts"
                }
            }
        },
        "type": "topic"
    },
    "links": {
        "self": "/topics/1"
    }
}

Now we have a new topic, so let’s create a post for it

Request:

$ curl "http://127.0.0.1:5000/topics/1/posts" -X POST\
  -H "Content-Type: application/vnd.api+json"\
  -H "Accept: application/vnd.api+json"\
  -d '{
    "data": {
      "type": "post",
      "attributes": {
        "title": "post 1",
        "content": "content of the post 1",
        "author_id": "1"
      }
    }
  }'

Response:

{
    "data": {
        "attributes": {
            "author_id": 1,
            "content": "content of the post 1",
            "title": "post 1"
        },
        "id": "1",
        "links": {
            "self": "/posts/1"
        },
        "relationships": {
            "author": {
                "links": {
                    "related": "/authors/1"
                }
            },
            "topic": {
                "links": {
                    "related": "/topics/2"
                }
            }
        },
        "type": "post"
    },
    "links": {
        "self": "/posts/1"
    }
}

List posts of topic 1

Request:

$ curl "http://127.0.0.1:5000/topics/1/posts" -H "Content-Type: application/vnd.api+json"\
  -H "Accept: application/vnd.api+json"

Response:

{
    "data": [
        {
            "attributes": {
                "author_name": "John Smith",
                "content": "content of the post 1",
                "title": "post 1"
            },
            "id": "1",
            "links": {
                "self": "/posts/1"
            },
            "relationships": {
                "author": {
                    "links": {
                        "related": "/authors/1"
                    }
                },
                "topic": {
                    "links": {
                        "related": "/topics/1"
                    }
                }
            },
            "type": "post"
        }
    ],
    "links": {
        "first": "/topics/1/posts",
        "last": "/topics/1/posts?page%5Bnumber%5D=1",
        "self": "/topics/1/posts"
    }
}

List posts of author 1 (John Smith)

Request:

$ curl "http://127.0.0.1:5000/authors/1/posts" -H "Content-Type: application/vnd.api+json"\
  -H "Accept: application/vnd.api+json"

Response:

{
    "data": [
        {
            "attributes": {
                "author_name": "John Smith",
                "content": "content of the post 1",
                "title": "post 1"
            },
            "id": "1",
            "links": {
                "self": "/posts/1"
            },
            "relationships": {
                "author": {
                    "links": {
                        "related": "/authors/1"
                    }
                },
                "topic": {
                    "links": {
                        "related": "/topics/1"
                    }
                }
            },
            "type": "post"
        }
    ],
    "links": {
        "first": "/authors/1/posts",
        "last": "/authors/1/posts?page%5Bnumber%5D=1",
        "self": "/authors/1/posts"
    }
}