Python ORM封装

 

import sys
import asyncio
import logging
logging.basicConfig(level=logging.INFO)
# 一次使用异步 处处使用异步
import aiomysql
 
def log(sql,args=()):
    logging.info('SQL:%s' %sql)
@asyncio.coroutine
def create_pool(loop, **kw): #这里的**kw是一个dict
    logging.info(' start creating database connection pool')
    global __pool
    # 理解这里的yield from 是很重要的
#dict有一个get方法,如果dict中有对应的value值,则返回对应于key的value值,否则返回默认值,例如下面的host,如果dict里面没有
#'host',则返回后面的默认值,也就是'localhost'
#这里有一个关于Pool的连接,讲了一些Pool的知识点,挺不错的,点击打开链接,下面这些参数都会讲到,以及destroy__pool里面的
#wait_closed()
    __pool=yield from aiomysql.create_pool(
        host=kw.get('host','localhost'),  
        port=kw.get('port',3306),    
        user=kw['user'],
        password=kw['password'],
        db=kw['db'],
        charset=kw.get('charset','utf8'),
        autocommit=kw.get('autocommit',True), #默认自动提交事务,不用手动去提交事务
        maxsize=kw.get('maxsize',10),
        minsize=kw.get('minsize',1),
        loop=loop
        )
 
@asyncio.coroutine
def destroy_pool():
    global __pool
    if __pool is not None :
        __pool.close()  #关闭进程池,The method is not a coroutine,就是说close()不是一个协程,所以不用yield from
        yield from __pool.wait_closed() #但是wait_close()是一个协程,所以要用yield from,到底哪些函数是协程,上面Pool的链接中都有
 
# 我很好奇为啥不用commit 事务不用提交么?我觉得是因为上面再创建进程池的时候,有一个参数autocommit=kw.get('autocommit',True)
# 意思是默认会自动提交事务
@asyncio.coroutine
def select(sql, args, size=None):
    log(sql,args)
    global __pool
    # 666 建立游标
    # -*- yield from 将会调用一个子协程,并直接返回调用的结果
    # yield from从连接池中返回一个连接, 这个地方已经创建了进程池并和进程池连接了,进程池的创建被封装到了create_pool(loop, **kw)
    with (yield from __pool) as conn: #使用该语句的前提是已经创建了进程池,因为这句话是在函数定义里面,所以可以这样用
        cur = yield from conn.cursor(aiomysql.DictCursor) #A cursor which returns results as a dictionary.
        yield from cur.execute(sql.replace('?', '%s'), args)
        if size:
            rs = yield from cur.fetchmany(size) #一次性返回size条查询结果,结果是一个list,里面是tuple
        else:
            rs = yield from cur.fetchall()  #一次性返回所有的查询结果
        yield from cur.close() #关闭游标,不用手动关闭conn,因为是在with语句里面,会自动关闭,因为是select,所以不需要提交事务(commit)
        logging.info('rows have returned %s' %len(rs))
    return rs  #返回列表查询结果,里面的元素是tuple
 
 
# 封装INSERT, UPDATE, DELETE
# 语句操作参数一样,所以定义一个通用的执行函数,只是操作参数一样,但是语句的格式不一样
# 返回操作影响的行号
# 我想说的是 知道影响行号有个叼用
 
@asyncio.coroutine
def execute(sql,args, autocommit=True):
    log(sql)
    global __pool
    with (yield from __pool) as conn:
        try:
            # 因为execute类型sql操作返回结果只有行号,不需要dict
            cur = yield from conn.cursor()
            # 顺便说一下 后面的args 别掉了 掉了是无论如何都插入不了数据的
            yield from cur.execute(sql.replace('?', '%s'), args)#把所有的问号替换成占位符
            yield from conn.commit() #这里为什么还要手动提交数据
            affected_line=cur.rowcount
            yield from cur.close()
            print('execute : ', affected_line)
        except BaseException as e:
            raise
        return affected_line
 
# 这个函数主要是把查询字段计数 替换成sql识别的?
# 比如说:insert into  `User` (`password`, `email`, `name`, `id`) values (?,?,?,?)  看到了么 后面这四个问号
def create_args_string(num):
    lol=[]
    for n in range(num):
        lol.append('?')
    return (','.join(lol))
 
# 定义Field类,负责保存(数据库)表的字段名和字段类型
class Field(object):
    # 表的信息包含字段名字、类型、是否为表的主键和默认值
    def __init__(self, name, column_type, primary__key, default):
        self.name = name
        self.column_type=column_type
        self.primary_key=primary__key
        self.default=default
    def __str__(self):
        # 返回 表名字 字段名 和字段类型
        return "<%s , %s , %s>" %(self.__class__.__name__, self.name, self.column_type)
# 定义数据库中五个存储类型
class StringField(Field):
    def __init__(self, name=None, primary_key=False, default=None, ddl='varchar(100)'):
        super().__init__(name,ddl,primary_key,default)
# 布尔类型不可以作为主键
class BooleanField(Field):
    def __init__(self, name=None, default=False):
        super().__init__(name,'Boolean',False, default)
# 不知道这个column type是否可以自己定义 先自己定义看一下
class IntegerField(Field):
    def __init__(self, name=None, primary_key=False, default=0):
        super().__init__(name, 'int', primary_key, default)
class FloatField(Field):
    def __init__(self, name=None, primary_key=False,default=0.0):
        super().__init__(name, 'float', primary_key, default)
class TextField(Field):
    def __init__(self, name=None, default=None):
        super().__init__(name,'text',False, default)
# class Model(dict,metaclass=ModelMetaclass):
 
# -*-定义Model的元类
 
# 所有的元类都继承自type
# ModelMetaclass元类定义了所有Model基类(继承ModelMetaclass)的子类实现的操作
 
# -*-ModelMetaclass的工作主要是为一个数据库表映射成一个封装的类做准备:
# ***读取具体子类(user)的映射信息
# 创造类的时候,排除对Model类的修改
# 在当前类中查找所有的类属性(attrs),如果找到Field属性,就将其保存到__mappings__的dict中,同时从类属性中删除Field(防止实例属性遮住类的同名属性)
# 将数据库表名保存到__table__中
 
# 完成这些工作就可以在Model中定义各种数据库的操作方法
# metaclass是类的模板,所以必须从`type`类型派生:
class ModelMetaclass(type):
    # __new__控制__init__的执行,所以在其执行之前
    # cls:代表要__init__的类,此参数在实例化时由Python解释器自动提供(例如下文的User和Model)
    # name:类名
    # bases:代表继承父类的集合
    # attrs:类的方法及属性的集合
    def __new__(cls, name, bases, attrs):
        # 如果类名是Model,不做修改直接返回.排除model 是因为要排除对model类的修改
        if name=='Model':
            return type.__new__(cls, name, bases, attrs)
        # 获取table名称 为啥获取table名称 至于在哪里我也是不明白握草
        table_name=attrs.get('__table__', None) or name   #r如果存在表名,则返回表名,否则返回 name
        logging.info('found table: %s (table: %s) ' %(name,table_name ))
        # 获取Field所有主键名和Field
        mappings=dict()
        fields=[]  #field保存的是除主键外的属性名
        primaryKey=None
        # 这个k是表示字段名
        for k, v in attrs.items():
            if isinstance(v, Field):#判断v是否是Field的实例
                logging.info('Found mapping %s===>%s' %(k, v))
            # 注意mapping的用法
                mappings[k] = v
                if v.primary_key:
                    logging.info('fond primary key %s'%k)
                    # 这里很有意思 当第一次主键存在primaryKey被赋值 后来如果再出现主键的话就会引发错误
                    if primaryKey:
                        raise RuntimeError('Duplicated key for field') #一个表只能有一个主键,当再出现一个主键的时候就报错
                    primaryKey=k # 也就是说主键只能被设置一次
                else:
                    fields.append(k)
 
        if not primaryKey:  #如果主键不存在也将会报错,在这个表中没有找到主键,一个表只能有一个主键,而且必须有一个主键
            raise RuntimeError('Primary key not found!')
        # w下面位字段从类属性中删除Field 属性
        for k in mappings.keys():
            attrs.pop(k)
        # 保存除主键外的属性为''列表形式
        # 将除主键外的其他属性变成`id`, `name`这种形式,关于反引号``的用法,可以参考点击打开链接
        escaped_fields=list(map(lambda f:'`%s`' %f, fields))
        # 保存属性和列的映射关系
        attrs['__mappings__']=mappings
        # 保存表名
        attrs['__table__']=table_name  #这里的tablename并没有转换成反引号的形式
        # 保存主键名称
        attrs['__primary_key__']=primaryKey
        # 保存主键外的属性名
        attrs['__fields__']=fields
        # 构造默认的增删改查 语句
        attrs['__select__']='select `%s`, %s from `%s` '%(primaryKey,', '.join(escaped_fields), table_name)
        attrs['__insert__'] = 'insert into  `%s` (%s, `%s`) values (%s) ' %(table_name, ', '.join(escaped_fields), primaryKey, create_args_string(len(escaped_fields)+1))
        attrs['__update__']='update `%s` set %s where `%s` = ?' % (table_name, ', '.join(map(lambda f:'`%s`=?' % (mappings.get(f).name or f), fields)), primaryKey)
        attrs['__delete__']='delete from `%s` where `%s`=?' %(table_name, primaryKey)
        return type.__new__(cls, name, bases, attrs)
 
 
# 定义ORM所有映射的基类:Model
# Model类的任意子类可以映射一个数据库表
# Model类可以看作是对所有数据库表操作的基本定义的映射
 
 
# 基于字典查询形式
# Model从dict继承,拥有字典的所有功能,同时实现特殊方法__getattr__和__setattr__,能够实现属性操作
# 实现数据库操作的所有方法,定义为class方法,所有继承自Model都具有数据库操作方法
class Model(dict,metaclass=ModelMetaclass):
    def __init__(self, **kw): #感觉这里去掉__init__的声明也是代码结果是没影响的
        super(Model,self).__init__(**kw)
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError("'Model' object have no attribution: %s"% key)
    def __setattr__(self, key, value):
        self[key] =value
    def getValue(self, key):
        # 这个是默认内置函数实现的,如果没有key则返回None
        return getattr(self, key, None)
 
    def getValueOrDefault(self, key):
        value=getattr(self, key , None)
        if value is None:
            field = self.__mappings__[key]
            if field.default is not None:
                #callable方法用来检测对象是否可被调用,可被调用指的是对象能否使用()括号的方法调用, 如果不可被调用,则value=field.default
                value = field.default() if callable(field.default) else field.default
                logging.info('using default value for %s : %s ' % (key, str(value)))
                setattr(self, key, value)
 
        return value
 
    @classmethod
    # 类方法有类变量cls传入,从而可以用cls做一些相关的处理。并且有子类继承时,调用该类方法时,传入的类变量cls是子类,而非父类。
    @asyncio.coroutine
    def find_all(cls, where=None, args=None, **kw):
        sql = [cls.__select__]
        if where:
            sql.append('where')
            sql.append(where)
        if args is None:
            args = []
 
        orderBy = kw.get('orderBy', None)
        if orderBy:
            sql.append('order by')
            sql.append(orderBy)
        # dict 提供get方法 指定放不存在时候返回后学的东西 比如a.get('Fuck',None)
        limit = kw.get('limit', None)
        if limit is not None:
            sql.append('limit')
            if isinstance(limit, int):
                sql.append('?')
                args.append(limit)
            elif isinstance(limit, tuple) and len(limit) ==2:
                sql.append('?,?')
                args.extend(limit)
            else:
                raise ValueError('Invalid limit value : %s ' % str(limit))
 
        rs = yield from select(' '.join(sql),args) #返回的rs是一个元素是tuple的list
        return [cls(**r) for r in rs]  # **r 是关键字参数,构成了一个cls类的列表,其实就是每一条记录对应的类实例
    
    @classmethod
    @asyncio.coroutine
    def findNumber(cls, selectField, where=None, args=None):
        '''find number by select and where.'''
        sql = ['select %s __num__ from `%s`' %(selectField, cls.__table__)]
        if where:
            sql.append('where')
            sql.append(where)
        rs = yield from select(' '.join(sql), args, 1)
        if len(rs) == 0:
            return None
        return rs[0]['__num__']
 
    @classmethod
    @asyncio.coroutine
    def find(cls, primarykey):
        '''find object by primary key'''
        #rs是一个list,里面是一个dict
        rs = yield from select('%s where `%s`=?' %(cls.__select__, cls.__primary_key__), [primarykey], 1)
        if len(rs) == 0:
            return None
        return cls(**rs[0])  #返回一条记录,以dict的形式返回,因为cls的夫类继承了dict类
 
    @classmethod
    @asyncio.coroutine
    def findAll(cls, **kw):
        rs = []
        if len(kw) == 0:
            rs = yield from select(cls.__select__, None)
        else:
            args=[]
            values=[]
            for k, v in kw.items():
                args.append('%s=?' % k )
                values.append(v)
            print('%s where %s ' % (cls.__select__,  ' and '.join(args)), values)#若不理解请查看join()函数
            rs = yield from select('%s where %s ' % (cls.__select__,  ' and '.join(args)), values)
        return rs
    
    @asyncio.coroutine
    def save(self):
        args = list(map(self.getValueOrDefault, self.__fields__))
        print('save:%s' % args)
        args.append(self.getValueOrDefault(self.__primary_key__))
        rows = yield from execute(self.__insert__, args)
        if rows != 1:
            print(self.__insert__)
            logging.warning('failed to insert record: affected rows: %s' %rows)
 
    @asyncio.coroutine
    def update(self): #修改数据库中已经存入的数据
        args = list(map(self.getValue, self.__fields__))  #获得的value是User2实例的属性值,也就是传入的name,email,password值
        args.append(self.getValue(self.__primary_key__))
        rows = yield from execute(self.__update__, args)
        if rows != 1:
            logging.warning('failed to update record: affected rows: %s'%rows)
 
    @asyncio.coroutine
    def delete(self):
        args = [self.getValue(self.__primary_key__)]
        rows = yield from execute(self.__delete__, args)
        if rows != 1:
            logging.warning('failed to delete by primary key: affected rows: %s' %rows)
 
 
if __name__=="__main__":#一个类自带前后都有双下划线的方法,在子类继承该类的时候,这些方法会自动调用,比如__init__
    class User2(Model): #虽然User类乍看没有参数传入,但实际上,User类继承Model类,Model类又继承dict类,所以User类的实例可以传入关键字参数
        id = IntegerField('id',primary_key=True) #主键为id, tablename为User,即类名
        name = StringField('name')
        email = StringField('email')
        password = StringField('password')
    #创建异步事件的句柄
    loop = asyncio.get_event_loop()
 
    #创建实例
    @asyncio.coroutine
    def test():
        yield from create_pool(loop=loop, host='localhost', port=3306, user='root', password='Limin123?', db='test')
        #user = User2(id=2, name='Tom', email='slysly759@gmail.com', password='12345')
        r = yield from User2.findAll()
        print(r)
        #yield from user.save()
        #ield from user.update()
        #yield from user.delete()
        # r = yield from User2.find(8)
        # print(r)
        # r = yield from User2.findAll()
        # print(1, r)
        # r = yield from User2.findAll(name='sly')
        # print(2, r)
        yield from destroy_pool()  #关闭pool
 
    loop.run_until_complete(test())
    loop.close()
    if loop.is_closed():
        sys.exit(0)

 

拆解

class Field(object):
    def __init__(self,name,column_type,primary_key,default):
        self.name = name
        self.column_type = column_type
        self.primary_key = primary_key
        self.default = default
        #print(self,name,column_type,primary_key,default)
    def __str__(self):
        return '<%s,%s,%s>' %(self.__class__.__name__,self.column_type,self.name)
class StringField(Field):
    def __init__(self,name=None,primary_key=False,default=None,ddl='varchar(100)'):
        super().__init__(name,ddl,primary_key,default)
        #print(self,name,ddl,primary_key,default)
        
def create_args_string(num):
    lol=[]
    for n in range(num):
        lol.append('?')
    return (','.join(lol))

# 元类,创建一个新的类,然后返回
class ModelMetaclass(type):
    def __new__(cls,name,bases,attrs):
        #print(bases,'\n')
        if name == 'Model':
            return type.__new__(cls,name,bases,attrs)
        tableName = attrs.get('__table__',None) or name
        mappings = dict()
        fields = []
        primaryKey = None
        #print(attrs)
        for k,v in attrs.items():
            if isinstance(v,Field):
                mappings[k] = v
                if v.primary_key:
                    if primaryKey:
                        raise RuntimeError('key aready exist')
                    primaryKey = k
                else:
                    fields.append(k)
        #print(fields)
        print(mappings)
        if not primaryKey:
            raise RuntimeError('Primary key not found!')
        for k in mappings.keys():
            attrs.pop(k)
        escaped_fields = list(map(lambda f: '`%s`' % f, fields))
        print(escaped_fields,fields)
        attrs['__mappings__'] = mappings # 保存属性和列的映射关系
        attrs['__table__'] = tableName
        attrs['__primary_key__']=primaryKey
        attrs['__fields__']=fields
        attrs['__select__']='select `%s`, %s from `%s`' %(primaryKey,', '.join(escaped_fields),tableName)
        attrs['__insert__']='insert into `%s` (%s) values (%s)' %(tableName,', '.join(escaped_fields),create_args_string(len(escaped_fields)))
        attrs['__update__']='update `%s` set %s where `%s` = ?' % (tableName, ', '.join(map(lambda f:'`%s`=?' % (mappings.get(f).name or f), fields)), primaryKey)

        print(mappings.get('name').default)
        return type.__new__(cls, name, bases, attrs)

                
class Model(dict,metaclass=ModelMetaclass):
    __metaclass__ = ModelMetaclass
    def __init__(self,**kw):
        super(Model,self).__init__(**kw)
        #super().__init__(**kw)
    def __getattr__(self,key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError("'%s' instatnce has no attribute '%s'" %(self.__class__.__name__,key))
    def __setattr__(self,key,value):
        self[key] = value
    def getValue(self,key):
        return getattr(self,key,None)
    def getValueOrDefault(self, key):
        value = getattr(self,key,None)
        if value is None:
            field = self.__mappings__[key]
            if field.default is not None:
                value = field.default() if callable(field.default) else field.default
                setattr(self, key, value)
        return value
    @classmethod
    def filter(cls,where='',**kwargs):
        sql = 'select * from %s %s' %(cls.__table__,'where %s' %where if where else '')
        print(sql)
    def findAll(cls, **kw):
        rs = []
        if len(kw) == 0:
            #rs = yield from select(cls.__select__, None)
            print(kw)
        else:
            args=[]
            values=[]
            for k, v in kw.items():
                #args.append('%s=?' % k )
                args.append('%s=%s' % (k,v) )
                values.append(v)
            #print(args, values)
            print('%s where %s ' % (cls.__select__,  ' and '.join(args)))
            print('%s where %s ' % (cls.__select__,  ' and '.join(args)), values)
            #rs = yield from select('%s where %s ' % (cls.__select__,  ' and '.join(args)), values)
            rs = '%s where %s ' % (cls.__select__,  ' and '.join(args))
        return rs
    def save(self):
        args = list(map(self.getValueOrDefault,self.__fields__))
        print(self.__fields__)
        print('save:%s' % args)
        #args.append(self.getValueOrDefault(self.__primary_key__))
        sql = self.__insert__
        print(sql.replace('?','%s'),args)
        
if __name__ == '__main__':
    class User(Model):
        __table__ = 'users'
        __database__ = 'test3'
        id = StringField('id',primary_key=True,ddl='varchar(50)')
        passwd = StringField('passwd',ddl='varchar(50)',default='pwd123')
        name = StringField('name',ddl='varchar(50)',default='lin')
        
    u = User(passwd='123',name='linyouyi')
    #u.filter(where='name="lin" passwd=123')
    #u.findAll(name='lin',passwd='123')
    u.save()

 

posted @ 2019-09-06 16:12  linyouyi  阅读(511)  评论(0编辑  收藏  举报