#coding: utf-8
#
#Python MySQL ORM QuickORM hacking#说明:#以前仅仅是知道有ORM的存在,但是对ORM这个东西内部工作原理不是很清楚,#这次正好需要用到,于是解读一个相对来说很简单的Python2 ORM的例子。#
#参考源码:#A simple ORM provides elegant API for Python-MySQL operation#https://github.com/2shou/QuickORM#
#2016-10-15 深圳 南山平山村 曾剑锋
importMySQLdb#
#作为和数据库中字段对应的域,同样也作为类属性存在#
classField(object):pass
classExpr(object):#合成where查询部分
def __init__(self, model, kwargs):
self.model=model#How to deal with a non-dict parameter?
#提取键值对的value部分
self.params =kwargs.values()#提取键值对的key部分,并合成替代字符串
equations = [key + '= %s' for key inkwargs.keys()]
self.where_expr= 'where' + 'and'.join(equations) if len(equations) > 0 else ''
def update(self, **kwargs):
_keys=[]
_params=[]#筛选数据
for key, val inkwargs.iteritems():if val is None or key not inself.model.fields:continue_keys.append(key)
_params.append(val)#和__init__中的键值对的values数据联合
_params.extend(self.params)#合成查询语句
sql = 'update %s set %s %s;' %(
self.model.db_table,','.join([key + '= %s' for key in_keys]), self.where_expr)returnDatabase.execute(sql, _params)def limit(self, rows, offset=None):#合成limit数据,这里就是合成想从那一行开始取数据,取多少数据
self.where_expr += 'limit %s%s' %('%s,' % offset if offset is not None else '', rows)returnselfdefselect(self):#合成查询语句,需要查询的字段,表明,条件
sql = 'select %s from %s %s;' % (','.join(self.model.fields.keys()), self.model.db_table, self.where_expr)#取出所有的数据,这里使用了yield,使得select可以被for in语法再次迭代从而获取到值
for row inDatabase.execute(sql, self.params).fetchall():#获取传入的模板类型,这样就不用知道是什么类运行了select
inst =self.model()#获取一条信息中的值
for idx, f inenumerate(row):
setattr(inst, self.model.fields.keys()[idx], f)yieldinst#返回查询的数据统计总数
defcount(self):
sql= 'select count(*) from %s %s;' %(self.model.db_table, self.where_expr)
(row_cnt, )=Database.execute(sql, self.params).fetchone()returnrow_cntclassMetaModel(type):
db_table=None
fields={}def __init__(cls, name, bases, attrs):
super(MetaModel, cls).__init__(name, bases, attrs)
fields={}#从类所有的属性中提取出类属性,和数据库中的字段对应,这里更多的给Expr类使用。
for key, val in cls.__dict__.iteritems():ifisinstance(val, Field):
fields[key]=val
cls.fields=fields
cls.attrs=attrsclassModel(object):#采用MetaModel来构建Model类
__metaclass__ =MetaModel#动态生成对应的sql语句,并执行对应的语句,要注意这里是self.__dict__获取的实例属性。
defsave(self):
insert= 'insert ignore into %s(%s) values (%s);' %(
self.db_table,','.join(self.__dict__.keys()), ','.join(['%s'] * len(self.__dict__)))return Database.execute(insert, self.__dict__.values())#使用where来查询
@classmethoddef where(cls, **kwargs):returnExpr(cls, kwargs)classDatabase(object):
autocommit=True
conn=None
db_config={}#通过db_config字典数据设置连接数据库的值
@classmethoddef connect(cls, **db_config):
cls.conn= MySQLdb.connect(host=db_config.get('host', 'localhost'), port=int(db_config.get('port', 3306)),
user=db_config.get('user', 'root'), passwd=db_config.get('password', ''),
db=db_config.get('database', 'test'), charset=db_config.get('charset', 'utf8'))
cls.conn.autocommit(cls.autocommit)
cls.db_config.update(db_config)#这里是连接数据库,里面有一些策略,譬如:
#1. 如果没有连接数据库,那么就连接数据库;
#2. 如果连接了数据库,那么测试是否可ping通再返回连接;
#3. 如果ping不通,那么重新连接,再返回。
@classmethoddefget_conn(cls):if not cls.conn or notcls.conn.open:
cls.connect(**cls.db_config)try:
cls.conn.ping()exceptMySQLdb.OperationalError:
cls.connect(**cls.db_config)returncls.conn#这里是直接执行sql语句,返回的是执行后的cursor
@classmethoddef execute(cls, *args):
cursor=cls.get_conn().cursor()
cursor.execute(*args)returncursor#对象被垃圾回收机回收的时候调用
def __del__(self):if self.conn andself.conn.open:
self.conn.close()#执行原始sql语句
def execute_raw_sql(sql, params=None):return Database.execute(sql, params) if params else Database.execute(sql)