• SQLAlchemy+Flask-RESTful使用(一)


    前言

    开新坑啦.最近打算自己开一个资源聚合网站.就用Flask.

    当然也使用了 Flask-RESTful和SQLAlchemy啦

    写的过程中遇到过很多坑/觉得比较有意义的就写在这里.

    变更记录

    # 19.3.15 增加 SQLAlchemy查询数据里中文乱码的问题

    # 19.2.18 增加 Flask-RESTful中序列化组件的使用方法

    # 19.4.11 增加 序列化组件写在def中的书写方式

    正文

    前几章也讲过了基本的结合,这里主要讲讲常见问题 or 使用技巧

    中文乱码(问号)

    最先遇到的问题是乱码问题,数据库里的数据是这样的

    经过flask传回后是这样的

    解决方法是检查连接数据库时是否指定了数据库数据格式

    指定数据库格式写在访问连接后类似GET请求传参

    # 用户名:密码@访问地址:端口/数据库?编码
    engine = create_engine('mysql+mysqldb://root:***@***:***/website?charset=utf8mb4')

    序列化

    说到ORM,就不得不说序列化.因为我们在使用ORM的时候,获取的obj是所有字段的集合.那么问题来了,如果 用户表 中包含了用户的密码字段,我们将所有字段全部返回明显是不合适的/正确做法是视开发逻辑来选择传递给前端哪些字段

    序列化的方法有很多.适合自己的才是坠吼的.本人一直讨厌捧一踩一的人.

    一种方法是依次取再放入字典中然后return字典(序列化),这样可以保证我们每次都重新指定了返回的字段.但是这样对后期的维护多有不便,比如如果POST和GET用户要求都返回一样的字段.是不是要在GET和POST里写上两段同样的代码?如果需求改变是不是要同时改变两段代码?

    另一种方法是利用Flask-RESTful(以下省略为'官方')自带的序列化组件(EN官方文档)

    https://flask-restful.readthedocs.io/en/latest/fields.html

    如果我们使用 官方 带的序列化时,我们需要写一个序列化函数.序列化函数可以有多个参数,例如

        resource_fields = {
            # 设置序列化,k为序列化后的字段名/v为序列化的类型(如果重命名需要设置attribute/不写attribute代表与k同名的字段)
            'id': fields.Integer, # integer代表数字
            'name': fields.String,  # String代表str
            'money': fields.Integer(default=0),  # 设置如果money无值默认为0
            'status': fields.Integer,
            'lv': fields.Integer(attribute='fk_vip_on_vip_lv'),  # 设置该字段重命名为lv
            'info': fields.String(attribute='VipInfo.info')  # 该attr代表跨表到VipInfo的info字段(代表attr可以写表达式来跨表查询),注意这里的VipInfo是model中uselist所在行的字段名
        }

    编写完序列化我们怎样将obj与组件结合呢?文档给出两个方法

    方法1:装饰器

    class Vip(Resource):
        # VIP信息(单)
        resource_fields = {
            # 设置序列化,k为序列化后的字段名/v为序列化的类型(如果重命名需要设置attribute/不写attribute代表与k同名的字段)
            'id': fields.Integer, # integer代表数字
            'name': fields.String,  # String代表str
            'money': fields.Integer(default=0),  # 设置如果money无值默认为0
            'status': fields.Integer,
            'lv': fields.Integer(attribute='fk_vip_on_vip_lv'),  # 设置该字段重命名为lv
            'info': fields.String(attribute='VipInfo.info')  # 该attr代表跨表到VipInfo的info字段(代表attr可以写表达式来跨表查询),注意这里的VipInfo是model中uselist所在行的字段名
      } @marshal_with(resource_fields) # 将序列化组件结合/如obj为空也会返回, envelope指定返回的json里的序列化字典的名字(如不填则直接在最大的json中) # 写装饰器会导致return任意值都经过渲染/如规避使用一下方式 def get(self, vip_id): # 获取vip基本信息 from app.website.models import DBSession, Vip, VipInfo # 必须在函数内引入 session = DBSession() obj = session.query(Vip).join(VipInfo).filter(Vip.id==vip_id).first() return obj

    使用装饰器会将所有请求在传回时强制序列化,这也带来了一个问题点,如果正常访问,一切看起来如此正常

    但是当我们访问一个不存在的id时

    这样的接口逻辑就可能让前后端对接出现问题.并且增加前端判断的工作量.大家都是打工的,何必苦苦为难呢?

    如果我们先判断是否为空然后再返回呢?我们修改一下代码

    class Vip(Resource):
        # VIP信息(单)
        resource_fields = {
            # 设置序列化,k为序列化后的字段名/v为序列化的类型(如果重命名需要设置attribute/不写attribute代表与k同名的字段)
            'id': fields.Integer, # integer代表数字
            'name': fields.String,  # String代表str
            'money': fields.Integer(default=0),  # 设置如果money无值默认为0
            'status': fields.Integer,
            'lv': fields.Integer(attribute='fk_vip_on_vip_lv'),  # 设置该字段重命名为lv
            'info': fields.String(attribute='VipInfo.info')  # 该attr代表跨表到VipInfo的info字段(代表attr可以写表达式来跨表查询),注意这里的VipInfo是model中uselist所在行的字段名
      } @marshal_with(resource_fields) # 将序列化组件结合/如obj为空也会返回, envelope指定返回的json里的序列化字典的名字(如不填则直接在最大的json中) # 写装饰器会导致return任意值都经过渲染/如规避使用一下方式 def get(self, vip_id): # 获取vip基本信息 from app.website.models import DBSession, Vip, VipInfo # 必须在函数内引入 session = DBSession() obj = session.query(Vip).join(VipInfo).filter(Vip.id==vip_id).first() if obj: return obj else: return None

    结果:

    为什么我直接写了无obj返回 None 还是不起作用呢?因为使用装饰器的方式会拦截所有返回强制序列化,所以找不到序列化字段所以都是默认值.

    如果我们想更为灵活的,比如obj存在时再使用序列化,为空时传送我们定制的错误信息就需要使用方法2

    方法2:手动使用序列化组件

    Flask-RESTful官方也提供了自定义的方式,这种方式更为灵活.

    class Vip(Resource):
        # VIP信息(单)
        vip_resource_fields = {
            # 设置序列化,k为序列化后的字段名/v为序列化的类型(如果重命名需要设置attribute/不写attribute代表与k同名的字段)
            'id': fields.Integer, # integer代表数字
            'name': fields.String,  # String代表str
            'money': fields.Integer(default=0),  # 设置如果money无值默认为0
            'status': fields.Integer,
            'lv': fields.Integer(attribute='fk_vip_on_vip_lv'),  # 设置该字段重命名为lv
            'info': fields.String(attribute='VipInfo.info')  # 该attr代表跨表到VipInfo的info字段(代表attr可以写表达式来跨表查询),注意这里的VipInfo是model中uselist所在行的字段名
      } # @marshal_with(resource_fields) # 将序列化组件结合/如obj为空也会返回, envelope指定返回的json里的序列化字典的名字(如不填则直接在最大的json中) # 写装饰器会导致return任意值都经过渲染/如规避使用一下方式 def get(self, vip_id, resource_fields=vip_resource_fields): # 必须映引入resource # 获取vip基本信息 from app.website.models import DBSession, Vip, VipInfo # 必须在函数内引入 session = DBSession() obj = session.query(Vip).join(VipInfo).filter(Vip.id==vip_id).first() if obj: dic = marshal(obj, resource_fields) # return时再指定序列化,不指示则不序列化 return info_tool.get_success_dic(dic) else: return info_tool.get_error_dic(1001, 'not user') return obj

    # 我们也可以将图上的 vip_resource_fields 卸载def中(比如写在 get 中),这样我们就不用在get的参数中传 resource_fields ,其他不变即可

    # info_tool模块是本人写的自用模块,功能有将 含有各种无法正常转json的字典转为正常的字典/定制封装返回值 等等.

    # 此处的 get_success_dic 功能是在外层包一个dic,有一个code值作为前后端交流,信息在data里,例如

    # get_error_success 同上

    info_tool模块如下(19.3.18贴出,后续优化恕不通知)

    # -*- coding=utf-8 -*-
    # 将pymysql得到的dic转换为可以正常转json的dic
    import time, datetime, decimal
    
    
    def kv_to_safe(k, v):
        # 判断v的数据类型,转换成可json的类型
        if type(v) == datetime.datetime:
            # 如果是datetime类型则转换为时间戳
            v = time.mktime(v.timetuple())
        if type(v) == decimal.Decimal:
            # 如果是decimal类型则转换为flaot
            v = float(v)
        if type(v) == bytes:
            # bytes类型转str(utf8)
            v = str(v, encoding='utf8')
        return k, v
    
    
    def dict_to_safe(inf, code):
        # dict转可转json的dict
        if inf:
            if type(inf) == dict:
                info = {}
                for k,v in inf.items():
                    k, v = kv_to_safe(k, v)
                    info[k] = v
            elif type(inf) == list:
                info = []
                for i in inf:
                    dic = {}
                    for k,v in i.items():
                        k, v = kv_to_safe(k, v)
                        dic [k] = v
                    info.append(dic)
        else:
            info = None
        dic = {
            'code': code,
            'data': info
        }
        return dic
    
    
    def oneobj_to_safe(model):
        # 单一对象转dic
        dic = {}
        if model == None:
            return None
        for col in model._sa_class_manager.mapper.mapped_table.columns:
            dic[col.name] = getattr(model, col.name)
        return dic
    
    
    def allobj_to_safe(model_list):
        # 多个对象转dic
        dic_list = []
        if model_list == None:
            return None
        for model in model_list:
            dic_list.append(oneobj_to_safe(model))
        return dic_list
    
    
    def get_error_dic(code, message):
        # 生成错误json
        dic = {
            'code': code,
            'data': {
                'message': message
            }
        }
        return dic
    
    
    def get_success_dic(dic):
        # 生成正确json,code默认200
        dic = {
            'code': 200,
            'data': dic
        }
        return dic
  • 相关阅读:
    几种常见的Map的区别
    BlockingQueue详解
    Android开发过程中内存泄露检测
    Android studio 技巧设置(持续更新中)
    Android Support兼容包详解
    单例模式的饿汉式为什么需要双重锁定
    View分析
    Activity的启动流程分析
    LeetCode第十四题-字符串数组中最长的共同前缀
    LeetCode第十三题-将罗马数字转化为数字
  • 原文地址:https://www.cnblogs.com/chnmig/p/10538149.html
Copyright © 2020-2023  润新知