RestAPI

从 19.5.10 版本起,pyDAL 包含一个名为 RestAPI 的 restful API [CIT0801] 。它的灵感来自 GraphQL [CIT0802] ,虽然由于功能较弱,它们并不完全相同,但 RestAPI 符合 py4web 的精神,因为它更实用、更易于使用。

与 GraphQL 一样,RestAPI 允许客户端使用 GET 方法查询信息,并允许指定有关响应格式的一些详细信息(要遵循哪些引用,以及如何对数据进行非规范化)。与 GraphQL 不同之处,它允许服务器指定策略并限制允许哪些查询和不允许哪些查询。可以根据用户和服务器的状态对每个请求进行动态评估。

顾名思义,RestAPI 允许所有标准方法:GET、POST、PUT 和 DELETE 。对于单个表和单个字段,可以根据策略启用或禁用它们中的任意一个。

备注

由于这是一项新功能,技术规范可能会发生变化。

在下面的示例中,我们假设有一个叫做 “superheroes” 的简单应用:

# in superheroes/__init__.py
import os
from py4web import action, request, Field, DAL
from pydal.restapi import RestAPI, Policy

# database definition
DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases')
if not os.path.isdir(DB_FOLDER):
    os.mkdir(DB_FOLDER)
db = DAL('sqlite://storage.sqlite', folder=DB_FOLDER)
db.define_table(
    'person',
    Field('name'),
    Field('job'))
db.define_table(
    'superhero',
    Field('name'),
    Field('real_identity', 'reference person'))
db.define_table(
    'superpower',
    Field('description'))
db.define_table(
    'tag',
    Field('superhero', 'reference superhero'),
    Field('superpower', 'reference superpower'),
    Field('strength', 'integer'))

# add example entries in db
if not db(db.person).count():
    db.person.insert(name='Clark Kent', job='Journalist')
    db.person.insert(name='Peter Park', job='Photographer')
    db.person.insert(name='Bruce Wayne', job='CEO')
    db.superhero.insert(name='Superman', real_identity=1)
    db.superhero.insert(name='Spiderman', real_identity=2)
    db.superhero.insert(name='Batman', real_identity=3)
    db.superpower.insert(description='Flight')
    db.superpower.insert(description='Strength')
    db.superpower.insert(description='Speed')
    db.superpower.insert(description='Durability')
    db.tag.insert(superhero=1, superpower=1, strength=100)
    db.tag.insert(superhero=1, superpower=2, strength=100)
    db.tag.insert(superhero=1, superpower=3, strength=100)
    db.tag.insert(superhero=1, superpower=4, strength=100)
    db.tag.insert(superhero=2, superpower=2, strength=50)
    db.tag.insert(superhero=2, superpower=3, strength=75)
    db.tag.insert(superhero=2, superpower=4, strength=10)
    db.tag.insert(superhero=3, superpower=2, strength=80)
    db.tag.insert(superhero=3, superpower=3, strength=20)
    db.tag.insert(superhero=3, superpower=4, strength=70)
    db.commit()

# policy definitions
policy = Policy()
policy.set('superhero', 'GET', authorize=True, allowed_patterns=['*'])
policy.set('*', 'GET', authorize=True, allowed_patterns=['*'])

# for security reasons we disabled here all methods but GET at the policy level,
# to enable any of them just set authorize = True
policy.set('*', 'PUT', authorize=False)
policy.set('*', 'POST', authorize=False)
policy.set('*', 'DELETE', authorize=False)

@action('api/<tablename>/', method = ['GET', 'POST'])
@action('api/<tablename>/<rec_id>', method = ['GET', 'PUT', 'DELETE'])
@action.uses(db)
def api(tablename, rec_id=None):
    return RestAPI(db, policy)(request.method,
                            tablename,
                            rec_id,
                            request.GET,
                            request.POST
                            )

@action("index")
def index():
    return "RestAPI example"

RestAPI 的 policy 和 action

该策略是针对每个表(或 * 代表的所有表)和每个方法的。 authorize 可以是 True(允许)、False(拒绝),也可以是返回 True/False 的带有签名的函数( (method, tablename, record_id, get_vars, post_vars))。对于 GET 策略,可以指定一个允许的查询模式列表( * 代表所有模式)。查询模式将与查询字符串中的键进行匹配。

上述 action 公开了:

/superheroes/api/{tablename}
/superheroes/api/{tablename}/{rec_id}

结果可以直接用浏览器看到,呈现为 JSON 格式。作为示例,让我们看一下 person 表:

_images/restapi.png

superhero 的数据库的示意图应该可以帮助你理解代码:

_images/restapi2.png

备注

请记住, request.POST 只包含使用 regular HTML-formJavaScript FormData 对象发送的表单数据。

RestAPI GET

一般查询的格式为 {something}.eq=value ,其中 eq= 表示“相等”, gt= 表示“大于”,以此类推。表达式前面可以加上 not.

{something} 可以是:

  • 表中的字段名称的查询,示例如下:

    所有被称为 “Superman” 的超级英雄

    /superheroes/api/superhero?name.eq=Superman
    
  • 被查询的表引用的表的字段的名称,示例如下:

    所有真实身份是 “Clark Kent” 的超级英雄

    /superheroes/api/superhero?real_identity.name.eq=Clark Kent
    
  • 引用被查询表的字段的名称,如下所示

    所有拥有超级力量大于 90 的超级英雄

    /superheroes/api/superhero?superhero.tag.strength.gt=90
    

    (这里的 tag 是链接表的名称,前面的 superhero 是引用所选表的字段的名称,strength 是用于过滤的字段名称)

  • 由多对多链接表引用的表字段,如下所示:

    所有拥有飞行能力的超级英雄

    /superheroes/api/superhero?superhero.tag.superpower.description.eq=Flight
    

提示

理解上述语法的关键是将其解读为:

当所述表 tagsuperpower 字段指向的表中 description 字段等于 “Flight” 时, “查询记录表 tag 中的 superhero 字段引用的 superhero 表中的记录。”

该查询允许使用其他修饰符,例如:

@offset=10
@limit=10
@order=name
@model=true
@lookup=real_identity

前三个是显而易见的。 @model 返回数据库模型的 JSON 描述。 @lookup 对链接字段进行非规范化处理。

RestAPI 实例

以下是一些实际例子:

URL:

/superheroes/api/superhero

输出:

{
    "count": 3,
    "status": "success",
    "code": 200,
    "items": [
        {
            "real_identity": 1,
            "name": "Superman",
            "id": 1
        },
        {
            "real_identity": 2,
            "name": "Spiderman",
            "id": 2
        },
        {
            "real_identity": 3,
            "name": "Batman",
            "id": 3
        }
    ],
    "timestamp": "2019-05-19T05:38:00.132635",
    "api_version": "0.1"
}

URL:

/superheroes/api/superhero?@model=true

输出:

{
    "count": 3,
    "status": "success",
    "code": 200,
    "items": [
        {
            "real_identity": 1,
            "name": "Superman",
            "id": 1
        },
        {
            "real_identity": 2,
            "name": "Spiderman",
            "id": 2
        },
        {
            "real_identity": 3,
            "name": "Batman",
            "id": 3
        }
    ],
    "timestamp": "2021-01-04T07:03:38.466030",
    "model": [
        {
            "regex": "[1-9]\\d*",
            "name": "id",
            "default": null,
            "required": false,
            "label": "Id",
            "post_writable": true,
            "referenced_by": [
                "tag.superhero"
            ],
            "unique": false,
            "type": "id",
            "options": null,
            "put_writable": true
        },
        {
            "regex": null,
            "name": "name",
            "default": null,
            "required": false,
            "label": "Name",
            "post_writable": true,
            "unique": false,
            "type": "string",
            "options": null,
            "put_writable": true
        },
        {
            "regex": null,
            "name": "real_identity",
            "default": null,
            "required": false,
            "label": "Real Identity",
            "post_writable": true,
            "references": "person",
            "unique": false,
            "type": "reference",
            "options": null,
            "put_writable": true
        }
    ],
    "api_version": "0.1"
}

URL:

/superheroes/api/superhero?@lookup=real_identity

输出:

{
    "count": 3,
    "status": "success",
    "code": 200,
    "items": [
        {
            "real_identity": {
                "name": "Clark Kent",
                "job": "Journalist",
                "id": 1
            },
            "name": "Superman",
            "id": 1
        },
        {
            "real_identity": {
                "name": "Peter Park",
                "job": "Photographer",
                "id": 2
            },
            "name": "Spiderman",
            "id": 2
        },
        {
            "real_identity": {
                "name": "Bruce Wayne",
                "job": "CEO",
                "id": 3
            },
            "name": "Batman",
            "id": 3
        }
    ],
    "timestamp": "2019-05-19T05:38:00.178974",
    "api_version": "0.1"
}

URL:

/superheroes/api/superhero?@lookup=identity:real_identity

(将 real_identity 反规范化并重命名为 identity )

输出:

{
    "count": 3,
    "status": "success",
    "code": 200,
    "items": [
        {
            "real_identity": 1,
            "name": "Superman",
            "id": 1,
            "identity": {
                "name": "Clark Kent",
                "job": "Journalist",
                "id": 1
            }
        },
        {
            "real_identity": 2,
            "name": "Spiderman",
            "id": 2,
            "identity": {
                "name": "Peter Park",
                "job": "Photographer",
                "id": 2
            }
        },
        {
            "real_identity": 3,
            "name": "Batman",
            "id": 3,
            "identity": {
                "name": "Bruce Wayne",
                "job": "CEO",
                "id": 3
            }
        }
    ],
    "timestamp": "2019-05-19T05:38:00.123218",
    "api_version": "0.1"
}

URL:

/superheroes/api/superhero?@lookup=identity!:real_identity[name,job]

(对 real_identity 进行非规范化 [但仅限于字段 name 和 job],用 identity 前缀折叠)

输出:

{
    "count": 3,
    "status": "success",
    "code": 200,
    "items": [
        {
            "name": "Superman",
            "identity.job": "Journalist",
            "identity.name": "Clark Kent",
            "id": 1
        },
        {
            "name": "Spiderman",
            "identity.job": "Photographer",
            "identity.name": "Peter Park",
            "id": 2
        },
        {
            "name": "Batman",
            "identity.job": "CEO",
            "identity.name": "Bruce Wayne",
            "id": 3
        }
    ],
    "timestamp": "2021-01-04T07:03:38.559918",
    "api_version": "0.1"
}

URL:

/superheroes/api/superhero?@lookup=superhero.tag

输出:

{
    "count": 3,
    "status": "success",
    "code": 200,
    "items": [
        {
            "real_identity": 1,
            "name": "Superman",
            "superhero.tag": [
                {
                    "strength": 100,
                    "superhero": 1,
                    "id": 1,
                    "superpower": 1
                },
                {
                    "strength": 100,
                    "superhero": 1,
                    "id": 2,
                    "superpower": 2
                },
                {
                    "strength": 100,
                    "superhero": 1,
                    "id": 3,
                    "superpower": 3
                },
                {
                    "strength": 100,
                    "superhero": 1,
                    "id": 4,
                    "superpower": 4
                }
            ],
            "id": 1
        },
        {
            "real_identity": 2,
            "name": "Spiderman",
            "superhero.tag": [
                {
                    "strength": 50,
                    "superhero": 2,
                    "id": 5,
                    "superpower": 2
                },
                {
                    "strength": 75,
                    "superhero": 2,
                    "id": 6,
                    "superpower": 3
                },
                {
                    "strength": 10,
                    "superhero": 2,
                    "id": 7,
                    "superpower": 4
                }
            ],
            "id": 2
        },
        {
            "real_identity": 3,
            "name": "Batman",
            "superhero.tag": [
                {
                    "strength": 80,
                    "superhero": 3,
                    "id": 8,
                    "superpower": 2
                },
                {
                    "strength": 20,
                    "superhero": 3,
                    "id": 9,
                    "superpower": 3
                },
                {
                    "strength": 70,
                    "superhero": 3,
                    "id": 10,
                    "superpower": 4
                }
            ],
            "id": 3
        }
    ],
    "timestamp": "2019-05-19T05:38:00.201988",
    "api_version": "0.1"
}

URL:

/superheroes/api/superhero?@lookup=superhero.tag.superpower

输出:

{
    "count": 3,
    "status": "success",
    "code": 200,
    "items": [
        {
            "real_identity": 1,
            "name": "Superman",
            "superhero.tag.superpower": [
                {
                    "strength": 100,
                    "superhero": 1,
                    "id": 1,
                    "superpower": {
                        "id": 1,
                        "description": "Flight"
                    }
                },
                {
                    "strength": 100,
                    "superhero": 1,
                    "id": 2,
                    "superpower": {
                        "id": 2,
                        "description": "Strength"
                    }
                },
                {
                    "strength": 100,
                    "superhero": 1,
                    "id": 3,
                    "superpower": {
                        "id": 3,
                        "description": "Speed"
                    }
                },
                {
                    "strength": 100,
                    "superhero": 1,
                    "id": 4,
                    "superpower": {
                        "id": 4,
                        "description": "Durability"
                    }
                }
            ],
            "id": 1
        },
        {
            "real_identity": 2,
            "name": "Spiderman",
            "superhero.tag.superpower": [
                {
                    "strength": 50,
                    "superhero": 2,
                    "id": 5,
                    "superpower": {
                        "id": 2,
                        "description": "Strength"
                    }
                },
                {
                    "strength": 75,
                    "superhero": 2,
                    "id": 6,
                    "superpower": {
                        "id": 3,
                        "description": "Speed"
                    }
                },
                {
                    "strength": 10,
                    "superhero": 2,
                    "id": 7,
                    "superpower": {
                        "id": 4,
                        "description": "Durability"
                    }
                }
            ],
            "id": 2
        },
        {
            "real_identity": 3,
            "name": "Batman",
            "superhero.tag.superpower": [
                {
                    "strength": 80,
                    "superhero": 3,
                    "id": 8,
                    "superpower": {
                        "id": 2,
                        "description": "Strength"
                    }
                },
                {
                    "strength": 20,
                    "superhero": 3,
                    "id": 9,
                    "superpower": {
                        "id": 3,
                        "description": "Speed"
                    }
                },
                {
                    "strength": 70,
                    "superhero": 3,
                    "id": 10,
                    "superpower": {
                        "id": 4,
                        "description": "Durability"
                    }
                }
            ],
            "id": 3
        }
    ],
    "timestamp": "2019-05-19T05:38:00.322494",
    "api_version": "0.1"
}

URL (这是一行,为了可读性而拆分):

/superheroes/api/superhero?
@lookup=powers:superhero.tag[strength].superpower[description]

输出:

{
    "count": 3,
    "status": "success",
    "code": 200,
    "items": [
        {
            "real_identity": 1,
            "name": "Superman",
            "powers": [
                {
                    "strength": 100,
                    "superpower": {
                        "description": "Flight"
                    }
                },
                {
                    "strength": 100,
                    "superpower": {
                        "description": "Strength"
                    }
                },
                {
                    "strength": 100,
                    "superpower": {
                        "description": "Speed"
                    }
                },
                {
                    "strength": 100,
                    "superpower": {
                        "description": "Durability"
                    }
                }
            ],
            "id": 1
        },
        {
            "real_identity": 2,
            "name": "Spiderman",
            "powers": [
                {
                    "strength": 50,
                    "superpower": {
                        "description": "Strength"
                    }
                },
                {
                    "strength": 75,
                    "superpower": {
                        "description": "Speed"
                    }
                },
                {
                    "strength": 10,
                    "superpower": {
                        "description": "Durability"
                    }
                }
            ],
            "id": 2
        },
        {
            "real_identity": 3,
            "name": "Batman",
            "powers": [
                {
                    "strength": 80,
                    "superpower": {
                        "description": "Strength"
                    }
                },
                {
                    "strength": 20,
                    "superpower": {
                        "description": "Speed"
                    }
                },
                {
                    "strength": 70,
                    "superpower": {
                        "description": "Durability"
                    }
                }
            ],
            "id": 3
        }
    ],
    "timestamp": "2019-05-19T05:38:00.309903",
    "api_version": "0.1"
}

URL (这是一行,为了可读性而拆分):

/superheroes/api/superhero?
@lookup=powers!:superhero.tag[strength].superpower[description]

输出:

{
    "count": 3,
    "status": "success",
    "code": 200,
    "items": [
        {
            "real_identity": 1,
            "name": "Superman",
            "powers": [
                {
                    "strength": 100,
                    "description": "Flight"
                },
                {
                    "strength": 100,
                    "description": "Strength"
                },
                {
                    "strength": 100,
                    "description": "Speed"
                },
                {
                    "strength": 100,
                    "description": "Durability"
                }
            ],
            "id": 1
        },
        {
            "real_identity": 2,
            "name": "Spiderman",
            "powers": [
                {
                    "strength": 50,
                    "description": "Strength"
                },
                {
                    "strength": 75,
                    "description": "Speed"
                },
                {
                    "strength": 10,
                    "description": "Durability"
                }
            ],
            "id": 2
        },
        {
            "real_identity": 3,
            "name": "Batman",
            "powers": [
                {
                    "strength": 80,
                    "description": "Strength"
                },
                {
                    "strength": 20,
                    "description": "Speed"
                },
                {
                    "strength": 70,
                    "description": "Durability"
                }
            ],
            "id": 3
        }
    ],
    "timestamp": "2019-05-19T05:38:00.355181",
    "api_version": "0.1"
}

URL (这是一行,为了可读性而拆分):

/superheroes/api/superhero?
@lookup=powers!:superhero.tag[strength].superpower[description],
identity!:real_identity[name]

输出:

{
    "count": 3,
    "status": "success",
    "code": 200,
    "items": [
        {
            "name": "Superman",
            "identity.name": "Clark Kent",
            "powers": [
                {
                    "strength": 100,
                    "description": "Flight"
                },
                {
                    "strength": 100,
                    "description": "Strength"
                },
                {
                    "strength": 100,
                    "description": "Speed"
                },
                {
                    "strength": 100,
                    "description": "Durability"
                }
            ],
            "id": 1
        },
        {
            "name": "Spiderman",
            "identity.name": "Peter Park",
            "powers": [
                {
                    "strength": 50,
                    "description": "Strength"
                },
                {
                    "strength": 75,
                    "description": "Speed"
                },
                {
                    "strength": 10,
                    "description": "Durability"
                }
            ],
            "id": 2
        },
        {
            "name": "Batman",
            "identity.name": "Bruce Wayne",
            "powers": [
                {
                    "strength": 80,
                    "description": "Strength"
                },
                {
                    "strength": 20,
                    "description": "Speed"
                },
                {
                    "strength": 70,
                    "description": "Durability"
                }
            ],
            "id": 3
        }
    ],
    "timestamp": "2021-01-04T07:31:34.974953",
    "api_version": "0.1"
}

URL:

/superheroes/api/superhero?name.eq=Superman

输出:

{
    "count": 1,
    "status": "success",
    "code": 200,
    "items": [
        {
            "real_identity": 1,
            "name": "Superman",
            "id": 1
        }
    ],
    "timestamp": "2019-05-19T05:38:00.405515",
    "api_version": "0.1"
}

URL:

/superheroes/api/superhero?real_identity.name.eq=Clark Kent

输出:

{
    "count": 1,
    "status": "success",
    "code": 200,
    "items": [
        {
            "real_identity": 1,
            "name": "Superman",
            "id": 1
        }
    ],
    "timestamp": "2019-05-19T05:38:00.366288",
    "api_version": "0.1"
}

URL:

/superheroes/api/superhero?not.real_identity.name.eq=Clark Kent

输出:

{
    "count": 2,
    "status": "success",
    "code": 200,
    "items": [
        {
            "real_identity": 2,
            "name": "Spiderman",
            "id": 2
        },
        {
            "real_identity": 3,
            "name": "Batman",
            "id": 3
        }
    ],
    "timestamp": "2019-05-19T05:38:00.451907",
    "api_version": "0.1"
}

URL:

/superheroes/api/superhero?superhero.tag.superpower.description=Flight

输出:

{
    "count": 1,
    "status": "success",
    "code": 200,
    "items": [
        {
            "real_identity": 1,
            "name": "Superman",
            "id": 1
        }
    ],
    "timestamp": "2019-05-19T05:38:00.453020",
    "api_version": "0.1"
}

RestAPI 的响应

所有 RestAPI 响应都有以下字段:

api_version:

RestAPI 的版本号

timestamp:

ISO 8601 格式的日期时间(YYYY-MM-DDThh:mm:ss[.mmm]TZD)

status:

RestAPI 状态(即 "success" 或 "error" )

code:

HTTP 状态

其他可选字段包括:

count:

匹配的总数量(不是返回的总数量),用于 GET

items:

针对 GET 的响应数据项目

errors:

通常是验证错误

models:

通常 status != "success" 时,才有此项

message:

有关错误的详细信息