tadpole 是一个flask starter 项目。从平时flask项目的开发过程当中提出来的一些通用的功能,如经过gunicorn管理flask应用的配置文件和启动脚本,初始化virtualenv环境同时安装必要的依赖库,生成flask secret以及提供restful route, 自动为sqlalchey model注册restful接口, 登陆认证,权限管理, restful支持等等技能。python
posix, Python 2.x >= 2.6
linux
pip install tadpole
git
https://github.com/echoyuanliang/tadpolegithub
欢迎pull request, 一块儿作好这个项目, 若是以为不错, 欢迎star。web
tadpole init -n PROJECT_NAME -v PROJECT_VERSION -o PROJECT_OWNER -e PROJECT_EMAIL
sql
其中PROJECT_NAME
是初始化的项目名,PROJECT_VERSION
是初始化的版本号(默认为0.0.1), PROJECT_OWNER
为项目负责人,PROJECT_EMAIL
为项目邮件组(用于接收邮件)。shell
也能够直接执行tadpole init
会提示填入项目名,其余采用默认, 例如:数据库
至此,已经使用tadpole初始化了一个新的flask项目,进入tadpole-demo目录能够看到json
dev是提供给开发者在开发环境下使用的工具,其中提供了以下技能flask
此处仅以 url 为例:
能够看到新初始化的项目已经有这么多注册的url了,其中prefix为/api/v0.0.1/rest_db开头的url都是为已经建立的
user,role,resource三张表自动生成的restful api。另一个/health则用于健康检查。最后的/static/则是flask默认提供的。
工做中常常会有人要接口查询数据,可是不少数据只须要执行sql语句就能拿到数据,可是又不能直接把DB权限给别人,
所以提供了一个把简单sql语句自动对应到restful查询的技能。这个技能实际上市面上已经有不少库提供了,可是
并无遇到让我本身用的很舒服的库,所以本身写了一个,这个技能之只须要用户写Model类,并直接或间接的
import到app/models/__init__.py中便可为其自动注册restful接口。
为了初始化出来的项目能够开箱即用, 会给默认的db(sqlite数据库,文件位于app.db)中建立user,role,resource等
表结构,同时会插入部分数据,所以访问已经注册的rest_db url是能够直接拿到数据的, 例如:
curl http://127.0.0.1:8080/api/v0.0.1/rest_db/user { "code": 200, "msg": "ok", "result": { "next_page": "http://127.0.0.1:8080/api/v0.0.1/rest_db/user?__page=2&__page_size=200", "page": 1, "page_size": 200, "prev_page": null, "result": [ { "__roles_link": "http://127.0.0.1:8080/api/v0.0.1/rest_db/user/1/roles", "account": "tadpole", "create_time": "2017-11-26 17:53:13", "email": "tadpole@tadpole.com", "id": 1, "name": "tadpole" } ] } }
能够看到user表已经有一条记录了,同时__roles_link连接到了每一个用户所拥有的角色,直接访问能够看到
curl http://127.0.0.1:8080/api/v0.0.1/rest_db/user/1/roles { "msg": "ok", "code": 200, "result": { "next_page": "http://127.0.0.1:8080/api/v0.0.1/rest_db/user/1/roles?__page=2&__page_size=200", "prev_page": null, "result": [ { "description": "super admin", "__resources_link": "http://127.0.0.1:8080/api/v0.0.1/rest_db/role/1/resources", "__users_link": "http://127.0.0.1:8080/api/v0.0.1/rest_db/role/1/users", "create_time": "2017-11-26 17:42:52", "id": 1, "name": "root" } ], "page_size": 200, "page": 1 } }
能够看到tadpole这个用户已经拥有了一个root角色, 每一条记录除了返回本身的的列以外还以__{relation}_link
的形式返回了其关联关系的连接。
OPERATORS = ('lt', 'le', 'gt', 'ge', 'eq', 'like', 'in', 'between') PROCESSES = ('__show', '__order') PAGINATE = ('__page', '__page_size')
查询条件分为3类,一类是基本的运算符在OPERATORS中,另外一类是对查询的数据进行一些处理,如排序、只展现部分列等,另外一类则是分页。
联合使用这些查询条件:
curl http://127.0.0.1:5000/api/v0.0.1/rest_db/user?name=tadpole&account.like=tad%&__show=account,email&__order=id.asc,name.desc { "msg": "ok", "code": 200, "result": { "next_page": "http://127.0.0.1:5000/api/v0.0.1/rest_db/user?name=tadpole&__page_size=200&__page=2&account.like=tad%25&__order=id.asc%2Cname.desc&__show=account%2Cemail", "prev_page": null, "result": Array[1][ { "account": "tadpole", "email": "tadpole@tadpole.com" } ], "page_size": 200, "page": 1 } }
能够看到仅仅返回了__show
中列出的列,并且按照name=tadpole,account.like=tad%
过滤的结果,而且根据__order
中的排序条件进行了排序。
能够看出生成的url都是有必定规则的, prefix为/api/v0.0.1,其中v0.0.1是项目的版本号,可是这个是能够定制的。经过配置文件中的BP_PREFIX
就能够配置每个bluprint对应的prefix,例如rest_db这个blueprint(即rest model使用的)的配置能够以下:
BP_PREFIX = { 'rest_db': '/api/{0}/rest_db/'.format(VERSION) }
除了prefix以外,后面紧跟着的则是表名,若是是关联查询则是{prefix}/{table_name}/{pk_id}/{relation_name}
并非全部的列都适合展现,有些列(好比密码,并不适合对外开放),初始化出来的项目对user表的password列就作了隐藏,以下:
class User(Model): # columns in __hide__ does'nt show in rest_db __hide__ = ('password',) account = Column( db.String(128), nullable=False, default=u'-', index=True, unique=True) name = Column(db.String(32), nullable=False, default=u'-') email = Column(db.Email(128), nullable=False, default=u'-') password = Column(db.Password(schemes=['pbkdf2_sha512', 'md5_crypt'], deprecated=['md5_crypt']), nullable=False, default=u'-')
声明Model时, __hide__
元组中的列不会在自动生成的restful接口中展现
对于每个应用来讲,都有不适合对全部人开放的资源,所以须要登陆控制和权限管理。tadpole默认在app/models/auth包中实现了用户和权限依赖的Model,
在app/lib/auth.py中实现了有关登陆和权限验证的逻辑。登陆验证目前采用的是Http Basic
认证, 由于密码是单向加密存储的,因此有些验证方法(如Http Digest
)不能直接使用,有需求能够对代码进行扩展。扩展也十分容易,有兴趣的朋友能够阅读实现源码进行扩展。权限校验则是简单的查询数据库看用户有没有对某一资源执行某一操做的权限(此处也能够很容易扩展本身的校验方式),权限校验默认对restful接口的http method进行了支持,所以只须要在数据库中添加合适的记录既能够作到接口的权限控制。为了应用能够开箱即用,已经对/api/v0.0.1/rest_db/
开头的url作了权限限制,其POST,DELETE,PUT
方法仅有root权限用户能够操做,如:
对于新初始化的项目,已经添加了account=tadpole-demo,password=12qwaszx
的用户,而且赋予了root角色,能够执行POST /api/v0.0.1/rest_db/*
测试权限校验是否正确。
只须要把resource 和 role关联起来便可以仅开放给对应角色的用户。数据库中没有记录的resource以及没有关联role的resource是对全部人开放的。一个资源开放给的用户是资源名称能够正则匹配的到全部resource.name
,且对资源的操做在resource.operation
(用','分割)中的资源列表所开放给的角色所拥有的用户。 例如对于http restful接口的权限校验, 会拿出全部匹配path 和 method的resource,而后查询这些resource开放的role列表,要求用户只有知足全部这些role,才能够访问对应接口。
对于restful接口来讲, 一是参数的校验几乎都须要,二是但愿能够返回python对象,由框架自动处理成json格式。rest_route对这些作了支持。
如:
from main import app validator = { 'required': ['user_name'] } @app.rest_route('/welcome', methods=['GET'], validator=validator) def welcome(data): return 'hell0', data['user_name']
首先validator中能够对参数进行校验,默认实现了几种经常使用校验,也能够本身扩充,除此以外还实现了custom校验,即传入用户本身的校验函数,这段代码提供了对user_name参数必填的校验。除此以外,为了提供统一的提交数据入口,全部提交数据都被merge到data参数中了,rest_route接口的POST方法必须提交json格式数据。最后返回一个元组,在rest_route中会自动将其转化为json list,请求这个接口返回以下:
curl http://127.0.0.1:5000/welcome { "msg": "param user_name is required", "code": 400 } curl http://127.0.0.1:5000/welcome?user_name=tadpole { "msg": "ok", "code": 200, "result": Array[2][ "hell0", "tadpole" ] }
返回结果不只支持直接返回元组,还支持sqlalchemy查询结果直接返回,set返回等等。对于用户自定义的对象若是要支持直接返回,只须要实现to_dict/_as_dict方法将对象转化成dict便可。
参数校验是经过app/lib/validator实现的,有兴趣的朋友能够直接看源码,实现很简单,也能够本身扩展。目前实现的校验方式有如下:
validator = { 'types': { 'age': int, 'active': bool, } }
oneof[param_name]
中的一个,数据类型为dict例如:
validator = { 'required': ['task_id'], # 必填参数 'nonempty': ['project', 'env', 'ip_list', 'component'], # 不能为空 'types': { 'ip_list': list, 'mem': int }, 'unique': ['ip_list'], # 对ip_list参数去重 'default': { 'region': 'Shanghai' # 若是用户没有填写region参数,则用Shanghai填充 }, 'oneof': { 'region': ['Shanghai', 'Beijing'], # region参数必属于Shanghai和Beijing之一 } }
已经实现了异常的自动捕捉,并返回合适的信息,默认提供的异常在app/lib/exceptions.py中,
全部继承自CustomError的异常都会被捕捉,而且返回msg做为错误信息,code做为返回码,所以能够直接抛出这些异常给用户,
不须要再进行处理。也能够扩展自定义的异常。默认异常定义示例:
class CustomError(Exception): def __init__(self, msg): super(CustomError, self).__init__(msg) self.msg = msg self.code = 500 def to_dict(self): return dict(code=self.code, msg=self.msg) def __unicode__(self): return unicode(self.msg) def __str__(self): return str(self.msg) class InternalError(CustomError): def __init__(self, msg): super(InternalError, self).__init__(msg) self.code = 500