Flask-REST-JSONAPI

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 資源管理器

資源管理器是邏輯數據抽象,數據層和可選的其他軟件之間的鏈接。它是您的資源的邏輯管理所在的位置。
這個拓展提供了三種默認的資源管理方式:

  1. ResourceList: 實現了get和post方法用來檢索或者創建一個對象的集合
  2. ResourceDetail: 實現了get,patch,delete方法,用於獲取對象的detail,更新對象,刪除對象
  3. 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}

還可以附加兩種方法:

  1. query: 使用view_kwargs當做參數,然後去檢索想要的對象集合
  2. 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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章