快速上手

现在我们就来写第一个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//computers GET computer_list 获取同某个person关联的computers集合
/persons//computers POST computer_list 创建一个computer,并将其关联到某个person
/persons//relationship/computers GET person_computers 获取person与computers的关系
/persons//relationship/computers POST person_computers 创建person与computers的关系
/persons//relationship/computers PATCH person_computers 更新person与computers的关系
/persons//relationship/computers 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//owner GET person_detail 获取某个computer的owner的详情
/computers//owner PATCH person_detail 更新一个computer的owner
/computers//owner DELETE person_detail 删除一个computer的owner
/computers//relationship/owner GET person_computers 获取person与computer的关系
/computers//relationship/owner POST person_computers 创建person与computer的关系
/computers//relationship/owner PATCH person_computers 更新person与computer的关系
/computers//relationship/owner DELETE person_computers 删除person与computer的关系

警告

本例中,我们使用了Flask-SQLAlchemy,所以确保你运行前已经安装。

$ pip install flask_sqlalchemy

保存这个文件到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