1. #!/usr/bin/env python3
  2. # -*- coding: utf- -*-
  3.  
  4. #
  5. # Python3 ORM hacking
  6. # 说明:
  7. # 之前分析了一个Python2 ORM的源代码,这次分析一个Python3的源代码,在写法上
  8. # 还是又挺大的区别的。
    # 2016-10-22 深圳 南山平山村 曾剑锋
  9. #
  10. # 源码:
  11. # https://github.com/michaelliao/awesome-python3-webapp/tree/day-03
  12. #
  13. # 参考文章:
  14. # . python logging模块使用教程
  15. # http://www.jianshu.com/p/feb86c06c4f4
  16. # . Python async/await入门
  17. # https://ipfans.github.io/2015/08/introduction-to-async-and-await/
  18. # . 浅析python的metaclass
  19. # http://jianpx.iteye.com/blog/908121
  20. # . Why I got ignored exception when I use aiomysql in python 3.5 #
  21. # https://github.com/aio-libs/aiomysql/issues/59
  22. #
  23.  
  24. __author__ = 'Michael Liao'
  25.  
  26. import asyncio, logging
  27.  
  28. import aiomysql
  29.  
  30. # SQL日志打印输出模板
  31. def log(sql, args=()):
  32. logging.info('SQL: %s' % sql)
  33.  
  34. # 创建数据库连接池
  35. async def create_pool(loop, **kw):
  36. logging.info('create database connection pool...')
  37. # 标记__pool为文件内全局变量,在其他函数内可以直接访问
  38. global __pool
  39. __pool = await aiomysql.create_pool(
  40. host=kw.get('host', 'localhost'),
  41. port=kw.get('port', ),
  42. user=kw['user'],
  43. password=kw['password'],
  44. db=kw['db'],
  45. charset=kw.get('charset', 'utf8'),
  46. autocommit=kw.get('autocommit', True),
  47. maxsize=kw.get('maxsize', ),
  48. minsize=kw.get('minsize', ),
  49. loop=loop
  50. )
  51.  
  52. # 数据库查询
  53. async def select(sql, args, size=None):
  54. # 输出SQL日志信息
  55. log(sql, args)
  56. global __pool
  57. # 从连接池中获取连接,aysnc是异步获取连接
  58. async with __pool.get() as conn:
  59. async with conn.cursor(aiomysql.DictCursor) as cur:
  60. # 合成实际的SQL
  61. await cur.execute(sql.replace('?', '%s'), args or ())
  62. # 根据size来获取数据多少行记录
  63. if size:
  64. rs = await cur.fetchmany(size)
  65. else:
  66. rs = await cur.fetchall()
  67. # 给出获取到的信息条数
  68. logging.info('rows returned: %s' % len(rs))
  69. return rs
  70.  
  71. # 数据库直接执行SQL
  72. async def execute(sql, args, autocommit=True):
  73. log(sql)
  74. async with __pool.get() as conn:
  75. # 如果不是自动提交
  76. if not autocommit:
  77. await conn.begin()
  78. try:
  79. async with conn.cursor(aiomysql.DictCursor) as cur:
  80. await cur.execute(sql.replace('?', '%s'), args)
  81. # 返回的执行SQL后有效行数,从代码上可以看出,这部分主要是执行更新、插入、删除等SQL语句
  82. affected = cur.rowcount
  83. # 完成提交工作
  84. if not autocommit:
  85. await conn.commit()
  86. except BaseException as e:
  87. # 出现问题,回滚
  88. if not autocommit:
  89. await conn.rollback()
  90. raise # 直接再次抛出异常
  91. return affected # 返回有效行数
  92.  
  93. # 合成可替代参数字符串,先使用'?'代替'%s'
  94. def create_args_string(num):
  95. L = []
  96. for n in range(num):
  97. L.append('?')
  98. return ', '.join(L)
  99.  
  100. # 对应数据库中每一个字段的一个域的基类
  101. class Field(object):
  102.  
  103. # 域名、域类型、是否是主键、默认值
  104. def __init__(self, name, column_type, primary_key, default):
  105. self.name = name
  106. self.column_type = column_type
  107. self.primary_key = primary_key
  108. self.default = default
  109.  
  110. # 重写默认输出的str函数
  111. def __str__(self):
  112. return '<%s, %s:%s>' % (self.__class__.__name__, self.column_type, self.name)
  113.  
  114. # 字符串类型的域
  115. class StringField(Field):
  116.  
  117. def __init__(self, name=None, primary_key=False, default=None, ddl='varchar(100)'):
  118. super().__init__(name, ddl, primary_key, default)
  119.  
  120. # Boolean类型的域
  121. class BooleanField(Field):
  122.  
  123. def __init__(self, name=None, default=False):
  124. super().__init__(name, 'boolean', False, default)
  125.  
  126. # 整形类型的域
  127. class IntegerField(Field):
  128.  
  129. def __init__(self, name=None, primary_key=False, default=):
  130. super().__init__(name, 'bigint', primary_key, default)
  131.  
  132. # 浮点类型的域
  133. class FloatField(Field):
  134.  
  135. def __init__(self, name=None, primary_key=False, default=0.0):
  136. super().__init__(name, 'real', primary_key, default)
  137.  
  138. # 文本类型的域
  139. class TextField(Field):
  140.  
  141. def __init__(self, name=None, default=None):
  142. super().__init__(name, 'text', False, default)
  143.  
  144. # MVC中的Model的元类,主要用于自动生成映射(map)类
  145. class ModelMetaclass(type):
  146.  
  147. # name: 类的名字
  148. # bases: 基类,通常是tuple类型
  149. # attrs: dict类型,就是类的属性或者函数
  150. def __new__(cls, name, bases, attrs):
  151. # 过滤掉Model类直接生成的实例类
  152. if name=='Model':
  153. return type.__new__(cls, name, bases, attrs)
  154. # 从类的属性中获取__table__,其实也就是于数据库对应的表名,如果不存在那么就是等于类名
  155. tableName = attrs.get('__table__', None) or name
  156. logging.info('found model: %s (table: %s)' % (name, tableName))
  157. # 创建映射字典
  158. mappings = dict()
  159. # 域list
  160. fields = []
  161. # 主键标记
  162. primaryKey = None
  163. # 获取类中的所有的键值对
  164. for k, v in attrs.items():
  165. # 选择Field类型实例的属性作为映射键值
  166. if isinstance(v, Field):
  167. logging.info(' found mapping: %s ==> %s' % (k, v))
  168. # 将当前的键值对放入mapping中
  169. mappings[k] = v
  170. if v.primary_key:
  171. # 防止出现两个、两个以上的主键
  172. if primaryKey:
  173. raise StandardError('Duplicate primary key for field: %s' % k)
  174. primaryKey = k
  175. else:
  176. # 将key添加进入fields中,也就是映射类中的属性和数据库中的表的域, 这里面不包含主键
  177. fields.append(k)
  178.  
  179. # 前面可能没有找到主键,提示一下
  180. if not primaryKey:
  181. raise StandardError('Primary key not found.')
  182. # 删除这些类属性 防止访问实例属性的时候发生错误,因为实例属性优先级大于类属性
  183. for k in mappings.keys():
  184. attrs.pop(k)
  185.  
  186. escaped_fields = list(map(lambda f: '`%s`' % f, fields))
  187.  
  188. attrs['__mappings__'] = mappings # 保存属性和列的映射关系
  189. attrs['__table__'] = tableName # 表名
  190. attrs['__primary_key__'] = primaryKey # 主键属性名
  191. attrs['__fields__'] = fields # 除主键外的属性名
  192.  
  193. # 生成查询SQL
  194. attrs['__select__'] = 'select `%s`, %s from `%s`' % (primaryKey, ', '.join(escaped_fields), tableName)
  195. # 生成插入SQL
  196. attrs['__insert__'] = 'insert into `%s` (%s, `%s`) values (%s)' % (tableName, ', '.join(escaped_fields), primaryKey, create_args_string(len(escaped_fields) + ))
  197. # 生成更新SQL
  198. attrs['__update__'] = 'update `%s` set %s where `%s`=?' % (tableName, ', '.join(map(lambda f: '`%s`=?' % (mappings.get(f).name or f), fields)), primaryKey)
  199. # 生成删除SQL
  200. attrs['__delete__'] = 'delete from `%s` where `%s`=?' % (tableName, primaryKey)
  201.  
  202. # 调用type生成类
  203. return type.__new__(cls, name, bases, attrs)
  204.  
  205. # 继承自ModelMetaclass元类、dict的类
  206. class Model(dict, metaclass=ModelMetaclass):
  207.  
  208. def __init__(self, **kw):
  209. super(Model, self).__init__(**kw)
  210.  
  211. # 重写get方法
  212. def __getattr__(self, key):
  213. try:
  214. return self[key]
  215. except KeyError:
  216. raise AttributeError(r"'Model' object has no attribute '%s'" % key)
  217.  
  218. # 重写set方法
  219. def __setattr__(self, key, value):
  220. self[key] = value
  221.  
  222. # 重写get方法
  223. def getValue(self, key):
  224. return getattr(self, key, None)
  225.  
  226. # 获取值,当不存在的时候获取的是默认值
  227. def getValueOrDefault(self, key):
  228. value = getattr(self, key, None)
  229. if value is None:
  230. field = self.__mappings__[key]
  231. if field.default is not None:
  232. value = field.default() if callable(field.default) else field.default
  233. logging.debug('using default value for %s: %s' % (key, str(value)))
  234. setattr(self, key, value)
  235. return value
  236.  
  237. @classmethod
  238. async def findAll(cls, where=None, args=None, **kw):
  239. ' find objects by where clause. '
  240. # 获取元类自动生成的SQL语句,并根据当前的参数,继续合成
  241. sql = [cls.__select__]
  242. if where:
  243. sql.append('where')
  244. sql.append(where)
  245. if args is None:
  246. args = []
  247. orderBy = kw.get('orderBy', None)
  248. if orderBy:
  249. sql.append('order by')
  250. sql.append(orderBy)
  251. limit = kw.get('limit', None)
  252. if limit is not None:
  253. sql.append('limit')
  254. if isinstance(limit, int):
  255. sql.append('?')
  256. args.append(limit)
  257. elif isinstance(limit, tuple) and len(limit) == :
  258. sql.append('?, ?')
  259. args.extend(limit)
  260. else:
  261. raise ValueError('Invalid limit value: %s' % str(limit))
  262. # 直接调用select函数来处理, 这里是等待函数执行完成函数才能返回
  263. rs = await select(' '.join(sql), args)
  264. # 该类本身是字典,自己用自己生成新的实例,里面的阈值正好也是需要查询
  265. return [cls(**r) for r in rs]
  266.  
  267. @classmethod
  268. async def findNumber(cls, selectField, where=None, args=None):
  269. ' find number by select and where. '
  270. # 这里的 _num_ 什么意思?别名? 我估计是mysql里面一个记录实时查询结果条数的变量
  271. sql = ['select %s _num_ from `%s`' % (selectField, cls.__table__)]
  272. if where:
  273. sql.append('where')
  274. sql.append(where)
  275. rs = await select(' '.join(sql), args, )
  276. if len(rs) == :
  277. return None
  278. return rs[]['_num_']
  279.  
  280. @classmethod
  281. async def find(cls, pk):
  282. ' find object by primary key. '
  283. # 通过主键查找对象, 如果不存在,那么就返回None
  284. rs = await select('%s where `%s`=?' % (cls.__select__, cls.__primary_key__), [pk], )
  285. if len(rs) == :
  286. return None
  287. return cls(**rs[])
  288.  
  289. # 插入语句对应的方法
  290. async def save(self):
  291. args = list(map(self.getValueOrDefault, self.__fields__))
  292. args.append(self.getValueOrDefault(self.__primary_key__))
  293. rows = await execute(self.__insert__, args)
  294. if rows != :
  295. logging.warn('failed to insert record: affected rows: %s' % rows)
  296.  
  297. # 更新语句对应的方法
  298. async def update(self):
  299. args = list(map(self.getValue, self.__fields__))
  300. args.append(self.getValue(self.__primary_key__))
  301. rows = await execute(self.__update__, args)
  302. if rows != :
  303. logging.warn('failed to update by primary key: affected rows: %s' % rows)
  304.  
  305. # 删除语句对应的方法
  306. async def remove(self):
  307. args = [self.getValue(self.__primary_key__)]
  308. rows = await execute(self.__delete__, args)
  309. if rows != :
  310. logging.warn('failed to remove by primary key: affected rows: %s' % rows)

Python3 ORM hacking的更多相关文章

  1. Python MySQL ORM QuickORM hacking

    # coding: utf-8 # # Python MySQL ORM QuickORM hacking # 说明: # 以前仅仅是知道有ORM的存在,但是对ORM这个东西内部工作原理不是很清楚, ...

  2. Python3+SQLAlchemy+Sqlite3实现ORM教程

    一.安装 Sqlite3是Python3标准库不需要另外安装,只需要安装SQLAlchemy即可.本文sqlalchemy版本为1.2.12 pip install sqlalchemy 二.ORM操 ...

  3. python3开发进阶-Django框架的起飞加速一(ORM)

    阅读目录 ORM介绍 Django中的ORM ORM中的Model ORM的操作 一.ORM介绍 1.ORM概念 对象关系映射(Object Relational Mapping,简称ORM)模式是一 ...

  4. python3开发进阶-Django框架的ORM常用字段和参数

    阅读目录 常用字段 字段合集 自定义字段 字段参数 关系参数 多对多的关联关系的三种方式 一.常用字段 AutoField int自增列,必须填入参数 primary_key=True.当model中 ...

  5. python3开发进阶-Django框架中的ORM的常用(增,删,改,查)操作

    阅读目录 如何在Django终端打印SQL语句 如何在Python脚本中调用Django环境 操作方法 单表查询之神奇的下划线 ForeignKey操作 ManyToManyField 聚合查询和分组 ...

  6. python3 + flask + sqlalchemy +orm(3):多对多关系

    一篇文章有多个tag,一个tag也可以属于多篇文章,文章和tag存在多对多关系 config.py DEBUG = True #dialect+driver://root:1q2w3e4r5t@127 ...

  7. python3 + flask + sqlalchemy +orm(2):数据库中添加表

    往数据库中添加一张保存文章的表,表明为article,字段有id,title,content 同样一个配置文件:config.py DEBUG = True #dialect+driver://roo ...

  8. python3 + flask + sqlalchemy +orm(1):链接mysql 数据库

    1.pycharm中新建一个flask项目 2.按装flask.PyMySQL.flask-sqlalchemy 3.项目下面新建一个config.py 文件 DEBUG = True #dialec ...

  9. Python3 sqlacodegen 根据已有数据库生成 ORM 使用的 model.py

    pip install sqlacodegen pip install pymysql sqlacodegen mysql+pymysql://username:password@127.0.0.1: ...

随机推荐

  1. SQL Server之字符串函数

    以下所有例子均Studnet表为例:   计算字符串长度len()用来计算字符串的长度 select sname ,len(sname) from student 字符串转换为大.小写lower() ...

  2. gitlab配置邮件通知

    配置用户提交评论.添加issue等的邮件通知: Gitlab邮件提醒方便跟踪项目进度,在这里介绍两种方式,一种是用系统的sendmail发送邮件,另一种是GMAIL的stmp来发送邮件 第一种 用系统 ...

  3. MongoDB相关资料

    MongoDB的介绍及安装参考http://www.cnblogs.com/lipan/archive/2011/03/08/1966463.html 安装过程: 第一步:下载安装包:官方下载地址←单 ...

  4. 如何在datagridview 的head上绘制一个全选按钮

    winform的项目中,经常要用到datagridview控件,但是为控件添加DataGridViewCheckBoxColumn来实现数据行选择这个功能的时候,经常需要提供全选反选功能,如果不重绘控 ...

  5. qml的打包问题

    qml2的打包问题: 相对于早期的项目,只需要打包plugin和动态库.带有sqlite的程序如果需要打包,需要打包如下东西: 1.打包AppData目录下的  Local/Qt Project/项目 ...

  6. linux-虚拟机安装

    第一步:下载 安装虚拟机! 链接: http://pan.baidu.com/s/1nuGLwsL 密码: 2qdy 第二步:镜像文件! 链接: http://pan.baidu.com/s/1nuG ...

  7. [转] jQuery源码分析-如何做jQuery源码分析

    jQuery源码分析系列(持续更新) jQuery的源码有些晦涩难懂,本文分享一些我看源码的方法,每一个模块我基本按照这样的顺序去学习. 当我读到难度的书或者源码时,会和<如何阅读一本书> ...

  8. CentOS linux 下eclipse+cdt编译报undefined reffrece to *

  9. zabbix邮件报警

    #!/usr/bin/python #coding:utf-8 import smtplib from email.mime.text import MIMEText import sys mail_ ...

  10. java接收键盘输入

    System.out.print("Please input String to check:");//提示输入 Scanner sc=new Scanner(System.in) ...