1.理解序列化时的default函数
我们最想做的一件事情,就是在视图函数中,读取出模型之后,还要把他的属性读出来,转换成一个字典。我们想直接jsonfiy(user)
现在jsonfiy并不能直接序列化对象,所以我们的目标就是必须想办法让jsonfiy直接序列化对象。
jsonfiy在序列化对象的时候,如果不知道如何序列化当前传进来的参数,就会去调用JSONEncoder类的default函数。
Copy def default(self, o):
"""Implement this method in a subclass such that it returns a
serializable object for ``o``, or calls the base implementation (to
raise a :exc:`TypeError`).
For example, to support arbitrary iterators, you could implement
default like this::
def default(self, o):
try:
iterable = iter(o)
except TypeError:
pass
else:
return list(iterable)
return JSONEncoder.default(self, o)
"""
if isinstance(o, datetime):
return http_date(o.utctimetuple())
if isinstance(o, date):
return http_date(o.timetuple())
if isinstance(o, uuid.UUID):
return str(o)
if hasattr(o, '__html__'):
return text_type(o.__html__())
return _json.JSONEncoder.default(self, o)
目前的default是没有提供对对象的序列化的,所以我们这里最关键的就是要重写default方法。在重写的过程中实现对对象的序列化就可以了
2.不完美的对象转字典
我们首先要做到的就是让Flask可以调用到我们自己定义的default函数。要做到这一点,我们需要继承JSONEncoder,然后重写defualt方法,然后继承Flask,在子类里,替换掉Flask原有的jsonencoder对象。然后,是实例化Flask核心对象的时候,使用我们的子类进行实例化 app_init .py
Copy from flask import Flask as _Flask
from flask.json import JSONEncoder as _JSONEncoder
class JSONEncoder(_JSONEncoder):
def default(self,o):
# return o.__dict__ # 内置方法,将对象转化为字典 # 缺点是只能转换实例变量,不能将类变量也转换成字典
return dict(o)
class Flask(_Flask): # 定义自己的Flask核心对象,继承原来的Flask核心对象
json_encoder = JSONEncoder # 替换原本的JSONEncoder
# 将Blueprint注册到flask核心对象上,并传入一个前缀'/v1'
def register_blueprints(app):
# from app.api.v1.user import user
# from app.api.v1.book import book
# app.register_blueprint(user)
# app.register_blueprint(book)
from app.api.v1 import create_blueprint_v1
app.register_blueprint(create_blueprint_v1(), url_prefix = '/v1')
def registe_plugin(app): # 插件的注册
from app.models.base import db
db.init_app(app)
with app.app_context(): # 上下文环境 把app推入到上下文栈中 才能使用create_all
db.create_all() # 来创建所有数据库,数据表
def create_app():
app = Flask(__name__) # 实例化flask核心对象
app.config.from_object('app.config.secure') # 读取配置文件下的secure
app.config.from_object('app.config.setting') # 读取配置文件下的setting
register_blueprints(app) # 注册蓝图到核心对象上
registe_plugin(app) # 最后调用 registe_plugin
return app
上面的写法o.dict 只能转换实例变量,不能将类变量也转换成字典。
3.深入理解dict机制
在Python中创建一个dict有很多种方式:
直接定义一个字典
Copy r = {
'name': 'gwf'
}
将一个对象传入dict函数 值得研究的是这第三种方法,当将一个对象传入dict函数的时候,他会去调用keys函数
keys 方法的目的就是为了拿到字典里所有的键,至于说这些键有那么,完全有我们自己来定义。keys 返回的必须是一个元组或者列表来声明要序列化的键。
而dict会以中括号的形式来拿到对应的值,如o["name"],但是默认是不能这么访问的,我们需要编写getitem 函数
Copy r = {'name': 'weilai'} # 直接定义一个字典
r = dict(name= 'weilai') # 使用dict函数
class Wei():
name = 'weilai'
age = 'age'
def __init__(self):
self.gender = 'male'
def keys(self):
return ('name','age','gender') # 取到 key , 做到自定义key
# # return ('name',) # 一个元素的元组
# return ['name'] # return 序列类型的都可以
def __getitem__(self,item):
return getattr(self,item) # 取到 key对应的value
o = Wei()
print(dict(o)) # {'name': 'weilai', 'age': 'age', 'gender': 'male'}
这样我们就成功的讲一个对象转化成了字典的形式,并且无论类变量和实例变量,都可以转化,更加灵活的是,我们可以自己控制,那些变量需要转化,哪些变量不需要转化
4.序列化SQLALChemy模型
有了之前的基础,我们就知道怎么序列化user对象了,我们只需要在User类中定义keys和getitem方法,然后在default函数中使用dict()函数即可
Copy class JSONEncoder(_JSONEncoder):
def default(self, o):
return dict(o)
class Flask(_Flask):
json_encoder = JSONEncoder
models/user.py
Copy class User(Base):
id = Column(Integer, primary_key=True)
email = Column(String(50), unique=True, nullable=False)
auth = Column(SmallInteger, default=1)
nickname = Column(String(24), nullable=False)
_password = Column('password', String(128))
# SQLALChemy的实例化是不会调用__init__函数的,要想让他调用就需要
# @orm.reconstructor这个装饰器
@orm.reconstructor
def __init__(self):
self.fields = ['id', 'email', 'nickname']
def keys(self):
return self.fields
# 支持隐藏字段
def hide(self, *keys):
[self.fields.remove(key) for key in keys]
# 支持添加字段
def append(self, *keys):
[self.fields.append(key) for key in keys]
5.完善序列化
优化1:每一个模型如果需要序列化,都要有getitem方法,可以放到基类里面去
优化2:default函数,是递归调用的,只要遇到不能序列化的对象,就会调用default函数。所以如果有其他类型,我们需要修改完善我们的default函数
优化3:我们的default函数需要增加容错性
Copy class JSONEncoder(_JSONEncoder):
def default(self,o):
# return o.__dict__ # 内置方法,将对象转化为字典 # 缺点是只能转换实例变量,不能将类变量也转换成字典
# return dict(o) # 考虑不全面.o得有上次定义的那两种方法才不会报错
if hasattr(o, 'keys') and hasattr(o, '__getitem__'):
return dict(o) # 得有这两种方法 才会return dict(o)
if isinstance(o, date): # 如果是 时间类型
return o.strftime('%Y-%m-%d')
raise ServerError()
优化4:之前编写的新的Flask类,JsonEncoder类都是不会轻易改变的,放到app.py中。 一些其他方法,却是 经常改变的,应该把他们放在init文件中
6.ViewModel对于API有意义吗?
viewmodel对于API来说,特别是内部开发来说非常有意义
viewmodel是为了我们的视图层,提供个性化的试图模型。SQLALChemy返回的模型是原始模型(格式和数据库中存储的一模一样)。 而前端可能需要我们返回一个意义更加明确的字段。
原始模型是根据数据库来生成的,他的格式是一定的,但是我们在视图层中或者API的返回中,要根据业务去具体的个性化一个个属性的 格式,这就必然存在一个由原始模型向视图模型转换的过程,这个过程最适合的是在View_model中进行一个转换。
我们在视图层写转换的代码,一来会污染视图层的代码,二来会难以复用 并且有的试图模型可能会比较复杂,设计到多个原始模型,这个代码必定会比较复杂,写在视图函数中就会非常不合适
对于完全严格意义上的RESTFul,viewmodel的意义并不大,因为完全资源意义的RESTFul是不考虑业务逻辑的