Python3 ORM hacking
- #!/usr/bin/env python3
- # -*- coding: utf- -*-
- #
- # Python3 ORM hacking
- # 说明:
- # 之前分析了一个Python2 ORM的源代码,这次分析一个Python3的源代码,在写法上
- # 还是又挺大的区别的。
# 2016-10-22 深圳 南山平山村 曾剑锋- #
- # 源码:
- # https://github.com/michaelliao/awesome-python3-webapp/tree/day-03
- #
- # 参考文章:
- # . python logging模块使用教程
- # http://www.jianshu.com/p/feb86c06c4f4
- # . Python async/await入门
- # https://ipfans.github.io/2015/08/introduction-to-async-and-await/
- # . 浅析python的metaclass
- # http://jianpx.iteye.com/blog/908121
- # . Why I got ignored exception when I use aiomysql in python 3.5 #
- # https://github.com/aio-libs/aiomysql/issues/59
- #
- __author__ = 'Michael Liao'
- import asyncio, logging
- import aiomysql
- # SQL日志打印输出模板
- def log(sql, args=()):
- logging.info('SQL: %s' % sql)
- # 创建数据库连接池
- async def create_pool(loop, **kw):
- logging.info('create database connection pool...')
- # 标记__pool为文件内全局变量,在其他函数内可以直接访问
- global __pool
- __pool = await aiomysql.create_pool(
- host=kw.get('host', 'localhost'),
- port=kw.get('port', ),
- user=kw['user'],
- password=kw['password'],
- db=kw['db'],
- charset=kw.get('charset', 'utf8'),
- autocommit=kw.get('autocommit', True),
- maxsize=kw.get('maxsize', ),
- minsize=kw.get('minsize', ),
- loop=loop
- )
- # 数据库查询
- async def select(sql, args, size=None):
- # 输出SQL日志信息
- log(sql, args)
- global __pool
- # 从连接池中获取连接,aysnc是异步获取连接
- async with __pool.get() as conn:
- async with conn.cursor(aiomysql.DictCursor) as cur:
- # 合成实际的SQL
- await cur.execute(sql.replace('?', '%s'), args or ())
- # 根据size来获取数据多少行记录
- if size:
- rs = await cur.fetchmany(size)
- else:
- rs = await cur.fetchall()
- # 给出获取到的信息条数
- logging.info('rows returned: %s' % len(rs))
- return rs
- # 数据库直接执行SQL
- async def execute(sql, args, autocommit=True):
- log(sql)
- async with __pool.get() as conn:
- # 如果不是自动提交
- if not autocommit:
- await conn.begin()
- try:
- async with conn.cursor(aiomysql.DictCursor) as cur:
- await cur.execute(sql.replace('?', '%s'), args)
- # 返回的执行SQL后有效行数,从代码上可以看出,这部分主要是执行更新、插入、删除等SQL语句
- affected = cur.rowcount
- # 完成提交工作
- if not autocommit:
- await conn.commit()
- except BaseException as e:
- # 出现问题,回滚
- if not autocommit:
- await conn.rollback()
- raise # 直接再次抛出异常
- return affected # 返回有效行数
- # 合成可替代参数字符串,先使用'?'代替'%s'
- def create_args_string(num):
- L = []
- for n in range(num):
- L.append('?')
- return ', '.join(L)
- # 对应数据库中每一个字段的一个域的基类
- 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
- # 重写默认输出的str函数
- 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)
- # Boolean类型的域
- class BooleanField(Field):
- def __init__(self, name=None, default=False):
- super().__init__(name, 'boolean', False, default)
- # 整形类型的域
- class IntegerField(Field):
- def __init__(self, name=None, primary_key=False, default=):
- super().__init__(name, 'bigint', primary_key, default)
- # 浮点类型的域
- class FloatField(Field):
- def __init__(self, name=None, primary_key=False, default=0.0):
- super().__init__(name, 'real', primary_key, default)
- # 文本类型的域
- class TextField(Field):
- def __init__(self, name=None, default=None):
- super().__init__(name, 'text', False, default)
- # MVC中的Model的元类,主要用于自动生成映射(map)类
- class ModelMetaclass(type):
- # name: 类的名字
- # bases: 基类,通常是tuple类型
- # attrs: dict类型,就是类的属性或者函数
- def __new__(cls, name, bases, attrs):
- # 过滤掉Model类直接生成的实例类
- if name=='Model':
- return type.__new__(cls, name, bases, attrs)
- # 从类的属性中获取__table__,其实也就是于数据库对应的表名,如果不存在那么就是等于类名
- tableName = attrs.get('__table__', None) or name
- logging.info('found model: %s (table: %s)' % (name, tableName))
- # 创建映射字典
- mappings = dict()
- # 域list
- fields = []
- # 主键标记
- primaryKey = None
- # 获取类中的所有的键值对
- for k, v in attrs.items():
- # 选择Field类型实例的属性作为映射键值
- if isinstance(v, Field):
- logging.info(' found mapping: %s ==> %s' % (k, v))
- # 将当前的键值对放入mapping中
- mappings[k] = v
- if v.primary_key:
- # 防止出现两个、两个以上的主键
- if primaryKey:
- raise StandardError('Duplicate primary key for field: %s' % k)
- primaryKey = k
- else:
- # 将key添加进入fields中,也就是映射类中的属性和数据库中的表的域, 这里面不包含主键
- fields.append(k)
- # 前面可能没有找到主键,提示一下
- if not primaryKey:
- raise StandardError('Primary key not found.')
- # 删除这些类属性 防止访问实例属性的时候发生错误,因为实例属性优先级大于类属性
- for k in mappings.keys():
- attrs.pop(k)
- escaped_fields = list(map(lambda f: '`%s`' % f, fields))
- attrs['__mappings__'] = mappings # 保存属性和列的映射关系
- attrs['__table__'] = tableName # 表名
- attrs['__primary_key__'] = primaryKey # 主键属性名
- attrs['__fields__'] = fields # 除主键外的属性名
- # 生成查询SQL
- attrs['__select__'] = 'select `%s`, %s from `%s`' % (primaryKey, ', '.join(escaped_fields), tableName)
- # 生成插入SQL
- attrs['__insert__'] = 'insert into `%s` (%s, `%s`) values (%s)' % (tableName, ', '.join(escaped_fields), primaryKey, create_args_string(len(escaped_fields) + ))
- # 生成更新SQL
- attrs['__update__'] = 'update `%s` set %s where `%s`=?' % (tableName, ', '.join(map(lambda f: '`%s`=?' % (mappings.get(f).name or f), fields)), primaryKey)
- # 生成删除SQL
- attrs['__delete__'] = 'delete from `%s` where `%s`=?' % (tableName, primaryKey)
- # 调用type生成类
- return type.__new__(cls, name, bases, attrs)
- # 继承自ModelMetaclass元类、dict的类
- class Model(dict, metaclass=ModelMetaclass):
- def __init__(self, **kw):
- super(Model, self).__init__(**kw)
- # 重写get方法
- def __getattr__(self, key):
- try:
- return self[key]
- except KeyError:
- raise AttributeError(r"'Model' object has no attribute '%s'" % key)
- # 重写set方法
- def __setattr__(self, key, value):
- self[key] = value
- # 重写get方法
- 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
- logging.debug('using default value for %s: %s' % (key, str(value)))
- setattr(self, key, value)
- return value
- @classmethod
- async def findAll(cls, where=None, args=None, **kw):
- ' find objects by where clause. '
- # 获取元类自动生成的SQL语句,并根据当前的参数,继续合成
- 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)
- 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) == :
- sql.append('?, ?')
- args.extend(limit)
- else:
- raise ValueError('Invalid limit value: %s' % str(limit))
- # 直接调用select函数来处理, 这里是等待函数执行完成函数才能返回
- rs = await select(' '.join(sql), args)
- # 该类本身是字典,自己用自己生成新的实例,里面的阈值正好也是需要查询
- return [cls(**r) for r in rs]
- @classmethod
- async def findNumber(cls, selectField, where=None, args=None):
- ' find number by select and where. '
- # 这里的 _num_ 什么意思?别名? 我估计是mysql里面一个记录实时查询结果条数的变量
- sql = ['select %s _num_ from `%s`' % (selectField, cls.__table__)]
- if where:
- sql.append('where')
- sql.append(where)
- rs = await select(' '.join(sql), args, )
- if len(rs) == :
- return None
- return rs[]['_num_']
- @classmethod
- async def find(cls, pk):
- ' find object by primary key. '
- # 通过主键查找对象, 如果不存在,那么就返回None
- rs = await select('%s where `%s`=?' % (cls.__select__, cls.__primary_key__), [pk], )
- if len(rs) == :
- return None
- return cls(**rs[])
- # 插入语句对应的方法
- async def save(self):
- args = list(map(self.getValueOrDefault, self.__fields__))
- args.append(self.getValueOrDefault(self.__primary_key__))
- rows = await execute(self.__insert__, args)
- if rows != :
- logging.warn('failed to insert record: affected rows: %s' % rows)
- # 更新语句对应的方法
- async def update(self):
- args = list(map(self.getValue, self.__fields__))
- args.append(self.getValue(self.__primary_key__))
- rows = await execute(self.__update__, args)
- if rows != :
- logging.warn('failed to update by primary key: affected rows: %s' % rows)
- # 删除语句对应的方法
- async def remove(self):
- args = [self.getValue(self.__primary_key__)]
- rows = await execute(self.__delete__, args)
- if rows != :
- logging.warn('failed to remove by primary key: affected rows: %s' % rows)
Python3 ORM hacking的更多相关文章
- Python MySQL ORM QuickORM hacking
# coding: utf-8 # # Python MySQL ORM QuickORM hacking # 说明: # 以前仅仅是知道有ORM的存在,但是对ORM这个东西内部工作原理不是很清楚, ...
- Python3+SQLAlchemy+Sqlite3实现ORM教程
一.安装 Sqlite3是Python3标准库不需要另外安装,只需要安装SQLAlchemy即可.本文sqlalchemy版本为1.2.12 pip install sqlalchemy 二.ORM操 ...
- python3开发进阶-Django框架的起飞加速一(ORM)
阅读目录 ORM介绍 Django中的ORM ORM中的Model ORM的操作 一.ORM介绍 1.ORM概念 对象关系映射(Object Relational Mapping,简称ORM)模式是一 ...
- python3开发进阶-Django框架的ORM常用字段和参数
阅读目录 常用字段 字段合集 自定义字段 字段参数 关系参数 多对多的关联关系的三种方式 一.常用字段 AutoField int自增列,必须填入参数 primary_key=True.当model中 ...
- python3开发进阶-Django框架中的ORM的常用(增,删,改,查)操作
阅读目录 如何在Django终端打印SQL语句 如何在Python脚本中调用Django环境 操作方法 单表查询之神奇的下划线 ForeignKey操作 ManyToManyField 聚合查询和分组 ...
- python3 + flask + sqlalchemy +orm(3):多对多关系
一篇文章有多个tag,一个tag也可以属于多篇文章,文章和tag存在多对多关系 config.py DEBUG = True #dialect+driver://root:1q2w3e4r5t@127 ...
- python3 + flask + sqlalchemy +orm(2):数据库中添加表
往数据库中添加一张保存文章的表,表明为article,字段有id,title,content 同样一个配置文件:config.py DEBUG = True #dialect+driver://roo ...
- python3 + flask + sqlalchemy +orm(1):链接mysql 数据库
1.pycharm中新建一个flask项目 2.按装flask.PyMySQL.flask-sqlalchemy 3.项目下面新建一个config.py 文件 DEBUG = True #dialec ...
- Python3 sqlacodegen 根据已有数据库生成 ORM 使用的 model.py
pip install sqlacodegen pip install pymysql sqlacodegen mysql+pymysql://username:password@127.0.0.1: ...
随机推荐
- SQL Server之字符串函数
以下所有例子均Studnet表为例: 计算字符串长度len()用来计算字符串的长度 select sname ,len(sname) from student 字符串转换为大.小写lower() ...
- gitlab配置邮件通知
配置用户提交评论.添加issue等的邮件通知: Gitlab邮件提醒方便跟踪项目进度,在这里介绍两种方式,一种是用系统的sendmail发送邮件,另一种是GMAIL的stmp来发送邮件 第一种 用系统 ...
- MongoDB相关资料
MongoDB的介绍及安装参考http://www.cnblogs.com/lipan/archive/2011/03/08/1966463.html 安装过程: 第一步:下载安装包:官方下载地址←单 ...
- 如何在datagridview 的head上绘制一个全选按钮
winform的项目中,经常要用到datagridview控件,但是为控件添加DataGridViewCheckBoxColumn来实现数据行选择这个功能的时候,经常需要提供全选反选功能,如果不重绘控 ...
- qml的打包问题
qml2的打包问题: 相对于早期的项目,只需要打包plugin和动态库.带有sqlite的程序如果需要打包,需要打包如下东西: 1.打包AppData目录下的 Local/Qt Project/项目 ...
- linux-虚拟机安装
第一步:下载 安装虚拟机! 链接: http://pan.baidu.com/s/1nuGLwsL 密码: 2qdy 第二步:镜像文件! 链接: http://pan.baidu.com/s/1nuG ...
- [转] jQuery源码分析-如何做jQuery源码分析
jQuery源码分析系列(持续更新) jQuery的源码有些晦涩难懂,本文分享一些我看源码的方法,每一个模块我基本按照这样的顺序去学习. 当我读到难度的书或者源码时,会和<如何阅读一本书> ...
- CentOS linux 下eclipse+cdt编译报undefined reffrece to *
- zabbix邮件报警
#!/usr/bin/python #coding:utf-8 import smtplib from email.mime.text import MIMEText import sys mail_ ...
- java接收键盘输入
System.out.print("Please input String to check:");//提示输入 Scanner sc=new Scanner(System.in) ...