標籤:
# 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 深圳 南山平山村 曾劍鋒import MySQLdb## 作為和資料庫中欄位對應的域,同樣也作為類屬性存在# class Field(object): passclass Expr(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 in kwargs.keys()] self.where_expr = ‘where ‘ + ‘ and ‘.join(equations) if len(equations) > 0 else ‘‘ def update(self, **kwargs): _keys = [] _params = [] # 篩選資料 for key, val in kwargs.iteritems(): if val is None or key not in self.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) return Database.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) return self def select(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 in Database.execute(sql, self.params).fetchall(): # 擷取傳入的模板類型,這樣就不用知道是什麼類運行了select inst = self.model() # 擷取一條資訊中的值 for idx, f in enumerate(row): setattr(inst, self.model.fields.keys()[idx], f) yield inst # 返回查詢的資料統計總數 def count(self): sql = ‘select count(*) from %s %s;‘ % (self.model.db_table, self.where_expr) (row_cnt, ) = Database.execute(sql, self.params).fetchone() return row_cntclass MetaModel(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(): if isinstance(val, Field): fields[key] = val cls.fields = fields cls.attrs = attrsclass Model(object): # 採用MetaModel來構建Model類 __metaclass__ = MetaModel # 動態產生對應的sql語句,並執行對應的語句,要注意這裡是self.__dict__擷取的執行個體屬性。 def save(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來查詢 @classmethod def where(cls, **kwargs): return Expr(cls, kwargs)class Database(object): autocommit = True conn = None db_config = {} # 通過db_config字典資料設定串連資料庫的值 @classmethod def 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不通,那麼重新串連,再返回。 @classmethod def get_conn(cls): if not cls.conn or not cls.conn.open: cls.connect(**cls.db_config) try: cls.conn.ping() except MySQLdb.OperationalError: cls.connect(**cls.db_config) return cls.conn # 這裡是直接執行sql語句,返回的是執行後的cursor @classmethod def execute(cls, *args): cursor = cls.get_conn().cursor() cursor.execute(*args) return cursor # 對象被記憶體回收機回收的時候調用 def __del__(self): if self.conn and self.conn.open: self.conn.close()# 執行原始sql語句def execute_raw_sql(sql, params=None): return Database.execute(sql, params) if params else Database.execute(sql)
Python MySQL ORM QuickORM hacking