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