Fastapi框架-冷饭再炒-(12)-异步数据库之ORM-Gino使用体验后的基于asyncpg 自制简易版异步 ORM

2,166 阅读15分钟

Gino ORM 使用比较难以说出感觉的体验 前置说明

在实际业务逻辑过程,之前我一直习惯于使用peewee来对我们的数据库进行相关数据处理。但是如果需要使用到我们的异步特性的时候,peewee就不能很好的实现了!比较它是同步机制的!虽然也有peewee_async对应的异步的库的支持!但是出于好奇~想实践其他的异步的ORM的使用!

之前也有介绍过关于asyncio中使用异步数据库几种方案,后来也有发现其他方式,总结下来其实是有几个ORM的实现方式的:

  • gino (这个库使用起来有点点小小的痛苦,主要是自己看源码看得晕,功力不够!理解的不够深!)

  • tortoise-orm 这个库,对于Postger来说有一个小问题,也许是我还不知道怎么处理,就是使用模型创建表的是,设置字段为主键的时候就报错了!

  • sqlalchemy 1.4版本中新增的sqlalchemy.ext.asyncio.AsyncSession (www.osgeo.cn/sqlalchemy/…)

  • peewee_async 第三方的peewee的异步实现(peewee-async.readthedocs.io/en/latest/)

  • sqlalchemy 同步库 + ormar (github.com/collerek/or…)

从实践来看:

  • tortoise-orm的使用其实还是挺好用,但是如果你使用是Postger可能就不那么好运~!有些地方还是有点问题!比如之前说的主键index=True的问题

  • gino是国内一大牛实现的,基于sqlalchemy之上,好处就是是可以直接从表中生成对应的模型。使用上如果是简单的单表的查询的话,是不错滴!但是如果涉及多表的操作,且是不用外键关联的方式下操作表的话,可能用起来就相对的复杂了一点,主要是官网的文档的示例不够,我也初入源码内简单看过,但是看看起来还是有点相对的复杂!以下是我初步使用gino的一些小实践笔记。

gino ORM 实践:

因为为的的业务生产环境上已经是有数据表了,现在是需要基于数据表来生成模式(不想一个一个的模型去写)。所以可以借助于sqlalchemy生成模型的神器来从表里生成对应的模型。

1 从数据表中生成模型:

表的结果如下:

image.png

image.png

sqlacodegen --noconstraints --outfile ./sysmodels.py postgres://postgres:123456@127.0.0.1:5432/zyxadminsystem

2 改造生成模型让它适用于

2.1定义基础模型类

import asyncpg
from gino.dialects.asyncpg import Pool as BasePool
from gino import Gino
from typing import List


class Pool(BasePool):
    """
    Pool allow to provide multiple postgresql hosts,
    since BasePool couldn't do that because it sets hosts implicitly
    """
    async def _init(self):
        args = self._kwargs.copy()
        args.update(
            loop=self._loop,
        )

        dsn = str(self._url)
        if dsn.startswith('postgresql+asyncpg') or dsn.startswith('postgres+asyncpg'):
            dsn = dsn.replace('+asyncpg', '', 1)

        self._pool = await asyncpg.create_pool(dsn, **args)
        return self


db = Gino()



class Base(db.Model):
    pass

    # self.to_dict() 自带有了!
    def __init__(self, **kw):
        super().__init__(**kw)
        self._children = set()

    @property
    def children(self):
        return self._children

    @children.setter
    def add_child(self, child):
        self._children.add(child)

    # 单个对象方法
    def single_to_dict(self):
        return {c.name: getattr(self, c.name) for c in self.__table__.columns}

    def cust_to_dict(self):
        return {c.name: getattr(self, c.name, None) for c in self.__table__.columns}

    def to_dict_exclude_field(self, fields: List[str] = None):
        if fields:
            return {key: val for key, val in self.to_dict().items() if key not in fields}
        return self.to_dict()

    # 多个对象
    def dobule_to_dict(self):
        result = {}
        for key in self.__mapper__.c.keys():
            if getattr(self, key) is not None:
                result[key] = str(getattr(self, key))
            else:
                result[key] = getattr(self, key)
        return result

    # 配合多个对象使用的函数---结果集的列表形式的的时候转化
    def dobule_to_json(self, all_vendors):
        v = [ven.dobule_to_dict() for ven in all_vendors]
        return v

    # 配合多个对象使用的函数---结果集的列表形式的的时候转化
    @staticmethod
    async def dobules_to_json_forsinge(all_vendors):
        v = [ven.to_dict() for ven in all_vendors]
        return v

    # @classmethod
    # def children_handle(cls,model,exclude_field=None):
    #     return [ model.to_dict_exclude_field(exclude_field) for ch in cls.children][0]
    # _result_user_depart = [ children_handle(item) for item in parents]

    @classmethod
    async def get_one_or_none(cls, where):
        return await cls.query.where(where).gino.one_or_none()

    @classmethod
    async def get_all(cls, where):
        return await cls.query.where(where).gino.all()

    @classmethod
    async def store_count(cls):
        # 测试失败
        return await db.func.count(cls.id).gino.scalar()


Column = db.Column
Integer = db.Integer
Text = db.Text
String = db.String
DateTime = db.DateTime
Date = db.Date

2.2 实现对应的模型和表映射

from apps.plugs.admin.database import *
from sqlalchemy import *
from sqlalchemy.orm import relationship

# import logging
# logging.getLogger("gino").setLevel(logging.INFO)

import logging


# logging.basicConfig(level=logging.INFO)


class ModelBase(Base):
    # 不同的时间类型
    create_time = Column(DateTime, comment='创建时间')
    # update_time = db.Column(db.Date, comment='修改时间')
    update_time = Column(DateTime, comment='创建时间')


class SysDepart(ModelBase):
    __tablename__ = 'sys_depart'
    __table_args__ = {'comment': '部门表'}

    id = Column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
    parent_id = Column(Integer, index=True, comment='父ID')
    depart_name = Column(Text, comment='当前机构/部门名称')
    sort_no = Column(Integer, comment='排序')
    description = Column(Text, comment='描述')
    org_category = Column(Text, comment='机构类别 1组织机构,2岗位')
    org_type = Column(Text, comment='机构类型 1一级部门 2子部门')
    org_code = Column(Text, index=True, comment='部门编码/机构编码')
    remark = Column(Text, comment='信息备注')
    status = Column(Integer, index=True, comment='状态(1-正常,2-冻结)')
    mobile = Column(Text, comment='联系电话')
    fax = Column(Text, comment='传真')
    address = Column(Text, comment='地址')
    del_flag = Column(Integer, index=True, comment='删除状态(0-正常,1-已删除)')
    memo = Column(Text, comment='备注')
    create_by = Column(Text, default='now()', comment='创建人')
    update_by = Column(Text, comment='更新人')


class SysDepartPermission(ModelBase):
    __tablename__ = 'sys_depart_permission'
    __table_args__ = {'comment': '部门权限关联表'}

    id = Column(Integer, primary_key=True, comment='主键ID')
    depart_id = Column(Integer, index=True, default="1", comment='部门id')
    permission_id = Column(Integer, index=True, comment='权限id')
    data_rule_ids = Column(Text, comment='数据规则id')
    
 (省略其他模型-········)

2.3 基本的操作

2.3.1模型操作

这些操作主要是有连表查询,单表多条件查询,分页查询等

  • 单表的多条件查询,并把返回结果转为字典
import asyncio
import traceback
if __name__ == '__main__':
    async def main():
        import gino
        try:
            await db.set_bind( 'postgres://postgres:123456@127.0.0.1:5432/zyxadminsystem',ssl=None,statement_cache_size=0,pool_class=Pool,
                # 输出查询的SQL
                # echo=True
            )
            # 且多条件的查询的方法
            obj = await SysUser.query.where(and_(SysUser.id == 1, SysUser.realname == '超级管理员')).gino.one_or_none()
            print(obj.to_dict())
            #
        except Exception:
            traceback.print_exc()

    asyncio.get_event_loop().run_until_complete(main())

输出的结果为:

{'del_flag': 0, 'password': '1856555555', 'third_type': '', 'phone': '11 ', 'telephone': '', 'email': '308711822@qq.com', 'client_id': '', 'create_by': '', 'sex': -5, 'org_code': 'A01A01', 'birthday': datetime.datetime(2020, 12, 16, 16, 0), 'third_id': '', 'username': '管理员22222', 'realname': '超级管理员', 'description': '', 'del_time': None, 'update_by': 'superadmin', 'post': '总经理', 'salt': '123465', 'work_no': '123456', 'status': 1, 'depart_ids': '', 'position_name': '研发部经理', 'create_time': datetime.datetime(2020, 12, 18, 17, 50, 17), 'avatar': xxxxxxx', 'update_time': None, 'user_identity': 1, 'id': 1, 'rel_tenant_ids': ''}

  • 连表查询
results = await SysUserDepart.join(SysDepart, SysUserDepart.dep_id == SysDepart.id).select().where((SysDepart.del_flag == 0) & (SysUserDepart.dep_id == 1)).order_by(SysUserDepart.user_id).gino.all()
print(results)
for i in results:
    print(i.dep_id)
  • 子查询
j = join(SysUserDepart, SysUser, SysUserDepart.user_id == SysUser.id)
results = await SysUserDepart.query.where(SysUser.id==1).select_from(j).order_by(SysUserDepart.user_id).gino.all()

  • 简单查询
# 返回全部
results = await SysUserRole.query.where(SysUserRole.user_id == 1).gino.all()

results = await SysUser.get_all(SysUser.id == 1)

# 返回一个
where = and_(SysUser.id == 1, SysUser.username == 'superadmin')
obj = await SysUser.get_one_or_none(where)
 
#返回所有并且排序 
results = await SysUser.query.where(SysUser.id == 1).order_by(SysUser.id).gino.all()


  • 模糊查询
 obj = await SysUser.query.where(and_(SysUser.id == 1, SysUser.username.contains('super22'))).gino.one_or_none()        
  • 查询表的某个字段
# 1
obj = await SysUser.select('username').gino.first()
  
# 2
cols = [c.name for c in SysUserDepart.__table__.c if c.name != 'large_field']
print(cols)
cards = await SysUserDepart.select(*cols).where(SysUserDepart.id == 1).gino.all()

PS 选择某个字段查询-返回的是一个元祖,不能使用to_dict

  • 复杂的连表
_result_user_depart = await SysUserDepart \
.join(SysDepart, SysUserDepart.dep_id == SysDepart.id) \
.select() \
.where((SysDepart.del_flag == 0) & (SysUserDepart.user_id == 1)) \
.order_by(SysUserDepart.user_id).gino.all(return_mode=True)
  • 查询记录数
_result_user_depart = await SysUserDepart.store_count()
 
# 查询总数的一种方法:

setattr(SysUser, 'count', func.count(SysUser.id).label('count'))
print(await SysUser.select('count').where(SysUser.id==1).gino.first())

连表的情况下查询总数:
query = SysUserDepart.outerjoin(SysDepart, SysUserDepart.dep_id == SysDepart.id).select().where((SysDepart.del_flag == 0) & (SysUserDepart.user_id == 1)).order_by(SysUserDepart.user_id)

  • db-select使用
query = db.select([SysUserDepart])
rows = await query.gino.all()
  • 连表加载器模型转换的使用
###==========
from gino.loader import ModelLoader
query = db.select([SysUserDepart])
query = query.execution_options(loader=ModelLoader(SysUserDepart))
users = await query.gino.all()

###==========
query = db.select([SysUserDepart])
query = query.execution_options(loader=SysUserDepart)
users = await query.gino.all()

###==========
query = db.select([SysUserDepart])
users = await query.gino.load(SysUserDepart).all()

##========
query = SysUserDepart.join(SysDepart, SysUserDepart.dep_id == SysDepart.id).select().where((SysDepart.del_flag == 0) & (SysUserDepart.user_id == 1)).order_by(SysUserDepart.user_id)
parents = await query.gino.load(SysUserDepart.distinct(SysUserDepart.id).load(add_child=SysDepart)).all()

print('parents', parents)
for isd in parents:
    print(dir(isd))
    print(isd.children)
    for ch in isd.children:
        print("还是兑换是", ch.depart_name)
        
##========
print("选择某个字段查询-返回的是一个元祖使用加载器的方式:to_dict")
query = db.select([SysUser.username, SysUser.phone])
users = await query.gino.load(SysUser).all()
print("选择某个字段查询-返回的是一个元祖,users", users)
for isdh in users:
    print(isdh.username)
    print(isdh.phone)
  • 分析SQL:
print('分析', await db.scalar(db.exists().where(SysUser.id == 1).select()))

2.3.2 执行SQL

query = db.text('SELECT * FROM sys_user WHERE id = :id_val')
row = await db.first(query, id_val=1)
print("SSSSSSSSSSSSSSSSS",row)

在进一步的进行连表操作分页的过程中,比如: 我单纯只是想查询某个表中某些字段,且需连表查询的情况下,并且需要进行相关的数据的分页查询,简单的数据分页效果如图:

image.png

我在使用这个Gino的时候就特别的费劲了!可能是我不知道怎么再继续使用哪个加载器!!!以至于我们的对- db-select的使用已经是感觉费劲!

所以最终还是觉得自己写一个简单的ORM来的直接一点,本来Gino也是基于asyncpg,所以感觉掌握在自己能理解范围内的ORM才比较顺手一点!

所以还是尝试进行小小实践封装一下:

2 基于asyncpg 自制简易异步 ORM

简单的结构为:

image.png

说白了其实就是如何进行链路调用式,把SQL语句给组装起来!

2.1 SQL封装

其中models.base:主要功能滴对,公共的类型

  • 条件语句
  • 查询语句
  • 限制语句
  • 分组语句
  • 排序语句
  • 查询或插入返回语句
  • 连表查询语句 等一系列的操作进行统一包装一下。

2.1.1 base.py 公共查询基础条件封装:

from dataclasses import dataclass

@dataclass
class Base:
    pass
    # 选择表的名称
    select_table = ''
    __where__ = []
    #  # 检索的字段
    __select_fields__ = []
    __sql__ = None
    __lock__ = None  # lock
    __groupby__ = []  # 排序字段
    __orderby__ = []  # 排序字段
    __join__ = []  # leftjoin
    __join_on_exp__ = []  # JOIN表达式的处理
    # 查询全局的总数QUA
    __count__ = []
    # PostgreSQL returning查询返回
    __returning__ = []

    __offset__ = 0
    __limit__ =0
    __ispaginate_by__= False

    # 常见的一些表达式关键词
    operators = [
        '=', '<', '>', '<=', '>=', '<>', '!=',
        'like', 'like binary', 'not like', 'between', 'not between',
        '&', '|', '^', '<<', '>>',
        'rlike', 'regexp', 'not regexp',
        '~', '~*', '!~', '!~*', 'similar to',
        'not similar to', 'not ilike', '~~*', '!~~*', 'in', 'not in'
    ]

    # 设置要查询的表名称
    def tablle(self, select_table):
        self.select_table = select_table
        # 重置设置相关信息
        self.__reset__()
        return self

    def __reset__(self):
        # 重置所有的表达式
        self.__where__.clear()
        self.__select_fields__.clear()
        self.__groupby__.clear()  # 排序字段
        self.__orderby__.clear()  # 排序字段
        self.__join__.clear()  # oin
        self.__join_on_exp__.clear()  # join
        self.__sql__ = None
        self.__returning__.clear()
        # 限制数
        self.__limit__ = 0
        # 偏移量
        self.__offset__ = 0
        # 乐观锁
        self.__lock__ = None

        __ispaginate_by__ = False


    def _format_columns(self, columns):
        return list(map(lambda index: index, columns))

    def format_column(self, columns=None):
        return '`{}`'.format(columns)

    def format_string(self, columns):
        return "'{}'".format(columns)

    def _compile_dict(self, data):
        return ['{}={}'.format(index, self.format_string(value)) for index, value in data.items()]

    def _compile_lock(self):
        return '' if not self.__lock__ else self.__lock__

    # 乐观锁
    def lock_by_for_update(self):
        self.__lock__ = ' for update'
        return self

    def sql(self):
        return self.__sql__

    def list_to_str(self, data):
        if data and isinstance(data, list):
            # 把列表转为字符串形式
            return '({})'.format(data[0]) if len(data) == 1 else tuple(data).__str__()
        if data and isinstance(data, tuple):
            return data.__str__()
        else:
            raise Exception('参数异常错误!')

    def list_to_str_no_format(self, data):
        '''
        列表转化为字符串格式
        :param data:
        :return:
        '''
        if data and isinstance(data, list):
            return '{}'.format(data[0]) if len(data) == 1 else tuple(data).__str__()
        if data and isinstance(data, tuple):
            return data.__str__()
        else:
            raise Exception('参数异常错误!')

    def _compile_where(self):
        '''
        开始组合为表达式类型
        :return:
        '''
        # 如果存在表达式值的话
        if len(self.__where__) > 0:
            sqlstr = []
            # 遍历所有的表达式的元素
            for index in self.__where__:
                # 如果表达式里面是一个字典类型的话
                if isinstance(index, dict):
                    # 使用and的符号进行串联起来
                    sqlstr.append(' and '.join(self._compile_dict(index)))
                # 如果是元组类型的话
                elif isinstance(index, tuple):
                    sqlstr.append(self._compile_tuple(index))
                elif isinstance(index, str):
                    sqlstr.append(index)
            return ' where {}'.format(' and '.join(sqlstr))
        return ''

    def _compile_groupby(self):
        # 如果存在分组信息则添加具体的分组字段信息
        return '' if len(self.__groupby__) == 0 else ' group by ' + ','.join(self.__groupby__)

    def _compile_orderby(self):
        # 串联排序字段
        return '' if len(self.__orderby__) == 0 else ' order by ' + ','.join(self.__orderby__)

    def offset(self, number:int):

        if self.__limit__ < 0:
            raise Exception('Limit 数不能小于等于0')

        if not isinstance(number,int):
            raise Exception('offset 必须是整型类型!')

        if number <0:
            raise Exception('offset 限制数不能小于0')

        self.__offset__ = int(number)
        return self

    def limit(self, number: int):
        if not isinstance(number, int):
            raise Exception('offset 必须是整型类型!')
        if number <0:
            raise Exception('Limit 限制数不能小于0')
        self.__limit__ = int(number)
        return self

    def paginate(self, page_no: int,page_size:int):
        self.__ispaginate_by__ = True
        self.limit(page_size)
        self.offset((page_no-1)*page_size)
        return self

    def _compile_limit_offset(self):
        pass
        # 不调用分页的场景情况下
        if not self.__ispaginate_by__:
            __offset__sql ='' if not self.__offset__ else ' offset {}'.format(self.__offset__)
        else:
            # 调用了分页函数场景下
            __offset__sql =' offset {}'.format(self.__offset__)

        return '' if not self.__limit__ else ' limit {}{}'.format(self.__limit__,__offset__sql)


    # 左连表的查询
    def _compile_leftjoin(self):
        if self.__join__:
            return ' ' + ' '.join([v for v in self.__join__])
        return ''

    def _compile_tuple(self, data):
        # 对in类的 where条件的做特殊的进行处理
        if data[1] in ['in', 'not in']:
            # data[2]的列表类型转为---把列表转为字符串
            return '{} {} {}'.format(data[0], data[1], self.list_to_str(data[2]))
        # 对between的表达式进行特殊的出来
        elif data[1] in ['between', 'not between']:
            # 如果最后的一个参数 存在两个的情况下
            if not (len(data) == 3 and len(data[2]) == 2):
                raise Exception('between表达式错误,需要有三个参数,且最后的一个参数值存在两个值')
            return '{} {} {} and {}'.format(data[0], data[1], self.format_string(data[2][0]), self.format_string(data[2][1]))

        return '{} {} {}'.format(data[0], data[1], self.format_string(data[2]))

    def groupby(self, *args):
        # 批量转换进行列表转
        self.__groupby__ = self._format_columns(list(args))
        return self

    def returning(self, column):
        # 批量转换进行列表转
        if isinstance(column, list):
            self.__returning__ = column
        else:
            self.__returning__.clear()
            self.__returning__.append(column)
        return self

    def orderby(self, column, direction='asc'):
        if direction.lower() == 'asc':
            self.__orderby__.append('{} {}'.format(column, direction))
        else:
            self.__orderby__.append('{} {}'.format(column, 'desc'))
        return self

        # 左连表的查询

    def _compile_returning(self):
        if self.__returning__:
            return 'returning {}'.format(','.join([v for v in self.__returning__]))
        return ''

    def where(self, *args):
        length = args.__len__()
        # 如果传入的查询条件是一个字典类型的,那么就转化为==
        if length == 1 and isinstance(args[0], dict):
            self.__where__.append(args[0])
        # 如果传入的是多个参数那么应该是一个元组类型的
        elif length == 2:
            self.__where__.append({args[0]: args[1]})
        # 如果传入的是一个表达式类型的,就三个的参数
        # 处理判断第二个的参数是否再对应的表达式
        elif length == 3:
            if args[1] in self.operators:
                # 如果是等号的类型
                if args[1] == '=':
                    self.__where__.append({args[0]: args[2]})
                else:
                    # 如果是其他的表达式
                    self.__where__.append((args[0], args[1], args[2]))
            else:
                raise Exception('表达式不在预定义范围内: "{}"'.format(args[1]))
        elif length == 1 and isinstance(args[0], str):
            self.__where__.append(args[0])
        else:
            raise Exception('错误的参数表达式类型')
        return self


    def join_on_exp(self, *args):

        # __join_on_exp__ 串联起来的的格式是使用 三个元祖对象进行串联
        length = args[0].__len__()
        if isinstance(args[0], set):
            raise Exception('__join_on_exp__表达式不知此set类型传入: ')
        elif isinstance(args[0], list):
            # 多条件的类型的串联
            for item in args[0]:
                self.__join_on_exp__.append(item)
            pass
        elif isinstance(args[0], dict):
            for key, value in args[0].items():
                self.__join_on_exp__.append((key, '=', value))
        # 如果传入的查询条件是一个字典类型的,那么就转化为==
        elif length == 1 and isinstance(args[0][0], str):
            # 需要组成三个对象的元祖类型
            self.__join_on_exp__.append((args[0][0], '', ''))
        # 如果传入的是多个参数那么应该是一个元组类型的
        elif length == 1 and isinstance(args[0][0], dict):
            # 多个字典类型的=号的串联
            for key, value in args[0][0].items():
                self.__join_on_exp__.append((key, '=', value))
        # 如果传入的是多个参数那么应该是一个元组类型的
        elif length == 2:
            # 如果里面包含有占位符的标记的话,可以串联起两个的表达式类型
            if '?' in args[0][0]:
                # 需要组成三个对象的元祖类型
                self.__join_on_exp__.append(("{}".format(args[0][0].replace('?', args[0][1])), '', ''))
            else:
                # 没有任何的占位符的默认是= 串联起来
                self.__join_on_exp__.append((args[0][0], '=', args[0][1]))
        # 如果传入的是一个表达式类型的,就三个的参数
        # 处理判断第二个的参数是否再对应的表达式
        elif length == 3:
            if args[0][1] in self.operators:
                # 如果是等号的类型
                if args[0][1] == '=':
                    self.__join_on_exp__.append((args[0][0], '=', args[0][2]))
                else:
                    # 如果是其他的表达式
                    self.__join_on_exp__.append((args[0][0], args[0][1], args[0][2]))
            else:
                raise Exception('__join_on_exp__表达式不在预定义范围内: "{}"'.format(args[0][1]))
        else:
            raise Exception('__join_on_exp__错误的参数表达式类型')
        return self

    def _compile_on(self):

        # if not self.__join_on_exp__:
        #     raise Exception('连表查询缺少__join_on_exp__参数表达式错误!')
        sqlstr = ['{} {} {}'.format(index[0], index[1], index[2]) for index in self.__join_on_exp__]

        return ' and '.join(sqlstr)

    def leftjoin_str(self, join_tablename, on: str):
        # if not  self.__join_on_exp__:
        #     raise Exception('__join_on_exp__缺少必要的JOIN ON表达式!')
        # ON表示的多个条件的处理
        self.__join__.append('left join {} on {}'.format(join_tablename, on))

        return self

    def innerjoin_tuple(self, join_tablename, on):
        self.join_on_exp(on)
        if not self.__join_on_exp__:
            raise Exception('连表查询缺少__join_on_exp__参数表达式错误!')
        self.__join__.append('inner join {} on {}'.format(join_tablename, self._compile_on()))
        self.__join_on_exp__.clear()  # join
        return self

    def leftjoin_tuple(self, join_tablename, on):
        self.join_on_exp(on)
        if not self.__join_on_exp__:
            raise Exception('连表查询缺少__join_on_exp__参数表达式错误!')
        self.__join__.append('left join {} on {}'.format(join_tablename, self._compile_on()))
        self.__join_on_exp__.clear()  # join
        return self

    def rightjoin_tuple(self, join_tablename, on):
        self.join_on_exp(on)
        if not self.__join_on_exp__:
            raise Exception('连表查询缺少__join_on_exp__参数表达式错误!')
        self.__join__.append('right join {} on {}'.format(join_tablename, self._compile_on()))
        self.__join_on_exp__.clear()  # join
        return self

    def full_outer_join_tuple(self, join_tablename, on):
        self.join_on_exp(on)
        if not self.__join_on_exp__:
            raise Exception('连表查询缺少__join_on_exp__参数表达式错误!')
        self.__join__.append('full outer join {} on {}'.format(join_tablename, self._compile_on()))
        self.__join_on_exp__.clear()  # join
        return self

2.1.2 select.py 选择器的封装:

from dataclasses import dataclass
from apps.ext.ormx.models.base import Base
from apps.ext.ormx.exceptions import ClientConfigBadException
from typing import List, Union, Tuple


@dataclass
class Select(Base):
    pass
    # 要刷选的字段信息,默认是全部字段都刷新

    def select(self, *args):
        '''
        支持字符串,元组和列表形式的参数传入
        :param args:
        :return:
        '''
        if len(args) == 1 and isinstance(args[0], str):
            # 如果是字符串类型,且带有,进行切分
            if ',' in args[0]:
                for v in args[0].split(','):
                    self.__select_fields__.append(v.strip())
            else:
                # 单纯的一个字符串没有,的直接添加
                self.__select_fields__.append(args[0])
        elif len(args) == 1 and isinstance(args[0], List):
            for v in args[0]:
                self.__select_fields__.append(v.strip() if isinstance(v, str) else v)
        elif len(args) > 1 and isinstance(args, Tuple):
            for v in args:
                self.__select_fields__.append(v.strip() if isinstance(v, str) else v)
        print(self.__select_fields__)
        return self



    def do_select(self,isback___sql__=False):
        if not self.select_table:
            raise ClientConfigBadException(errmsg='请设置需要查询查询的表')
        if not self.__select_fields__:
            self.__select_fields__.append('*')

        # 子SQL,如查询条件等其他的组合
        # subsql = ''.join([self._compile_where(), self._compile_whereor(), self._compile_orwhere(), self._compile_groupby(), self._compile_orderby(),self._compile_having(), self._compile_offset(), self._compile_lock()])
        subsql = ''.join([self._compile_where(),self._compile_groupby(), self._compile_orderby(),self._compile_lock(),self._compile_limit_offset()])
        joinsql = ''.join(self._compile_leftjoin())

        if self.__returning__:
            raise ClientConfigBadException(errmsg='select查询中不允许调用returning函数!请检测!')

        self.__sql__ = 'select {} from {}{}{}'.format(','.join(self.__select_fields__), self.select_table,joinsql,subsql)

        # returnsql = "select {} from {}{}{}".format(self._compile_limit(), ','.join(self.__select__), self._tablename(), joinsql, subsql)
        if isback___sql__:
            return self.__sql__
        return self

2.1.3 update.py 更新器的封装:

from dataclasses import dataclass
from apps.ext.ormx.models.base import Base
from apps.ext.ormx.exceptions import ClientConfigBadException
import collections


@dataclass
class Update(Base):
    pass
    # 要刷选的字段信息,默认是全部字段都刷新

    # 自减
    def decrement(self, key, amount=1):
        if isinstance(amount, int) and amount > 0:
            data = collections.defaultdict(dict)
            data[key] = '{}-{}'.format(str(key), str(amount))
            self.__sql__ = self._compile_increment(data)
            return self

    # 自增
    def increment(self, key, amount=1):
        if isinstance(amount, int) and amount > 0:
            data = collections.defaultdict(dict)
            data[key] = '{}+{}'.format(str(key), str(amount))
            self.__sql__ = self._compile_increment(data)
            return self

    # 组合
    def _compile_update(self, data):
        if not self.select_table:
            raise ClientConfigBadException(errmsg='请设置需要查询查询的表')

        print("当时",self.__returning__)

        return "update {} set {}{}{}".format(self.select_table, ','.join(self._compile_dict(data)), self._compile_where(),self._compile_returning())

    def _compile_increment(self, data):
        if not self.select_table:
            raise ClientConfigBadException(errmsg='请设置需要查询查询的表')
        subsql = ','.join(['{}={}'.format(str(index), value) for index, value in data.items()])
        return "update {} set {}{}{}".format(self.select_table, subsql, self._compile_where(),self._compile_returning())

    # 查询
    def update(self, data):
        if data and isinstance(data, dict):
            data = {key: value for key, value in data.items()}
            self.__sql__ = self._compile_update(data)
            return self

2.1.4 insert.py 插入数据器的封装:

from dataclasses import dataclass
from apps.ext.ormx.models.base import Base
from apps.ext.ormx.exceptions import ClientConfigBadException
import collections


@dataclass
class Update(Base):
    pass
    # 要刷选的字段信息,默认是全部字段都刷新

    # 自减
    def decrement(self, key, amount=1):
        if isinstance(amount, int) and amount > 0:
            data = collections.defaultdict(dict)
            data[key] = '{}-{}'.format(str(key), str(amount))
            self.__sql__ = self._compile_increment(data)
            return self

    # 自增
    def increment(self, key, amount=1):
        if isinstance(amount, int) and amount > 0:
            data = collections.defaultdict(dict)
            data[key] = '{}+{}'.format(str(key), str(amount))
            self.__sql__ = self._compile_increment(data)
            return self

    # 组合
    def _compile_update(self, data):
        if not self.select_table:
            raise ClientConfigBadException(errmsg='请设置需要查询查询的表')

        print("当时",self.__returning__)

        return "update {} set {}{}{}".format(self.select_table, ','.join(self._compile_dict(data)), self._compile_where(),self._compile_returning())

    def _compile_increment(self, data):
        if not self.select_table:
            raise ClientConfigBadException(errmsg='请设置需要查询查询的表')
        subsql = ','.join(['{}={}'.format(str(index), value) for index, value in data.items()])
        return "update {} set {}{}{}".format(self.select_table, subsql, self._compile_where(),self._compile_returning())

    # 查询
    def update(self, data):
        if data and isinstance(data, dict):
            data = {key: value for key, value in data.items()}
            self.__sql__ = self._compile_update(data)
            return self

2.1.5 delete.py 删除器的封装:

from dataclasses import dataclass
from apps.ext.ormx.models.base import Base
from apps.ext.ormx.exceptions import ClientConfigBadException


@dataclass
class Delect(Base):
    pass

    def __columnize(self, columns):
        # 把字典类型的资源化为元祖类型,且元祖的内容元素使用“” 进行包裹处理!
        return tuple(columns).__str__().replace('\'', '"')

    def __valueize(self, data):
        print("data", data)
        # 把字典类型的值进行值使用(,)的方式进行包裹处理,取字典的原始的值,
        # 首先先把所有的值给添加到一个元祖列表里面
        # 然后列表进行串联
        return ','.join([tuple(index.values()).__str__() for index in data])

    def delete(self):
        self.__sql__ = self._compile_delete()
        return self

    def _compile_delete(self):
        if not self.select_table:
            raise ClientConfigBadException(errmsg='请设置需要查询查询的表')
        return 'delete from {}{}{}'.format(self.select_table, self._compile_where(),self._compile_returning())

2.1.6 core.py 数据库连接器.py :

import asyncpg
import traceback
from apps.ext.ormx.exceptions import ClientConfigBadException
from typing import Optional
from dotmap import DotMap
from apps.ext.ormx.models.select import Select
from apps.ext.ormx.models.insert import Insert
from apps.ext.ormx.models.update import Update
from apps.ext.ormx.models.delete import Delect

# DSN = 'postgres://{0}:{1}@{2}:{3}/{4}'.format(db_user, db_pass, db_host, db_port, db_name,)

class ZyxOrm(Select,Insert,Delect,Update):
    def __init__(self, config):
        self.pool: Optional[asyncpg.Pool] = None
        self.config = config

        # 如果配置信息不存在,则抛出异常
        if not self.config:
            raise ClientConfigBadException

    async def connect_pool(self,
                           # 表示URL必须传入
                           # url: str, *,
                           max_queries=50000,
                           # 用来设置一个connection在连接池中的存活时间,默认是1800000,即30分钟。如果设置为0,表示存活时间无限大。如果不等于0且小于30秒则会被重置回30分钟。
                           max_inactive_connection_lifetime=300.0,
                           ):
        try:
            if self.config:
                # 把配置字典转换为 m=DotMap(dict(row1.items()))
                # 创建连接处的对象
                self.pool = await asyncpg.create_pool(min_size=self.config.get('min_size', 5),
                                                      max_size=self.config.get('max_size', 5),
                                                      user=self.config.user,
                                                      host=self.config.host,
                                                      max_queries=max_queries,
                                                      max_inactive_connection_lifetime=max_inactive_connection_lifetime,
                                                      port=self.config.get('port', 5432),
                                                      password=self.config.password,
                                                      database=self.config.database)


            else:
                pass

            return self.pool

        except Exception:
            # 需要手动的捕获异常的异常的信息
            traceback.print_exc()
            pass

    # 在getCurosr方法中是从连接池中重新获取了一个可用的连接。
    async def get_conn(self):
        conn = await self.pool.acquire()
        return conn

    # 释放某个池中某个链接
    async def close_conn(self, conn):
        await self.pool.release(conn)

    # 关闭整个连接池
    async def close_pool(self):
        if self.pool is not None:
            await self.pool.close()
        raise ClientConfigBadException(errmsg='数据库还没连接上!')

    # execute 和execute_many 执行SQL语句,不需要返回记录值!执行效率高,速度快
    async def act_execute(self, *args, sql: str, timeout=None):
        async with self.pool.acquire() as conn:
            await conn.execute(sql, *args, timeout=timeout)

    # execute 和execute_many 执行SQL语句,不需要返回记录值!执行效率高,速度快
    async def act_execute_many(self, *args, sql: str, timeout=None,isopen_transaction=False):
        async with self.pool.acquire() as conn:
            if isopen_transaction:
                async with conn.transaction():
                    await conn.executemany(sql, *args, timeout=timeout)
            else:
                await conn.executemany(sql, *args, timeout=timeout)

    # 下面的有数据返回----这种方式不可以执行多条SQL可以避免SQL注入
    async def fetch_all(self, sql=None,*args, isdict=True,timeout=None,isopen_transaction=False):
        print("SQL:", self.__sql__)
        if not self.__sql__ and sql:
            self.__sql__ = sql

        async with self.pool.acquire() as conn:
            # 调用 await conn.fetch 执行 models 语句,获取满足条件的全部记录
            # 结果返回是:一个 Record 对象列表,这个 Record 对象等于将返回的记录进行了一个封装
            if isopen_transaction:
                async with conn.transaction():
                    rows = await conn.fetch(self.__sql__, *args, timeout=timeout)
            else:
                rows = await conn.fetch(self.__sql__, *args, timeout=timeout)
            # return  list(map(dict, rows))if rows else None
            return (list(map(dict, rows)) if isdict else rows) if rows else None

    # ----这种方式不可以执行多条SQL可以避免SQL注入
    async def fetch_get(self, *args, isdict=True,timeout=None,isopen_transaction=False):
        print("SQL:", self.__sql__)
        if self.__sql__:
            async with self.pool.acquire() as conn:
                #  调用 await conn.fetchrow 执行 models 语句,获取满足条件的单条记录
                # 结果返回是:一个 Record 对象,这个 Record 对象等于将返回的记录进行了一个封装
                if isopen_transaction:
                    async with conn.transaction():
                        row = await conn.fetchrow(self.__sql__, *args, timeout=timeout)
                else:
                    row = await conn.fetchrow(self.__sql__, *args, timeout=timeout)
                return  (dict(row.items())  if isdict else row )if row else None

        # 下面的有数据返回----这种方式不可以执行多条SQL可以避免SQL注入

    async def raw_sql_fetch_all(self, sql,*args, isdict=True, timeout=None,isopen_transaction=False):
        # 连接是从池子里面取出的,上下文结束之后会自动放回到到池子里面
        return await self.fetch_all(sql=sql,timeout=timeout,isopen_transaction=isopen_transaction)

2.2 使用篇:

import asyncio


async def main():

        myorm = ZyxOrm(config=DotMap(min_size=5,
                                    max_size=10,
                                    user='postgres',
                                    host='localhost',
                                    port=5432,
                                    password="123456",
                                    database="zyxadminsystem"))

        await myorm.connect_pool()

        try:

            _restlt = await myorm \
                .tablle('sys_user') \
                .where('id', '=', 1) \
                .where('username', 'like', '%super%') \
                .do_select() \
                .fetch_get()
            print("查询语句-查询全部字段,并返回字典", _restlt)

            _restlt = await myorm \
                .tablle('sys_user') \
                .select('username') \
                .where('id', '=', 1) \
                .where('username', 'like', '%super%') \
                .do_select() \
                .fetch_get()
            print("查询语句-查询某个字典,并返回字典",_restlt)

            _restlt = await myorm \
                .tablle('sys_user') \
                .select('username') \
                .where('id', 'not in', [1, 2]) \
                .do_select() \
                .fetch_all()
            print("查询语句-where的表达式的多种方法:", _restlt)

            _restlt = await myorm \
                .tablle('sys_user_role') \
                .select('sys_user_role.user_id') \
                .select('sys_user.realname') \
                .leftjoin_tuple('sys_user', on=('sys_user_role.user_id', '=', 3)) \
                .leftjoin_tuple('sys_user_depart', on=('sys_user_depart.user_id', 'sys_user.id')) \
                .groupby('sys_user_role.user_id', 'sys_user.realname') \
                .orderby('sys_user.realname', 'desc') \
                .do_select() \
                .fetch_all()
            print("连表查询+分组+排序表达式方法:", _restlt)

            _restlt = await myorm \
                .tablle('sys_user') \
                .select('sys_user_role.user_id') \
                .select('sys_user.*') \
                .leftjoin_tuple(join_tablename='sys_user_role', on=('sys_user_role.user_id', '=', 3)) \
                .leftjoin_tuple(join_tablename='sys_user_depart', on=[('sys_user_role.user_id', '=', 3),('sys_user_depart.user_id', '=', 'sys_user.id')]) \
                .leftjoin_tuple(join_tablename='sys_user_tenant', on=('sys_user_tenant.name = ?', 'sys_user.username')) \
                .do_select() \
                .fetch_all()
            print('连表查询,某个表的全部字段+连表的某个字段方法',_restlt)

            _restlt = await myorm \
                .tablle('sys_user') \
                .select('max(id),max(create_time)') \
                .where('username', 'like', '%admin%') \
                .do_select() \
                .fetch_get()
            print('函数的使用!!', _restlt)

            susadsql = myorm \
                .tablle('sys_user') \
                .select('id') \
                .where('create_time', '>=', '2020-07-08 09:12:37') \
                .do_select(isback___sql__=True)

            print('返回SQL语句:-函数的使用!!', susadsql)

            _restlt = myorm \
                .tablle('sys_user') \
                .select('count(id)') \
                .where('id in ({})'.format(susadsql)) \
                .do_select()
            # 查询结果
            _restlt = await _restlt.fetch_get()
            print('子查询:-函数的使用!!', susadsql)


            # # # 批量插入===仅打印SQL---后续调用相关传入到SQL执行器里面
            _restlt = myorm.tablle('sys_user_role') .create({'user_id':34,'role_id':324})
            _restlt = myorm.tablle('sys_user_role').create([{'user_id': 34, 'role_id': 324},{'user_id': 134, 'role_id': 3324}])

            # print('批量插入2:',resiasda.__sql__)
            # 多个值进插入-------字段列表和值列表
            _restlt =myorm.tablle('sys_user_role').insert(['user_id', 'role_id'], [['35', '4554'], ['56', '56']])
          

            print("删除示例》》》》》" * 5)
            resiasda = myorm.tablle('sys_user_role').where('id',11).delete()
            print('删除', resiasda.__sql__)


            print("更新》》》》》"*5)
            resiasda = myorm.tablle('sys_user').where('id', 1).returning('username as nimie').update({'username': "管理员22222", 'password': '1856555555'})
            print('修改', resiasda.__sql__)


            resiasda = myorm.tablle('sys_user').where('id', 1).decrement('sex', 1)  # 字段自减3
            print('decrement', resiasda.__sql__)
           
            # 乐观锁
            resiasda = myorm \
                .tablle('sys_user') \
                .select('username') \
                .where('id', '=', 1) \
                .where('username', 'like', '%管理员%') \
                .lock_by_for_update()\
                .limit(1)\
                .do_select() \

            print('乐观锁', resiasda.sql())

            print("lmist和偏移量设置!!!")
            # Offset 来表示偏移
            # Limit 限制数量
            resiasda = myorm \
                .tablle('sys_user') \
                .lock_by_for_update() \
                .limit(0) \
                .offset(2)\
                .do_select() \

            print('lmist和偏移量设置', resiasda.sql())

            resiasda = myorm \
                .tablle('sys_user') \
                .paginate(1,1)\
                .do_select()
            print('分页查询', resiasda.sql())
            resiasda = await resiasda.fetch_all()

            print('paginate函数分页查询', resiasda)


        # 数据插入{'user_id':34,'role_id':324}

        except Exception:
            # 需要自己手动的处理捕获错误异常
            traceback.print_exc()
            
if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

总结:

对比其他ORM,其实大体上使用是相同,后期还可以结合我们的pydantic来根据SQL表生成我们的pydantic对应表的模型来做相关字段是数据的校验等。(生成pydantic对应表的模型后期再继续弄~!)

关于使用ORM和非ORM,这种仁者见仁智者见智!同事和业务需求和复杂度也有一定关系,如何权衡全靠你自己!

以上仅仅是个人结合自己的实际需求,做简单的实践笔记!如有笔误!欢迎批评指正!感谢各位大佬!

结尾

简单小笔记!仅供参考!

END

简书:www.jianshu.com/u/d6960089b…

掘金:juejin.cn/user/296393…

公众号:微信搜【小儿来一壶枸杞酒泡茶】

小钟同学 | 文 【原创】【欢迎一起学习交流】| QQ:308711822