东半球最好用的Flask RESTful框架:SinglePage
Posted TaylorHere
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了东半球最好用的Flask RESTful框架:SinglePage相关的知识,希望对你有一定的参考价值。
Document for SingePage
GitHub
SingePage 是一个基于flask的python RESTful 框架
源代码很简单,容易修改与扩展
使用简单,提供GeneralView,完全不用管json部分
框架目前主要类,描述列表如下:
类名 | 用途 |
---|---|
url | 给View注册url |
SinglePage | 基于flask.view,提供请求分发,结果序列化功能 |
GeneralViewWithSQLAlchemy | 基于SinglePage类的通用视图函数,能和SQLAlchemy配合快速实现接口 |
permission | 用于视图访问权限管理,可用来实现业务逻辑 |
快速开始
与flask常用的基于方法的视图不同,SinglePage的常规用法是基于类
# coding: utf-8
from SinglePage import app, SinglePage
from base import Base, db_session
from sqlalchemy import Column, Integer, String, Text, Boolean, Float, DateTime, Enum
class Address(SinglePage, Base):
pass
以上代码片段,从SinglePage导入flask实例,SinglePage类,导入数据库相关
我们的Address类继承至SinglePage,同时继承了来自SQLAlchemy的Base,这样这个类同时是视图和ORM
当然目前这份代码没有任何作用,我们继续完善它,首先定义字段
# coding: utf-8
from SinglePage import *
from SinglePage import app
from base import Base, db_session
from sqlalchemy import Column, Integer, String, Text, Boolean, Float, DateTime, Enum
class Address(SinglePage, Base):
# 定义表字段
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
address = Column(String(15))
新增的代码定义了表名字,主键id,address
既然这是一个RESTful框架,那么应该能够方便的响应HTTP动词,事实上在SinglePage内部实现了请求分发,不同动词的请求会由不同的方法来响应,目前支持,PUT,GET,POST,DELETE ,四种常用的方法,他们会分别由同名但是小写的方法来响应,代码如下。
# coding: utf-8
from SinglePage import *
from SinglePage import app
from base import Base, db_session
from sqlalchemy import Column, Integer, String, Text, Boolean, Float, DateTime, Enum
class Address(SinglePage, Base):
# 定义数据表
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
address = Column(String(15))
# 处理http get方法
def get(self):
return db_session.query(self.object).all(), 'sqlalchemy'
# 处理http post方法
def post(self):
# 获取request的json并新建一个用户
data = request.get_json()
address = self.object(data)
db_session.add(address)
db_session.commit()
return 'ok', 'basic'
以上代码处理了GET和POST方法,当对应的请求到来时会分别由get(),post()来响应
其中get方法会把数据库中所有address表记录返回,post方法会新增一条记录
你可能注意到了,return语句很奇怪。
return db_session.query(self.object).all(), 'sqlalchemy'
return 'ok', 'basic'
第一条语句返回了查询队列和字符串‘sqlalchemy’,第二条队列返回了字符串’ok‘和字符串’basic‘
事实上SinglePage处理了动词响应方法的返回,它会将这些返回进行序列化和JSON化,由于序列化器实现的原因,必须指明带序列对象的类型,目前支持的序列化对象包括sqlalchemy的普通类,他们对应使用’sqlalchemy’和’basic’来注明,字符串,数据包装类都属于‘basic’,当然,我知道,这很不优雅。
第二个让人遗憾的地方应该是self.object,它指向类自身,注意self.object得到的不是类实例而是类本身。
所以以下代码:
data = request.get_json()
address = self.object(data)
获取来自请求中的json数据,self.object(data),实际上执行了Address的实例化操作,SinglePage为我们提供了映射,它会自动把我们定义的id,和address字段作为json中的key,去获取json中的值,并完成创建SQLAlchemy对象的操作。
ok,到这里,我们已经有了数据库,表定义,和接口规则,那么URL呢?
# coding: utf-8
from SinglePage import *
from SinglePage import app
from base import Base, db_session
from sqlalchemy import Column, Integer, String, Text, Boolean, Float, DateTime, Enum
# 注册url
@url('/addresses/')
class Address(SinglePage, Base):
# 定义数据表
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
address = Column(String(15))
# 处理http get方法
def get(self, address_id):
# 查询数据
if address_id is not None:
return db_session.query(self.object).filter(self.object.id == address_id), 'sqlalchemy'
else:
return db_session.query(self.object).all(), 'sqlalchemy'
# 处理http post方法
def post(self):
# 获取request的json并新建一条地址
data = request.get_json()
address = self.object(data)
db_session.add(address)
db_session.commit()
return 'ok', 'basic'
if __name__ = '__main__':
Address()
app.run()
使用@语法可以轻松的注册把Address注册到路由系统,URL中请一定带上结尾的’/’。
接下来在启动这个python脚本,Adders()这个实例化操作,会把Address真正的注册到路由系统中,然后访问http:127.0.0.1:5000/addresses/便可以使用这个视图了。
更RESTful的使用URL
RESTful应该按如下方式来使用URL
URL | HTTP动词 | 功能 |
---|---|---|
/addresses/ | GET | 获取所有数据 |
/addresses/id | GET | 获取对应id的数据 |
/addresses/ | POST | 新增一条 |
/addresses/id | PUT | 更新对应id的数据 |
想要使用id,只需要在对应方法里增加一个参数,id变会注册到路由系统中,如下:
# coding: utf-8
from SinglePage import *
from SinglePage import app
from base import Base, db_session
from sqlalchemy import Column, Integer, String, Text, Boolean, Float, DateTime, Enum
# 注册url
@url('/addresses/')
class Address(SinglePage, Base):
# 定义数据表
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
address = Column(String(15))
# __exclude__ = ['id']
# 处理http get方法
def get(self, address_id):
# 查询数据
if address_id is not None:
return db_session.query(self.object).filter(self.object.id == address_id), 'sqlalchemy'
else:
return db_session.query(self.object).all(), 'sqlalchemy'
# 处理http post方法
def post(self):
# 获取request的json并新建一个用户
data = request.get_json()
address = self.object(data)
db_session.add(address)
db_session.commit()
return 'ok', 'basic'
我们便有了/addresses/address_id这样一条url你可以用以下方式使用它
/addresses/address_id=1
/addresses/1
两种方法中的1最终都会映射到get方法中的参数address_id上面
如果仅仅访问/addresses/那么这个参数为None,所以你不用设置默认值为None
不需要客户端知道东西
上文提到过self.object()会知道把Address中定义的字段作为json数据的key去获取json里面的值,然后实例化一个ORM对象,那么你可以回疑问,难道id也要由客户端传入吗?
当然我们做了一下简单的措施来防止客户端填充不需要的字段
# coding: utf-8
from SinglePage import *
from SinglePage import app
from base import Base, db_session
from sqlalchemy import Column, Integer, String, Text, Boolean, Float, DateTime, Enum
# 注册url
@url('/addresses/')
class Address(SinglePage, Base):
# 定义数据表
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
address = Column(String(15))
__in_exclude__ = ['id']
__exclude__ = ['id']
# 处理http get方法
def get(self, address_id):
# 查询数据
if address_id is not None:
return db_session.query(self.object).filter(self.object.id == address_id), 'sqlalchemy'
else:
return db_session.query(self.object).all(), 'sqlalchemy'
# 处理http post方法
def post(self):
# 获取request的json并新建一个用户
data = request.get_json()
address = self.object(data)
db_session.add(address)
db_session.commit()
return 'ok', 'basic'
__in_exclude__
这个列表内的字符串,标注了不需要由客户端填充的字段
__exclude__
这个列表内的字符串,标注了不会返回给客户端的字段,
实例化这步我有话说
或许自动生成ORM实例这步,会影响到你处理一些字段数据,比如客户端发来密码而你打算将密码hash之后再存储,那么你可以使用python的属性方法以及SinglePage的属性方法字典
# coding: utf-8
from SinglePage import *
from SinglePage import app
from base import Base, db_session
from sqlalchemy import Column, Integer, String, Text, Boolean, Float, DateTime, Enum
# 注册url
@url('/users/')
class User(GeneralViewWithSQLAlchemy, Base):
# 配置数据库会话链接
db_session = db_session
# 定义delete方法是真实删除还是软删除
real_delete = False
# 定义数据表
__tablename__ = 'User'
id = Column(Integer, primary_key=True)
telephone = Column(String(15))
nickname = Column(String(20))
_pwd = Column(String(50))
deleted = Column(Boolean())
# 定义哪些字段不由前端填充
__in_exclude__ = ['id', '_pwd', 'deleted']
# 定义哪些字段不展示给前端
__exclude__ = ['_pwd']
# 定义属性装饰方法
__property__ = 'pwd': '_pwd'
@property
def pwd(self):
return self._pwd
@pwd.setter
def pwd(self, value):
self._pwd = u'假装加密' + value
if __name__ == '__main__':
User()
app.run()
以上代码的效果是,客户端拥有一个/users/接口,可以轻松使用HTTP动词执行数据的增删改查
并且,每次pwd字段变动时都会进行在客户端值的基础上加上‘假装加密‘这样的前缀,
# 定义哪些字段不由前端填充
__in_exclude__ = ['id', '_pwd', 'deleted']
# 定义哪些字段不展示给前端
__exclude__ = ['_pwd']
# 定义属性装饰方法
__property__ = 'pwd': '_pwd'
这三条语句指明了不需要客户端填充的字段,和不需要展示给客户端的字段,以及_pwd字段的属性方法为pwd
客户端使用POST时只需提交:
"nickname":"TayloeHere",
"telephone":"151****8887",
"pwd":"00000"
使用GET时会收到到如下类似的数据:
"data": [
"deleted": null,
"id": 1,
"nickname": "TaylorHere",
"pwd": "假装加密00000",
"telephone": "151****8887"
]
细心的你可能会发现User类是基于GeneralViewWithSQLAlchemy实现的
这个类提供了通用的视图,并按RESTful的风格提供URL和返回,同时提供了软删除选项,当然这需要你定义一个叫做deleted的boolean型字段。
通用视图提供的URL如下(以User类为例):
URL | HTTP动词 | 功能 |
---|---|---|
/users/ | GET | 获取所有数据 |
/users/id | GET | 获取对应id的用户 |
/users/ | POST | 新增一条 |
/users/id | PUT | 更新对应id的用户 |
权限管理
class permission():
def get(self, request):
'get permission'
return True
def post(self, request):
'post permission'
return True
def put(self, request):
'put permission'
return True
def delete(self, request):
'delete permission'
return True
以上为permission源代码,一个permission可以控制一个视图不同动作函数的访问权限,准确说是访问这个动作后,是否执行动作内代码的权限。
使用时只需继承permission类,并重载对应动作方法,返回True则为权限通过,返回false为权限不通过,
最后将你的权限类注册到__permission__列表中,一个视图可以拥有多个权限类,同时,权限动作方法中的注释会被作为权限不同时的默认返回。
# coding: utf-8
from SinglePage import *
from SinglePage import app
from base import Base, db_session
from sqlalchemy import Column, Integer, String, Text, Boolean, Float, DateTime, Enum
# 注册url
@url('/users/')
class User(GeneralViewWithSQLAlchemy, Base):
class UserPermission(permission):
"""author:Taylor<tank357@icloud.com>"""
def get(self, request):
'do not open this api'
return False
# 配置数据库会话链接
db_session = db_session
# 定义delete方法是真实删除还是软删除
real_delete = False
# 定义数据表
__tablename__ = 'User'
id = Column(Integer, primary_key=True)
telephone = Column(String(15))
nickname = Column(String(20))
_pwd = Column(String(50))
deleted = Column(Boolean())
# 定义哪些字段不由前端填充
__in_exclude__ = ['id', '_pwd', 'deleted']
# 定义哪些字段不展示给前端
__exclude__ = ['_pwd']
# 定义属性装饰方法
__property__ = 'pwd': '_pwd'
__permission__ = [UserPermission]
@property
def pwd(self):
return self._pwd
@pwd.setter
def pwd(self, value):
self._pwd = u'假装加密' + value
过滤器
对于过滤器的设计是最纠结的,既要考虑到拓展又要考虑到通用,同时还希望实现结果缓存。
所以最终解决方案是,为每个get操作提供一组默认过滤器,注册在__query_args__字典里,扩展时只需提供过滤器名称和实现方法即可。
例如asc_order_by过滤器实现是这样的:
def asc_order_by(self, query, value):
from sqlalchemy import text
return query.order_by(text(value))
我们一共实现了5个过滤器,并且很纠结是否应该实现最后两个,这5个过滤器如下:
def filter(self, query, value):
"""
等于 key = value
不等于 key != value
boolean 值 Flase:0,True:1
大于 key > value
小于 key < value
或 expression A or expression B
且 expression A and expression B
ex:
'name = taylor and and age <20 and deleted = 0'
"""
from sqlalchemy import text
return query.filter(text(value))
def asc_order_by(self, query, value):
from sqlalchemy import text
return query.order_by(text(value))
def desc_order_by(self, query, value):
from sqlalchemy import desc
from sqlalchemy import text
return query.order_by(desc(text(value)))
def limit(self, query, value):
return query[0:int(value)]
def fileds(self, query, value):
pass
# 过滤器实现于args名称字典
__query_args__ = 'filter': filter, 'asc_order_by': asc_order_by,
'desc_order_by': desc_order_by, 'limit': limit, 'fileds': fileds
我们在思考是否应该默认提供limit(分页功能),和fileds(返回字段过滤),因为我们认为应该把这两个交给Query资源去处理
Query资源
Query资源即把查询本身看作一种资源,使用post发起一个资源,我们知道post动词应该对应创建操作,事实上我们就是这样思考的,post发起一次查询后,查询结果会被缓存,之后可以通过get或者post获取这次缓存,当然使用post执行获取,会显得很奇怪,但事实上,如果对于普通资源,我们使用post创建一个已存在资源时,他依然会把这个已存在资源返回,所以这样想就不奇怪了。
Query资源可以轻松使用SinglePage子类来实现。
Query资源还在开发中。
未来
SinglePage是花草秀团队目前正在开发中的RESTful框架,接下来,我们将会为改框架添加更多的特性,使其更符合商业用例。
以上是关于东半球最好用的Flask RESTful框架:SinglePage的主要内容,如果未能解决你的问题,请参考以下文章