Flask-REST-JSONAPI是Flask的擴展,它爲JSONAPI 1.0規範提供了極大的靈活性,可以快速構建REST API。
Logical data abstraction邏輯數據抽象
這是將資源暴露給api,而不是對數據結構的精確映射。
首先我們有SQLAlchemy的orm:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
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.String)
password = db.Column(db.String)
class Computer(db.Model):
computer_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'))
然後我們構建暴露給前端api的資源邏輯數據抽象:上面的是models下面的是schemas
from marshmallow_jsonapi.flask import Schema, Relationship
from marshmallow_jsonapi import fields
class PersonSchema(Schema): # 這裏的命名是表明加上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()
# 這裏可以發現少了password,因爲我們不想將之暴露給API
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',
id_field='computer_id') # 這裏如果不設置id_field,那麼relationship會去尋找命名爲id的field
class ComputerSchema(Schema):
class Meta:
type_ = 'computer'
self_view = 'computer_detail'
self_view_kwargs = {'id': '<id>'}
id = fields.Str(as_string=True, dump_only=True, attribute='computer_id') # 這裏可以看到我們將computer_id暴露成id,是爲了保持API的一致性
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')
這裏需要將某個字段暴露給id,是爲了保證API的一致性
Resource Manager 資源管理器
資源管理器是邏輯數據抽象,數據層和可選的其他軟件之間的鏈接。它是您的資源的邏輯管理所在的位置。
這個拓展提供了三種默認的資源管理方式:
- ResourceList: 實現了get和post方法用來檢索或者創建一個對象的集合
- ResourceDetail: 實現了get,patch,delete方法,用於獲取對象的detail,更新對象,刪除對象
- ResourceRelationship:實現了 get, post, patch and delete 方法,用於獲取,更新,創建,刪除對象之間的聯繫
如果要使用默認的資源管理方式,我們至少需要設置兩個必需的屬性:schema和data_layer
schema就是之前定義的邏輯數據抽象
data_layer用於初始化你的data layer,後面會講到
例子:
from flask_rest_jsonapi import ResourceList
from your_project.schemas import PersonSchema
from your_project.models import Person
from your_project.extensions import db
class PersonList(ResourceList):
schema = PersonSchema
data_layer = {'session': db.session,
'model': Person}
可選屬性:
method: 列出資源管理可以使用的方法,如果不指明任何方法,那麼多有方法都會被處理
decorators: 一個列出所有裝飾器的tuple
get_schema_kwargs、post_schema_kwargs、before_get、after_get…
class PersonList(ResourceDetail):
schema = PersonSchema
data_layer = {'session': db.session,
'model': Person}
methods = ['GET', 'PATCH']
decorators = (login_required, )
get_schema_kwargs = {'only': ('name', )}
def before_patch(*args, **kwargs):
"""Make custom work here. View args and kwargs are provided as parameter
"""
Data layer (在資源管理器裏面定義)
Data layer是資源管理和數據之間CRUD(增刪改查)的接口,他非常靈活可以使用任何ORM或者數據儲存,它同時也支持管理分頁,過濾和排序功能。
最基本的data_layer是這樣:
data_layer = {'session': db.session,
'model': Person}
還可以附加兩種方法:
- query: 使用view_kwargs當做參數,然後去檢索想要的對象集合
- pre / post process methods: 在增刪改查的前後都可以做一些操作,可用的方法見鏈接data_layer dase
class ComputerList(ResourceList):
def query(self, view_kwargs):
query_ = self.session.query(Computer)
if view_kwargs.get('id') is not None: # 當view裏面的id不爲空的時候,查詢的時候需要加上filter
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}}
Routing路由
模板:api.route(<Resource manager>, <endpoint name>, <url_1>, <url_2>, ...)
# all required imports are not displayed in this example
from flask_rest_jsonapi import Api
api = Api()
# 第一個參數對應資源管理器名字,第二個對應self_view,後面的對應self_view_kwargs
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')
filtering 過濾
GET /persons?filter=[{"name":"name","op":"eq","val":"John"}] HTTP/1.1
Accept: application/vnd.api+json
這個accept格式一定需要帶上
請求的url裏面帶有過濾信息
name: 需要過濾的字段,op:比較的方法, val:需要比較的值
GET /persons?filter=[{"name":"name","op":"eq","field":"birth_date"}] HTTP/1.1
Accept: application/vnd.api+json
這個例子裏面是沒有特定的比較值,需要提取名字等於生日的對象集合
如果要根據對象之間的關係過濾:
GET /persons?filter=[
{
"name": "computers",
"op": "any",
"val": {
"name": "serial",
"op": "ilike",
"val": "%Amstrad%"
}
}
] HTTP/1.1
Accept: application/vnd.api+json
簡單filter: 只支持相等操作的filter
GET /persons?filter[name]=John HTTP/1.1
Accept: application/vnd.api+json
或者
GET /persons?filter[name]=John&filter[gender]=male HTTP/1.1
Accept: application/vnd.api+json
Include related objects
可以使用名爲“include”的查詢字符串參數將相關對象詳細信息包含在響應中
GET /persons/1?include=computers HTTP/1.1
Accept: application/vnd.api+json
Sparse fieldsets
返回指定的字段:?fields[<resource_type>]=<list of fields to return>
GET /persons?fields[person]=display_name HTTP/1.1
Accept: application/vnd.api+json
在這個例子裏面只有display_name這個field會被API返回
下面這個例子會返回兩個表裏面的指定字段
GET /persons/1?include=computers&fields[computer]=serial&fields[person]=name,computers HTTP/1.1
Accept: application/vnd.api+json
Pagination 分頁
默認的分頁是30條一頁,但是我們可以使用page屬性來指定分頁的單頁數量
GET /persons?page[size]=10 HTTP/1.1
Accept: application/vnd.api+json
GET /persons?page[number]=2 HTTP/1.1
Accept: application/vnd.api+json
GET /persons?page[size]=10&page[number]=2 HTTP/1.1
Accept: application/vnd.api+json
Sorting 排序
GET /persons?sort=name HTTP/1.1
Accept: application/vnd.api+json
多字段排序:
GET /persons?sort=name,birth_date HTTP/1.1
Accept: application/vnd.api+json
降序排序:
GET /persons?sort=-name HTTP/1.1
Accept: application/vnd.api+json