# 快速上手 现在我们就来写第一个REST API。这个教程会假设你是熟悉Flask的,并且确保你已经安装了Flask和Flask-REST-JSONAPI。如果还没有的话,可以先查看安装章节的教程。 本章节,我们会围绕实际的例子和简短的教程,来介绍Flask-REST-JSONAPI的基础使用方法。本教程的数据层,我们将选用默认的Flask-REST-JSONAPI数据层SQLAlchemy。下面,我们就来演示person和computers的API例子。 ## 第一个例子 --- 如下所示,代码构建了Flask-REST-JSONAPI的API: ```python # -*- 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': ''} 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': ''}, related_view='computer_list', related_view_kwargs={'id': ''}, many=True, schema='ComputerSchema', type_='computer') class ComputerSchema(Schema): class Meta: type_ = 'computer' self_view = 'computer_detail' self_view_kwargs = {'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': ''}, related_view='person_detail', related_view_kwargs={'computer_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/', '/computers//owner') api.route(PersonRelationship, 'person_computers', '/persons//relationships/computers') api.route(ComputerList, 'computer_list', '/computers', '/persons//computers') api.route(ComputerDetail, 'computer_detail', '/computers/') api.route(ComputerRelationship, 'computer_person', '/computers//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,所以确保你运行前已经安装。 > > ```bash > $ pip install flask_sqlalchemy > ``` 保存这个文件到api.py,然后用Python运行。请注意代码中我们开启了 [Flask debugging](http://flask.pocoo.org/docs/quickstart/#debug-mode) 模式,这样代码就在修改后自动重载,并且能更好地提示报错信息。 ```bash $ python api.py * Running on http://127.0.0.1:5000/ * Restarting with reloader ``` > ### 警告 > > 请在生产环境中关闭Debug模式。 ## 经典的CRUD操作 --- ### 创建对象 Request: ```http POST /computers HTTP/1.1 Content-Type: application/vnd.api+json Accept: application/vnd.api+json { "data": { "type": "computer", "attributes": { "serial": "Amstrad" } } } ``` Response: ```http 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: ``` http GET /computers HTTP/1.1 Accept: application/vnd.api+json ``` Response: ```http 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: ```http 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 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: ```http DELETE /computers/1 HTTP/1.1 Accept: application/vnd.api+json ``` Response: ```http 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: ```http 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 HTTP/1.1 201 Created Content-Type: application/vnd.api+json { "data": { "type": "person", "id": "1", "attributes": { "display_name": "JOHN ", "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”。 ```http POST /persons?include=computers HTTP/1.1 ``` 正是使用了这个参数,在创建人的同时,也允许添加了关联的电脑。如果你想了解更多细节,请查看包含关联对象章节。 ### 更新对象及其关系 现在,John卖了他的电脑Amstrad,同时买了台新电脑Nestor(id:3)。所以我们需要关联这个新电脑给John。另外,John还填错了生日,所以我们现在需要同时更新两个数据。 Request: ```http 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 HTTP/1.1 200 OK Content-Type: application/vnd.api+json { "data": { "type": "person", "id": "1", "attributes": { "display_name": "JOHN ", "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: ```http 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 HTTP/1.1 200 OK Content-Type: application/vnd.api+json { "data": { "type": "person", "id": "1", "attributes": { "display_name": "JOHN ", "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: ```http 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 HTTP/1.1 200 OK Content-Type: application/vnd.api+json { "data": { "type": "person", "id": "1", "attributes": { "display_name": "JOHN ", "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](http://jsonapi.org/)