快速上手¶
现在我们就来写第一个REST API。这个教程会假设你是熟悉Flask的,并且确保你已经安装了Flask和Flask-REST-JSONAPI。如果还没有的话,可以先查看安装章节的教程。
本章节,我们会围绕实际的例子和简短的教程,来介绍Flask-REST-JSONAPI的基础使用方法。本教程的数据层,我们将选用默认的Flask-REST-JSONAPI数据层SQLAlchemy。下面,我们就来演示person和computers的API例子。
第一个例子¶
如下所示,代码构建了Flask-REST-JSONAPI的API:
# -*- coding: utf-8 -*-
from flask import Flask
from flask_rest_jsonapi import Api, ResourceDetail, ResourceList, ResourceRelationship
from flask_rest_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/test.db'
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)
birth_date = db.Column(db.Date)
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.Str(required=True, load_only=True)
email = fields.Email(load_only=True)
birth_date = fields.Date()
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')
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.Str(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')
# Create resource managers
class PersonList(ResourceList):
schema = PersonSchema
data_layer = {'session': db.session,
'model': Person}
class PersonDetail(ResourceDetail):
def before_get_object(self, view_kwargs):
if view_kwargs.get('computer_id') is not None:
try:
computer = self.session.query(Computer).filter_by(id=view_kwargs['computer_id']).one()
except NoResultFound:
raise ObjectNotFound({'parameter': 'computer_id'},
"Computer: {} not found".format(view_kwargs['computer_id']))
else:
if computer.person is not None:
view_kwargs['id'] = computer.person.id
else:
view_kwargs['id'] = None
schema = PersonSchema
data_layer = {'session': db.session,
'model': Person,
'methods': {'before_get_object': before_get_object}}
class PersonRelationship(ResourceRelationship):
schema = PersonSchema
data_layer = {'session': db.session,
'model': Person}
class ComputerList(ResourceList):
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({'parameter': 'id'}, "Person: {} not found".format(view_kwargs['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
schema = ComputerSchema
data_layer = {'session': db.session,
'model': Computer,
'methods': {'query': query,
'before_create_object': before_create_object}}
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)
这个例子提供了以下API接口:
| url | method | endpoint | action |
|---|---|---|---|
| /persons | GET | person_list | 获取persons集合 |
| /persons | POST | person_list | 创建一个person |
| /persons/ |
GET | person_detail | 获取一个person的详情 |
| /persons/ |
PATCH | person_detail | 更新一个person |
| /persons/ |
DELETE | person_detail | 删除一个person |
| /persons/ |
GET | computer_list | 获取同某个person关联的computers集合 |
| /persons/ |
POST | computer_list | 创建一个computer,并将其关联到某个person |
| /persons/ |
GET | person_computers | 获取person与computers的关系 |
| /persons/ |
POST | person_computers | 创建person与computers的关系 |
| /persons/ |
PATCH | person_computers | 更新person与computers的关系 |
| /persons/ |
DELETE | person_computers | 删除person与computers的关系 |
| /computers | GET | computer_list | 获取computers集合 |
| /computers | POST | computer_list | 创建一个computer |
| /computers/ |
GET | computer_detail | 获取一个computer的详情 |
| /computers/ |
PATCH | computer_detai | 更新一个computer |
| /computers/ |
DELETE | computer_detail | 删除一个computer |
| /computers/ |
GET | person_detail | 获取某个computer的owner的详情 |
| /computers/ |
PATCH | person_detail | 更新一个computer的owner |
| /computers/ |
DELETE | person_detail | 删除一个computer的owner |
| /computers/ |
GET | person_computers | 获取person与computer的关系 |
| /computers/ |
POST | person_computers | 创建person与computer的关系 |
| /computers/ |
PATCH | person_computers | 更新person与computer的关系 |
| /computers/ |
DELETE | person_computers | 删除person与computer的关系 |
保存这个文件到api.py,然后用Python运行。请注意代码中我们开启了 Flask debugging 模式,这样代码就在修改后自动重载,并且能更好地提示报错信息。
$ python api.py
* Running on http://127.0.0.1:5000/
* Restarting with reloader
警告¶
请在生产环境中关闭Debug模式。
经典的CRUD操作¶
创建对象¶
Request:
POST /computers HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": {
"type": "computer",
"attributes": {
"serial": "Amstrad"
}
}
}
Response:
HTTP/1.1 201 Created
Content-Type: application/vnd.api+json
{
"data": {
"type": "computer",
"id": "1",
"attributes": {
"serial": "Amstrad"
},
"relationships": {
"owner": {
"links": {
"related": "/computers/1/owner",
"self": "/computers/1/relationships/owner"
}
}
},
"links": {
"self": "/computers/1"
}
},
"links": {
"self": "/computers/1"
},
"jsonapi": {
"version": "1.0"
}
}
获取对象列表¶
Request:
GET /computers HTTP/1.1
Accept: application/vnd.api+json
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": [
{
"type": "computer",
"id": "1",
"attributes": {
"serial": "Amstrad"
},
"relationships": {
"owner": {
"links": {
"related": "/computers/1/owner",
"self": "/computers/1/relationships/owner"
}
}
},
"links": {
"self": "/computers/1"
}
}
],
"meta": {
"count": 1
},
"links": {
"self": "/computers"
},
"jsonapi": {
"version": "1.0"
},
}
更新对象¶
Request:
PATCH /computers/1 HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": {
"type": "computer",
"id": "1",
"attributes": {
"serial": "Amstrad 2"
}
}
}
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": {
"type": "computer",
"id": "1",
"attributes": {
"serial": "Amstrad 2"
},
"relationships": {
"owner": {
"links": {
"related": "/computers/1/owner",
"self": "/computers/1/relationships/owner"
}
}
},
"links": {
"self": "/computers/1"
}
},
"links": {
"self": "/computers/1"
},
"jsonapi": {
"version": "1.0"
}
}
删除对象¶
Request:
DELETE /computers/1 HTTP/1.1
Accept: application/vnd.api+json
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"meta": {
"message": "Object successfully deleted"
},
"jsonapi": {
"version": "1.0"
}
}
关系¶
现在我们来看一下关系的使用。第一步,如同上例中,分别创建3个名叫Halo,Nestor和Comodor的电脑。
完成了?接下来,我们继续。
我们假设Halo的id是2,Nestor的id是3,Comodor的id是4。
创建带有关系的对象¶
Request:
POST /persons?include=computers HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": {
"type": "person",
"attributes": {
"name": "John",
"email": "john@gmail.com",
"birth_date": "1990-12-18"
},
"relationships": {
"computers": {
"data": [
{
"type": "computer",
"id": "1"
}
]
}
}
}
}
Response:
HTTP/1.1 201 Created
Content-Type: application/vnd.api+json
{
"data": {
"type": "person",
"id": "1",
"attributes": {
"display_name": "JOHN <john@gmail.com>",
"birth_date": "1990-12-18"
},
"links": {
"self": "/persons/1"
},
"relationships": {
"computers": {
"data": [
{
"id": "1",
"type": "computer"
}
],
"links": {
"related": "/persons/1/computers",
"self": "/persons/1/relationships/computers"
}
}
},
},
"included": [
{
"type": "computer",
"id": "1",
"attributes": {
"serial": "Amstrad"
},
"links": {
"self": "/computers/1"
},
"relationships": {
"owner": {
"links": {
"related": "/computers/1/owner",
"self": "/computers/1/relationships/owner"
}
}
}
}
],
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "/persons/1"
}
}
你可以看到,我在url中添加了查询语句参数“include”。
POST /persons?include=computers HTTP/1.1
正是使用了这个参数,在创建人的同时,也允许添加了关联的电脑。如果你想了解更多细节,请查看包含关联对象章节。
更新对象及其关系¶
现在,John卖了他的电脑Amstrad,同时买了台新电脑Nestor(id:3)。所以我们需要关联这个新电脑给John。另外,John还填错了生日,所以我们现在需要同时更新两个数据。
Request:
PATCH /persons/1?include=computers HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": {
"type": "person",
"id": "1",
"attributes": {
"birth_date": "1990-10-18"
},
"relationships": {
"computers": {
"data": [
{
"type": "computer",
"id": "3"
}
]
}
}
}
}
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": {
"type": "person",
"id": "1",
"attributes": {
"display_name": "JOHN <john@gmail.com>",
"birth_date": "1990-10-18",
},
"links": {
"self": "/persons/1"
},
"relationships": {
"computers": {
"data": [
{
"id": "3",
"type": "computer"
}
],
"links": {
"related": "/persons/1/computers",
"self": "/persons/1/relationships/computers"
}
}
},
},
"included": [
{
"type": "computer",
"id": "3",
"attributes": {
"serial": "Nestor"
},
"relationships": {
"owner": {
"links": {
"related": "/computers/3/owner",
"self": "/computers/3/relationships/owner"
}
}
},
"links": {
"self": "/computers/3"
}
}
],
"links": {
"self": "/persons/1"
},
"jsonapi": {
"version": "1.0"
}
}
创建关系¶
现在John买了台叫Comodor的新电脑,让我们把他关联给John。
Request:
POST /persons/1/relationships/computers HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": [
{
"type": "computer",
"id": "4"
}
]
}
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": {
"type": "person",
"id": "1",
"attributes": {
"display_name": "JOHN <john@gmail.com>",
"birth_date": "1990-10-18"
},
"relationships": {
"computers": {
"data": [
{
"id": "3",
"type": "computer"
},
{
"id": "4",
"type": "computer"
}
],
"links": {
"related": "/persons/1/computers",
"self": "/persons/1/relationships/computers"
}
}
},
"links": {
"self": "/persons/1"
}
},
"included": [
{
"type": "computer",
"id": "3",
"attributes": {
"serial": "Nestor"
},
"relationships": {
"owner": {
"links": {
"related": "/computers/3/owner",
"self": "/computers/3/relationships/owner"
}
}
},
"links": {
"self": "/computers/3"
}
},
{
"type": "computer",
"id": "4",
"attributes": {
"serial": "Comodor"
},
"relationships": {
"owner": {
"links": {
"related": "/computers/4/owner",
"self": "/computers/4/relationships/owner"
}
}
},
"links": {
"self": "/computers/4"
}
}
],
"links": {
"self": "/persons/1/relationships/computers"
},
"jsonapi": {
"version": "1.0"
}
}
删除关系¶
现在,John卖了旧的Nestor电脑,让我们给John移除关联。
Request:
DELETE /persons/1/relationships/computers HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": [
{
"type": "computer",
"id": "3"
}
]
}
Response:
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": {
"type": "person",
"id": "1",
"attributes": {
"display_name": "JOHN <john@gmail.com>",
"birth_date": "1990-10-18"
},
"relationships": {
"computers": {
"data": [
{
"id": "4",
"type": "computer"
}
],
"links": {
"related": "/persons/1/computers",
"self": "/persons/1/relationships/computers"
}
}
},
"links": {
"self": "/persons/1"
}
},
"included": [
{
"type": "computer",
"id": "4",
"attributes": {
"serial": "Comodor"
},
"relationships": {
"owner": {
"links": {
"related": "/computers/4/owner",
"self": "/computers/4/relationships/owner"
}
}
},
"links": {
"self": "/computers/4"
}
}
],
"links": {
"self": "/persons/1/relationships/computers"
},
"jsonapi": {
"version": "1.0"
}
}
如果你想了解更多例子,请访问 JSON API 1.0 specification