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: ...
随机推荐
- C#语法小用法
数据在存为数据库之前,用JS的encodeURIComponent进行编码,现需要在后台代码中进行解码,实现decodeURIComponent的功能, 如下: HttpUtility.UrlDeco ...
- qt 提高ui响应度
如何使Qt 平台中的GUI保持响应流畅?一般来说耗时较长的操作,分为计算密集型操作和IO密集型操作,对于这两类操作如何提高响应速度. 而从操作的本质上来说,操作又可分为不可分解操作,如在第三方库中耗时 ...
- 从原理上搞定编码(四)-- Base64编码
开发者对Base64编码肯定很熟悉,是否对它有很清晰的认识就不一定了.实际上Base64已经简单到不能再简单了,如果对它的理解还是模棱两可实在不应该.大概介绍一下Base64的相关内容,花几分钟时间就 ...
- java调用c++生成的动态和静态库时遇到的问题
java.lang.UnsatisfiedLinkError: no jacob in java.library.path -Djava.library.path 关于java用jni调用 dll动态 ...
- 酷狗音乐盒缓存文件夹KuGouCache的设置方法
1.每次一打开酷狗总能在E盘里找到这个 KuGouCache 文件夹 ,是自动生成的MV缓存文件 .按照常规 ,可以修改这个文件的办法是 找到C盘里的:用户\administrator\AppDate ...
- HDU 2255 二分图最佳匹配 模板题
题目大意: 给定每一个人能支付的房子价值,每个人最多且必须拥有一套房子,问最后分配房子可得到的最大收益 抄了个别人的KM模板,就这样了... #include <cstdio> #incl ...
- HDU 4087 三维上的平移缩放旋转矩阵变化
题目大意: 就是根据它给的程序的要求,不断平移,缩放,旋转三维的点,最后计算出点的位置 这里主要是要列出三种转换方式的齐次矩阵描述 平移translate tx ty tz1 0 0 00 1 0 0 ...
- 框架之 spring
spring有两大特性,其一为ioc,其二为aop 1.ioc的理解 ioc为依赖注入,他的好处就是把创建对象的权利交给spring去管理,这样的好处是 将应用程序中的对象解耦,传统的方式程序中的对象 ...
- Javascript 基础--JS函数(三)
一.基本概念:未完成某一个功能的代码(语句,指令)的集合. 二.函数的调用方式: 2.1.函数名(传递参数1,传递参数2) 基本语法 function 函数名(参数列表){ //代码; retur ...
- treap模版代码
treap模版暂存. 以后修改整理. #include<cstdio> #include<iostream> #include <time.h> #include& ...