pyDbRowFactory -- Python版Db Row Factory

Java包Apache DBUtils有一个很好用特性是, 它能自动根据resultset的结果生成JavaBean对象, 用起来很方便.
Apache DBUtils, http://commons.apache.org/dbutils
Automatically populate JavaBean properties from ResultSets. You don't need to manually copy column values into bean instances by calling setter methods. Each row of the ResultSet can be represented by one fully populated bean instance.

受此启发, 自己造了个轮子, 做了个Python版的 DbRowFactory, 功能相似, 完成Relational-->Object的单向mapping. 只要数据模块符合Python Database API Specification v2.0, 就可以使用 pyDbRowFactory, 支持python/jython.

代码和例子都在下面的code中. 最新版在github上了, https://github.com/harryliu/pyDbRowFactory

文件名: pyObjectCreator.py

#! /usr/bin/env python
#coding=utf-8

import inspect
import sys

__author__ = 'Wade Liu, <wadeliu2008@gmail.com>'
__date__ = '16 Feb 2012'
__version__="0001"

##reference doc
#http://www.cnblogs.com/sevenyuan/archive/2010/12/06/1898056.html
#http://stackoverflow.com/questions/4513192/python-dynamic-class-names
#http://stackoverflow.com/questions/1796180/python-get-list-of-al-classes-within-current-module

def createInstance(full_class_name,*args,**kwargs):
    '''
    instantiate class dynamically
    [arguments]
    full_class_name: full class name that you want to instantiate, included package and module name if has
    *args: list style arguments in class constructor
    *kwargs: dict style arguments in class constructor
    [return]
    an instance of this full_class_name
    [example]
    import pyObjectCreator
    full_class_name="pck1.cls1"
    logger=pyObjectCreator.createInstance(full_class_name,'logname')
    '''
    class_meta=getClassMeta(full_class_name)
    if class_meta!=None:
        obj=class_meta(*args,**kwargs)
    else:
        obj=None
    return obj

    
def getClassMeta(full_class_name):   
    '''
    get class meta object of full_class_name, then we can use this meta object to instantiate full_class_name
    [arguments]
    full_class_name: full class name that you want to instantiate, included package and module name if has
    [return]
    an instance of this full_class_name
    [example]
    import pyObjectCreator
    full_class_name="pck1.cls1"
    loggerMeta=pyObjectCreator.getClassMeta(full_class_name)
    '''
    namespace=full_class_name.strip().rsplit('.',1)
    if len(namespace)==1:
        class_name=namespace[0]
        class_meta=_getClassMetaFromCurrModule(class_name)
    else:
        module_name=namespace[0]
        class_name=namespace[1]
        class_meta=_getClassMetaFromOtherModule(class_name,module_name)
    return class_meta
 

def _getClassMetaFromCurrModule(class_name):
    result=None
    module_name="__main__"
    for name, obj in inspect.getmembers(sys.modules[module_name]):
        if inspect.isclass(obj):
            if name==class_name:
                result=obj
                break
    return result


def _getClassMetaFromOtherModule(class_name, module_name):
    module_meta=__import__(module_name,globals(), locals(),[class_name])
    if module_meta!=None:
        class_meta=getattr(module_meta,class_name)
    else:
        class_meta=None
    return class_meta

 

文件名: pyDbRowFactory.py

# -*- coding: utf-8 -*-
'''
#@summary: DbRowFactory is one common factory to convert db row tuple into user-defined class object.
           It is supported SqlAlchemy, and any database modules conformed to Python Database API
           Specification v2.0. e.g. cx_Oracle, zxJDBC
#@note: The DbRowFactory will create one row instance based on row class binding,
        and try to assign all fields' value to the new object.
        The DbRowFactory maps field and class setter_method/attribute
        by matching names. If both a setter_method and an attribute match
        the same field, the setter_method will be chosen.
#@see: http://www.python.org/dev/peps/pep-0249/
#Tested under: Python 2.7, Jython2.5.2
#Change log:
#version 0001, 09 Nov. 2011, initial version
#version 0002, 16 Feb. 2012, use pyObjectCreator to instantiate rowClass
#version 0003, 08 Mar. 2012, fromSqlAlchemyResultProxy(), fetchAllRowObjects() functions added

##====================sample begin=======
#sample code , file: OracleJdbcSample,py
from __future__ import with_statement
from com.ziclix.python.sql import zxJDBC
from pyDbRowFactory import DbRowFactory

class rowClass2(object):
    def __init__(self):
        self.owner=None
        self.tablename=None

    def setOWNER(self, value):
        self.owner=value

    def print2(self):
        print("ownerName="+self.owner+",tablename="+self.tablename)


if __name__=="__main__":

    #DB API 2.0 cursor sample
    jdbc_url="jdbc:oracle:thin:@127.0.0.1:1521:orcl";
    username = "user1"
    password = "pwd1"
    driver = "oracle.jdbc.driver.OracleDriver"
    with zxJDBC.connect(jdbc_url, username, password, driver) as conn:
        with conn.cursor() as cursor :
            cursor.execute("""select tbl.owner, tbl.table_name tablename,
            tbl.tablespace_name from all_tables tbl""")
            #use DbRowFactory to bind rowClass2 class defined in pkg1.OracleJdbcSample.py
            rowFactory=DbRowFactory(cursor, "pkg1.OracleJdbcSample.rowClass2")
            for rowObject in rowFactory.fetchAllRowObjects():
                rowObject.print2()



    #sqlalchemy sample
    from sqlalchemy import create_engine
    engine=create_engine("sqlite:///:memory:", echo=True)
    sql="""select tbl.owner, tbl.table_name tablename,
            tbl.tablespace_name from all_tables tbl"""
    resultProxy=engine.execute(sql)
    rowFactory=DbRowFactory.fromSqlAlchemyResultProxy(resultProxy, "pkg1.OracleJdbcSample.rowClass2")
    for rowObject in rowFactory.fetchAllRowObjects():
        rowObject.print2()

##====================sample end=======
'''
import pyObjectCreator

__author__ = 'Wade Liu, <wadeliu2008@gmail.com>'
__date__ = '08 Mar 2012'
__version__="0003"


class DbRowFactory(object):
    '''
    #@summary: DbRowFactory is one common row factory for any database
               module conformed to Python Database API Specification
               v2.0. e.g. cx_Oracle, zxJDBC
    #@note: The DbRowFactory will create one row instance based on row class binding,
            and try to assign all fields' value to the new object.
            The DbRowFactory maps field and class setter_method/attribute
            by matching names. if both a setter_method and an attribute match
            the same field, the setter_method will be choosed evently.
    #@see: http://www.python.org/dev/peps/pep-0249/

    #@author: wade liu, wadeliu2008@gmail.com
    '''

    FIELD_TO_SETTER=1
    FIELD_TO_ATTRIBUTE=2
    FIELD_TO_NONE=0



    def __init__(self, cursor, rowClassFullName, setterPrefix="set", caseSensitive=False):
        '''
        ##@summary: Constructor of DbRowFactory
        [arguments]
        cursor: Db API 2.0 cursor object
        rowClassFullName: full class name that you want to instantiate, included package and module name if has
        setterPrefix: settor method prefix
        caseSensitive: match fieldname with class setter_method/attribute in case sensitive or not
        '''
        self._cursor=cursor
        self._setterPrefix=setterPrefix
        self._caseSensitive=caseSensitive

        self._fieldMemeberMapped=False
        self._allMethods=[]
        self._allAttributes=[]
        self._fieldMapList={}

        self._rowClassMeta = pyObjectCreator.getClassMeta(rowClassFullName)
        self._resultProxy=None


    @classmethod
    def fromSqlAlchemyResultProxy(cls, resultProxy, rowClassFullName, setterPrefix="set", caseSensitive=False):
        '''
        ##@summary: another constructor of DbRowFactory
        [arguments]
        resultProxy: SqlAlchemyResultProxy object, can returned after engine.execute("select 1") called,
        rowClassFullName: full class name that you want to instantiate, included package and module name if has
        setterPrefix: settor method prefix
        caseSensitive: match fieldname with class setter_method/attribute in case sensitive or not
        '''
        factory= cls(resultProxy.cursor, rowClassFullName, setterPrefix, caseSensitive)
        factory._resultProxy=resultProxy
        return factory


    def createRowInstance(self, row ,*args,**kwargs):
        '''
        #@summary: create one instance object, and try to assign all fields' value to the new object
        [arguments]
        row: row tuple in a _cursor
        *args: list style arguments in class constructor related to rowClassFullName
        *kwargs: dict style arguments in class constructor related to rowClassFullName
        '''

        
        #step 1: initialize rowInstance before finding attributes. 
        rowObject = self._rowClassMeta(*args,**kwargs)

        #mapping process run only once in order to gain better performance
        if self._fieldMemeberMapped==False:
            #dir() cannot list attributes before one class instantiation
            self._allAttributes=self._getAllMembers(rowObject)
            self._allMethods=self._getAllMembers(rowObject)
            self._fieldMapList=self._mapFieldAndMember()
            self._fieldMemeberMapped=True


        #step 2: assign field values
        i=0
        #self._fieldMapList is [{Field1:(member1Flag,member1)},{Field2:(member2Flag,member2)}]
        for fieldMemberDict in self._fieldMapList:
            for field in fieldMemberDict:
                member=fieldMemberDict[field]
                if member[0]==self.FIELD_TO_NONE:
                    pass
                else:
                    fieldValue=row[i]
                    if member[0]==self.FIELD_TO_SETTER:
                        m=getattr(rowObject, member[1])
                        m(fieldValue)
                    elif member[0]==self.FIELD_TO_ATTRIBUTE:
                        setattr(rowObject, member[1], fieldValue)

            i=i+1
        return rowObject


    def _getAllMembers(self,clazz) :
        '''
        #@summary: extract all user-defined methods in given class
        #@param param clazz: class object
        '''
        members=[member for member in dir(clazz)]
        sysMemberList=['__class__','__doc__','__init__','__new__','__subclasshook__','__dict__', '__module__','__delattr__', '__getattribute__', '__hash__', '__repr__', '__setattr__', '__str__','__format__', '__reduce__', '__reduce_ex__', '__sizeof__', '__weakref__']
        members=[member for member in members if str(member) not in sysMemberList]
        return members



    def _mapFieldAndMember(self):
        '''
        #@summary: create mapping between field and class setter_method/attribute, setter_method is preferred than attribute
        #field can be extract from cursor.description, e.g.
         sql: select 1 a, sysdate dt from dual
         cursor.description:
         [(u'A', 2, 22, None, 0, 0, 1), (u'DT', 91, 7, None, None, None, 1)]
        '''
        #print(self._cursor.description)
        fields=[f[0] for f in self._cursor.description]
        mapList=[]
        #result is [{Field1:(member1Flag,member1)},{Field2:(member2Flag,member2)}]
        for f in fields:
            m= self._getSetterMethod(f)
            key=f
            if m:
                value=(self.FIELD_TO_SETTER,m)
            else:
                m= self._getAttribute(f)
                if m:
                    value=(self.FIELD_TO_ATTRIBUTE,m)
                else:
                    value=(self.FIELD_TO_NONE,None)
            mapList.append({key:value})
        return mapList



    def _getAttribute(self, fieldName):
        '''
        #@summary: get related attribute to given fieldname
        '''
        if self._caseSensitive:
            if fieldName in self._allAttributes:
                return fieldName
        else:
            fieldNameUpper=fieldName.upper()
            allAttributesMap={} # attributeUpper=attribute
            for attr in self._allAttributes:
                allAttributesMap[attr.upper()]=attr
            if fieldNameUpper in allAttributesMap:
                return allAttributesMap[fieldNameUpper]



    def _getSetterMethod(self, fieldName):
        '''
        ##@summary: get related setter method to given fieldname
        '''
        if self._caseSensitive:
            setter=self._setterPrefix+fieldName
            if setter in self._allMethods:
                return setter
        else:
            setterUpper=self._setterPrefix+fieldName
            setterUpper=setterUpper.upper()
            allMethodMap={} #methodUpper=method
            for method in self._allMethods:
                allMethodMap[method.upper()]=method
            if setterUpper in allMethodMap:
                return allMethodMap[setterUpper]


    def _closeResultProxy(self):
        if self._resultProxy is not None:
            if self._resultProxy.closed==False:
                self._resultProxy.close()


    def fetchAllRowObjects(self):
        """Fetch all rows, just like DB-API ``cursor.fetchall()``.
         the cursor is automatically closed after this is called
         """
        result=[]
        rows=self._cursor.fetchall()
        for row in rows:
            rowObject=self.createRowInstance(row)
            result.append(rowObject)
        self._cursor.close()
        self._closeResultProxy()
        return result


    def fetchManyRowObjects(self, size=None):
        """Fetch many rows, just like DB-API
        ``cursor.fetchmany(size=cursor.arraysize)``.

        If rows are present, the cursor remains open after this is called.
        Else the cursor is automatically closed and an empty list is returned.

        """
        result=[]
        rows=self._cursor.fetchmany(size)
        for row in rows:
            rowObject=self.createRowInstance(row)
            result.append(rowObject)
        if len(rows) == 0:
            self._cursor.close()
            self._closeResultProxy()
        return result



    def fetchOneRowObject(self):
        """Fetch one row, just like DB-API ``cursor.fetchone()``.

        If a row is present, the cursor remains open after this is called.
        Else the cursor is automatically closed and None is returned.

        """
        result=None
        row = self._cursor.fetchone()
        if row is not None:
            result=self.createRowInstance(row) 
        else:
            self._cursor.close()
            self._closeResultProxy()

        return result

 



posted @ 2011-11-10 17:47  harrychinese  阅读(1927)  评论(0编辑  收藏  举报