• 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



  • 相关阅读:
    DIV+CSS列表式布局(同意图片的应用)
    Cache 应用程序数据缓存
    mysql 中 isnull 和 ifnull 判断字段是否为null
    Logo图标快速生成软件(Sothink Logo Maker) v3.5 官方设计师版
    Linqer工具
    mvc学习视频
    MvcPager注意版本与mvc的版本
    此版本的 SQL Server 不支持用户实例登录标志。该连接将关闭“的解决
    ASP.NET 免费开源控件
    逆向知识之CS1.6辅助/外挂专题.1.实现CS1.6主武器副武器无限子弹
  • 原文地址:https://www.cnblogs.com/harrychinese/p/DbRowFactory_Utility_For_Python.html
Copyright © 2020-2023  润新知