实在找不到图了……
最近因为工作的关系,被要求用到 Eve 这个框架,需求简要概括就是对一个 mongo 表的 crud,本来用 flask 直接一把梭完就一个下午的事,但因为 “工作” 的关系,不得不盘一下 Eve
Eve 简介
详细看了 Eve 的源码,他是一个对 Flask 高度定制化的,以 配置 为驱动的 restful 框架,根据配置文件生成 endpoint url 和 endpoint method,以 http method 对应 crud 操作。Eve 的数据持久化原生支持 mongodb,配置了 mongo 相关的参数后,对 endpoint url 的请求操作相当于直接对对应的 mongo 表进行 crud
下面上官方文档的例子进行说明
快速开始
-
先启动一个 mongodb,这里用 docker 启一个 mongodb 容器 (这里选 3.4 版本的和公司一致,你可以自行选择其他版本)
1
2
3
4
5
6> $ docker run --name mongodb -p 27017:27017 \
-v /srv/mongodb/etc:/etc/mongo \
-v /srv/mongo/data/db:/data/db \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=admin123 \
-d mongo:3.4-xenial -
进入容器,创建
test
数据库,并设置用户test
,密码test123
-
进入容器的 mongodb 交互终端
1
> $ docker exec -it mongodb mongo -u admin -p admin123 --authenticationDatabase admin
-
创建
test
数据库,并设置用户test
,密码test123
1
2> use test
> db.createUser({user: 'test', pwd: 'test123', roles:[{role: 'readWrite', db: 'test'}]})
-
-
假设你当前目录为
workspace
,在这个目录下新建settings.py
文件,顾名思义,这就是 配置文件文件名可以自定义,在初始化 Eve 对象的时候传进去就好了,默认是
settings.py
settings.py 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67# 下面为 mongo 相关配置
MONGO_HOST = 'localhost'
MONGO_PORT = 27017
MONGO_USERNAME = 'test'
MONGO_PASSWORD = 'test123'
MONGO_AUTH_SOURCE = 'test'
MONGO_DBNAME = 'test'
# 定义 resource 和 item 能够接受的 http method
# 这里统一规定,resource 相当于 mongo collection,item 相当于 mongo document
RESOURCE_METHODS = ['GET', 'POST', 'DELETE']
ITEM_METHODS = ['GET', 'PATCH', 'PUT', 'DELETE']
# 定义 item schema,schema 相当于 mongo document 的字段定义
schema = {
'firstname': {
'type': 'string',
'minlength': 1,
'maxlength': 10,
},
'lastname': {
'type': 'string',
'minlength': 1,
'maxlength': 15,
'required': True,
'unique': True,
},
'role': {
'type': 'list',
'allowed': ["author", "contributor", "copy"],
},
'location': {
'type': 'dict',
'schema': {
'address': {'type': 'string'},
'city': {'type': 'string'}
},
},
'born': {
'type': 'datetime',
},
}
# 定义 resource,名为 people
people = {
'item_title': 'person',
# 默认的请求 item 的 endpoint path 为 '/people/<mongo ObjectId>',这样不太方便,
# 我们可以自定义 item 的 endpoint path,这里用 lastname,因为是 unique 字段
'additional_lookup': {
'url': 'regex("[\w]+")',
'field': 'lastname'
},
# cache 相关
'cache_control': 'max-age=10,must-revalidate',
'cache_expires': 10,
# 这里会覆盖上面定义的全局 RESOURCE_METHOD
'resource_methods': ['GET', 'POST'],
# 指定 schema
'schema': schema
}
# 定义 domain,相当于将 people resource 绑定到 ‘people’ 这个 resource endpoint
DOMAIN = {'people': people} -
接着新建
main.py
文件同样文件名可自定义,这个文件主要实例化 Eve 对象,相当于入口文件
main.py 1
2
3
4
5from eve import Eve
app = Eve()
if __name__ == '__main__':
app.run() -
执行
main.py
这个脚本1
2
3
4
5
6
7
8> $ python main.py
* Serving Flask app "eve" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) -
curl
测试一下 api1
> $ curl -i http://127.0.0.1:5000/
如果没有意外的话,你会得到下面的返回
1
2
3
4
5
6
7HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 62
Server: Eve/0.9.2 Werkzeug/0.15.4 Python/3.6.8
Date: Fri, 23 Aug 2019 09:32:08 GMT
{"_links": {"child": [{"href": "people", "title": "people"}]}}请求一下
people
资源1
> $ curl http://127.0.0.1:5000/people
如果没有意外的话,你会得到下面的返回
1
2
3
4
5
6
7
8
9
10
11
12
13{
"_items": [],
"_links": {
"self": {
"href": "people",
"title": "people"
},
"parent": {
"href": "/",
"title": "home"
}
}
}试一下
DELETE
http method1
> $ curl -X DELETE http://127.0.0.1:5000/people
你应该会得到
1
2
3
4
5
6
7{
"_status": "ERR",
"_error": {
"code": 405,
"message": "The method is not allowed for the requested URL."
}
}因为我们在
people
里配置了'resource_methods': ['GET', 'POST']
,除了GET
、POST
外,其他请求都是不成功的我们
POST
一些数据过去1
2
3
4
5# POST 多条数据传 list,单条直接 json
> $ curl -X POST \
-H 'Content-Type: application/json' \
-d '[{"firstname": "barack", "lastname": "obama"}, {"firstname": "mitt", "lastname": "romney"}]' \
http://127.0.0.1:5000/people返回结果为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30{
"_status": "OK",
"_items": [
{
"_updated": "Fri, 23 Aug 2019 10:45:30 GMT",
"_created": "Fri, 23 Aug 2019 10:45:30 GMT",
"_etag": "fc6d5b3a95813465269c57d180ef1716f700cc6a",
"_id": "5d5fc3cae85c86602304ef3b",
"_links": {
"self": {
"title": "person",
"href": "people/5d5fc3cae85c86602304ef3b"
}
},
"_status": "OK"
},
{
"_updated": "Fri, 23 Aug 2019 10:45:30 GMT",
"_created": "Fri, 23 Aug 2019 10:45:30 GMT",
"_etag": "30761f80c263beb3b96fef0e07f5ec60ca4086e2",
"_id": "5d5fc3cae85c86602304ef3c",
"_links": {
"self": {
"title": "person",
"href": "people/5d5fc3cae85c86602304ef3c"
}
},
"_status": "OK"}
]
}查看一下 mongo
已经自动创建了
people
表,并插入了刚刚传的两条数据我们请求一下
item
,也就是people
里的document
1
> $ curl -i http://127.0.0.1:5000/people/obama
可以看到,我们直接用
people/obama
来请求资源,因为我们之前在people
里定义了additional_lookup
,因此我们直接用last_name
作为endpoint
来访问,没有意外应该能得到下面的返回1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22{
"_id": "5d5fc3cae85c86602304ef3b",
"firstname": "barack",
"lastname": "obama",
"_updated": "Fri, 23 Aug 2019 10:45:30 GMT",
"_created": "Fri, 23 Aug 2019 10:45:30 GMT",
"_etag": "fc6d5b3a95813465269c57d180ef1716f700cc6a",
"_links": {
"self": {
"title": "person",
"href": "people/5d5fc3cae85c86602304ef3b"
},
"parent": {
"title": "home",
"href": "/"
},
"collection": {
"title": "people",
"href": "people"
}
}
}
源码分析
Eve 的目录结构如下
1 |
eve |
这里不会一一详解,只会分析和配置比较相关的,影响开发的部分,部分分析会在注释里呈现
endpoints
Eve 初始化的时候 __init__()
方法会调用
register_resource()
方法来注册 resource
信息
1 |
# Use a snapshot of the DOMAIN setup for iteration so |
self.register_resource()
方法会调用
self._add_resource_url_rules(resource,
settings)
,绑定
endpoint url
和 endpoint method
1 |
# set up resource |
_add_resource_url_rules()
实现如下
1 |
def _add_resource_url_rules(self, resource, settings): |
collections_endpoint
和
item_endpoint
封装了
http method 对应的 mongo io
操作
1 |
def collections_endpoint(**lookup): |
对应 get
、put
等具体实现在
method
文件夹下
使用建议
在我的需求场景中,mongo 表有一个唯一标识字段
urs
,查询
操作直接通过这个字段查询,插入
数据时,已存在则覆盖(upsert),不存在则直接插入(insert),删除
时根据这个字段删除
但是因为 POST
只有
collections_endpoint
实现,而且还不能够
upsert
只能 insert
,所以插入需要用
PUT
来做,因此
settings.py
里只设置 item_methods
settings 大致如下:
1 |
import os |
在上面的快速开始中,你可能注意到,插入数据时,eve 会自动给你加上
_updated
、_created
的 date
格式字段,你想动它又不知如何下手;还有它返回的
json 格式,可能并不符合需求。这时你就要利用到 hook 的特性了。Eve
提供了一系列 hook
函数 让你进行操作,这里贴上我定义的 hook 函数供参考,具体用法跟如何定义参考 Eve
文档
1 |
import json |
在 app 对象里注册 hook 函数
1 |
from eve import Eve |
hook 的原理其实就是在执行 method
里的函数时(如
get
、put
),通过 getattr
获取到
app
对象注册的 hook 函数,然后调用执行,下面是代码片段
1 |
def pre_event(f): |
结语
在看了大部分的源码和文档之后,总结一下,Eve 这个框架实现的并不复杂,思想上非常符合 restful 风格,可以说是一个通用的,以配置为驱动的 restful 框架,但是也仅仅是通用而已了,因为它的 endpoint method 已经封装好了,你想要做一些改动,比如入参或返回,要通过 hook 来做,这样就很不友好而且很不规范,还仅支持 mongo,那就只能写些小接口玩玩了。或许这就是它原本的初衷呢?