PYPERCOM DB Zugriffsschicht

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
tabellar
User
Beiträge: 186
Registriert: Mittwoch 4. September 2002, 15:28

Samstag 17. Dezember 2005, 21:26

Durch verschiedenste Diskussionen hier im Forum (SQLObject,sqlalchemy,etc)
und dem Beitrag von blackbirdDatenbank Wrapper,
welche ich angeregt mitverfolgt habe, habe ich mich dazu entschlossen,
eine DB Zugriffsschicht für persistente Objekte zu basteln, welche den
Entwickler bei der Objekt und Datenbankdefinition nicht einschränken
(dies ist einer meiner grössten Kritikpunkte bei den momentan aktuellen
ORM Lösungen). PYPERCOM [PYthon PERsistence COMponent :wink: ] ist
vielmehr ein Konzept, das Schnittstellen, Verantwortlichkeiten, Anwendung
und Technik klar regelt und an aktuellen Software Architekturen angelehnt
ist.

Die Anwendungsschicht greift ausschliesslich über die Schnittstellen
PoolManager und QueryManager zu. Der PoolManager liefert pools an die
Anwendung, über die Objekte erzeugt, geholt, geändert und gelöscht werden können.
Der QueryManager verwaltet ausschliesslich SELECT Abfragen, die entsprechende
Ergbenislisten zurückgeben. Sämtliche SQL Statements sind im SqlManager geregelt.
Objekte werden vom ObjectManager zur Verfügung gestellt.

Durch diese klare Regelung können anspruchsvolle Objektdefinitionen und
leistungsfähige SQL queries (JOINS, Subselects, etc.) erstellt und trotzdem
sauber von der Anwendung getrennt bleiben. Durch den Pool Mechanismus
können mehrere Objekte gleichzeitig verwaltet und durch den entsprechenden
commit() Aufruf mit der Datenbank synchronisiert werden. Als Anwendungsbeispiel
poste ich hier eine Interpreter Eingabe. Das Beispiel ist mit SQLite
erstellt worden mit der Tabelle, die am Ende des Python codes steht.
Am Anfang war ich bzgl. des Pool Konzepts sehr kritisch. Aber nach ersten
Anwendungen, auch bei grösseren Projekten, möchte ich das Pool und Query
Konzept nicht mehr missen. Viel Spass und über Kritik würde ich mich sehr freuen :P

Tabellar


edit: Delete() Funktion implementiert
Fehler im sqlObject-Dict korrigiert
Delete() Funktion in Interpreter Anwendung eingefügt
>>> import pypercom
>>> ppc=pypercom.PyPerCom()
>>> pool=ppc.poolMgr.makePool()
>>>
>>> p1=pool.makeObject('person')
>>> p1.props['vorname' ]='Bill'
>>> p1.props['nachname']='Clinton'
>>> p1.props['nickname']='Billy'
>>> p1.props['birthday']='2005-17-12'
>>> p1.props['status' ] =1
>>>
>>> p2=pool.makeObject('person')
>>> p2.props['vorname' ]='Gen'
>>> p2.props['nachname']='too'
>>> p2.props['nickname']='Gentux'
>>> p2.props['birthday']='2005-17-12'
>>> p2.props['status' ]=1
>>>
>>>
>>> pool.commit()
>>>
>>> ppc.queryMgr.getPersons()
[(1, u'2005-12-17 20:29:37', u'Bill', u'Clinton', u'Billy', u'2005-17-12', 1), (
2, u'2005-12-17 20:29:37', u'Gen', u'too', u'Gentux', u'2005-17-12', 1)]
>>>
>>> p=pool.getObject('person',1)
>>> p.props
{'status': 1, 'created': u'2005-12-17 20:29:37', 'birthday': u'2005-17-12', 'id'
: 1, 'nachname': u'Clinton', 'nickname': u'Billy', 'vorname': u'Bill'}
>>>
>>> p.updateProps('nickname','Billy the Kid')
>>> pool.commit()
>>>
>>> ppc.queryMgr.getPersons()
[(1, u'2005-12-17 20:29:37', u'Bill', u'Clinton', u'Billy the Kid', u'2005-17-12', 1),
(2, u'2005-12-17 20:29:37', u'Gen', u'too', u'Gentux', u'2005-17-12', 1)]
>>>
>>> p.delete()
>>> pool.commit()
>>>
>>> ppc.queryMgr.getPersons()
[(2, u'2005-12-18 11:45:51', u'Gen', u'too', u'Gentux', u'2005-17-12', 1)]

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""

   2005-12-18: v0.0.1a    - Created by Tabellar

   PYPERCOM: PYthon PERsistence COMponent
   =======================================

   Access and modify component for database objects.
   Object access only available over the Pool and
   Query Manager Interface.

                        o 
                        |
                     PYPERCOM
                        |
                        o
                       /  \ 
                      /    \
                     /      \
                    o        o
                    |        |  
        OBJMGR--o--POOL    QUERY
                    |  \  /  |
                    |   \/   |
                    |   /\   |
                    |  /  \  |
                    | /    \ |
                    o        o
                    |        |
                 CONMGR   SQLMGR
                    |
                    o
                    |
                  DBAPI     


   Pool Manager
   ----------------------
   Creates and shares object pools. Object pools are
   responsible for object modifications and the DB
   persistence synchronisation.

   Query Manager
   ----------------------
   Provides only SELECT queries for list requests. 

   Connection Manager
   ----------------------
   Creates and shares CURSORCONNECTIONS, a special
   PYPER DB-API cursor connection object.

   Object Manager
   ----------------------
   Provides database objects

   SQL Manager
   ----------------------
   Administrate all SQL statements.    

"""



# DB-Engine settings. Maybe later stored in a config file.
dbEngines={
           'postgres': {'database':'dbwf', 
                        'host'    :'127.0.0.1', 
                        'user'    :'postgres', 
                        'password':'xxxxxxx'},
           'sqlite'  : {'database':'pypercom.sqlite'}
           }


class PyPerCom(object):
  def __init__(self):
      self.conMgr  = ConnectionMgr()
      self.sqlMgr  = SqlMgr()
      self.objMgr  = ObjectMgr()
      self.poolMgr = PoolMgr(self.conMgr,self.objMgr,self.sqlMgr)
      self.queryMgr= QueryMgr(self.conMgr,self.sqlMgr)

        

class PoolMgr(object):
  def __init__(self,conMgr,objMgr,sqlMgr):
      """ Pool Manager """
      self.pools=[]
      self.conMgr=conMgr
      self.objMgr=objMgr
      self.sqlMgr=sqlMgr

  def makePool(self):
      pool=Pool(self.conMgr.getCursorConnection(),self.objMgr,self.sqlMgr)
      self.pools.append(pool)
      return pool

class Pool(object):
  def __init__(self,curcon,objMgr,sqlMgr):
      """ Objekt Pool """
      self.objects=[]
      self.curcon=curcon
      self.objMgr=objMgr
      self.sqlMgr=sqlMgr
      
  def makeObject(self,type):
      object=self.objMgr.makeObject(type)
      object._dirty=1
      self.objects.append(object)
      return object

  def getObject(self,type,id):
      object=self.objMgr.makeObject(type)
      object.props['id']=id
      self.objects.append(object)
      sqlStat=self.sqlMgr.getSqlPropertyStatement(object)
      self.curcon.execute(sqlStat[0],sqlStat[1])
      results=self.curcon.fetchall()
      object.props={}
      resultindex=0
      for i in sqlStat[2]:
          object.props[i]=results[0][resultindex]
          resultindex+=1
      return object

  def insertObject(self,object):
      self.objects.append(object)

  def commit(self):
      for i in self.objects:
            if i._dirty!=0:
               sqlStat=self.sqlMgr.getSqlModifyStatement(i)
               self.curcon.execute(sqlStat[0],sqlStat[1])
               i._dirty=0
      self.curcon.commit()

  def clear(self):    
      self.objects=[]

  def close(self):
      self.curcon.ready()
      self=None



class QueryMgr(object):
    def __init__(self,conMgr,sqlMgr):
        """ SELECT Query Manager """
        self.conMgr=conMgr
        self.sqlMgr=sqlMgr
        
    def getPersons(self):
        curcon=self.conMgr.getCursorConnection()
        sql=self.sqlMgr.getSqlQuery('getPersons')
        curcon.execute(sql,())
        results=curcon.fetchall()
        curcon.ready()
        return results


class ConnectionMgr(object):
    def __init__(self):
        self.maxsize=4
        self.curcons=[]
        
    def newCursorConnection(self):
        if len(self.curcons) <= self.maxsize:
           curcon=CursorConnection()
           self.curcons.append(curcon)
           return curcon
        else:
           raise NotImplementedError
        
    def getCursorConnection(self):
        notusedcons=0
        for i in self.curcons:
           if i.inUse()==0:
              notusedcons += 1
              i.used()
              return i
              break
        if notusedcons == 0:
           curcon=self.newCursorConnection()
           curcon.used()
           return curcon
           
    def closeCursorConnections(self):
        for i in self.curcons:
            i.close()

class CursorConnection(object):
    def __init__(self):
        self.inuse=0
        self.connect()
    def connect(self,dbengine='sqlite'):
        if dbengine=='postgres':
            import pyPgSQL.PgSQL as dbAPI
            args=dbEngines['postgres']
            self.con=dbAPI.connect(**args)
            self.enginetype='postgres'
        elif dbengine=='sqlite':
            from pysqlite2 import dbapi2 as dbAPI
            args=dbEngines['sqlite']
            self.con=dbAPI.connect(args['database'])
            self.enginetype='sqlite'
        self.cursor=self.con.cursor()
    def inUse(self):
        return self.inuse
    def used(self):
        self.inuse=1
    def ready(self):
        self.inuse=0
    def cursor(self):
        return self.con.cursor()
    def execute(self,sql,args):
        self.cursor.execute(sql,args)
    def fetchall(self):
        return self.cursor.fetchall()
    def commit(self):
        self.con.commit()
    def close(self):
        self.con.close()

class ObjectMgr(object):
  def __init__(self):
      """ object Mgr """
  def makeObject(self,type):
      if type=='person':
         return Person()


class Person(object):
  def __init__(self):
      """ Example Object """
      self.type='person'
      self._dirty=0
      self.props={}

  def init(self,vorname,nachname,nickname,birthday,status):
      self.props['vorname'] = vorname
      self.props['nachname']= nachname
      self.props['nickname']= nickname
      self.props['birthday']= birthday
      self.props['status']  = status
      self._dirty=1

  def initByProps(self,props):
      self.props=props
      self._dirty=5

  def updateProps(self,propname,propvalue):
      self.props[propname]=propvalue      
      self._dirty=2 #update

  def delete(self):
      self._dirty=3 #delete


class SqlMgr(object):
  def __init__(self):
      """ Statement Mgr """ 

  def getSqlModifyStatement(self,obj):
      sql=sqlObject[obj.type][obj._dirty]['sql']
      args=[]
      for i in sqlObject[obj.type][obj._dirty]['args']:
          args.append(obj.props[i])
      return (sql,args)

  def getSqlPropertyStatement(self,obj):
      sql=sqlObject[obj.type][9]['sql']
      args=[]
      for i in sqlObject[obj.type][9]['args']:
          args.append(obj.props[i])
      propDef=sqlObject[obj.type][9]['prop']    
      return (sql,args,propDef)

  def getSqlQuery(self,key):
      return sqlQuery[key]





#---------------------------------------------------------------------------------   
#   Dictionary for SQL Object modifications -> Pool
#---------------------------------------------------------------------------------   
"""
     CursorConnection._dirty Flags:
     ------------------------------------
     1:INSERT
     2:UPDATE
     3:DELETE
     9:PROPERTIES
"""

sqlObject={'person':{1:{'sql' :"INSERT INTO tperson (vorname,nachname,nickname,birthday,status) VALUES (?,?,?,?,?);",
                        'args': ('vorname','nachname','nickname','birthday','status')},
                     2:{'sql' :"UPDATE tperson set created=?,vorname=?,nachname=?,nickname=?,birthday=?,status=? where id=?;",
                        'args': ('created','vorname','nachname','nickname','birthday','status','id')},
                     3:{'sql' :"DELETE FROM tperson where id=?;",
                        'args': ('id',)},
                     9:{'sql' :"""SELECT id,created,vorname,nachname,nickname,birthday,status
                                  FROM tperson
                                  WHERE id=?;""",
                        'args': ('id',),
                        'prop': ('id','created','vorname','nachname','nickname','birthday','status')},
                    } 
        } 


#---------------------------------------------------------------------------------   
#   Dictionary for SELECT statements -> QueryManager
#---------------------------------------------------------------------------------   

sqlQuery={
          'getPersons':"select * from tperson;",
         }


#---------------------------------------------------------------------------------   
#   SQL Table example
#---------------------------------------------------------------------------------   

"""
create Table tperson        (
             id             INTEGER PRIMARY KEY,
             created        TIMESTAMP,
             vorname        VARCHAR (16),
             nachname       VARCHAR (16),
             nickname       VARCHAR (32),
             birthday       DATE,
             status         INTEGER DEFAULT 0          
);
CREATE TRIGGER insert_tperson AFTER  INSERT ON tperson
BEGIN
 UPDATE tperson SET created = DATETIME('NOW','LOCALTIME') WHERE rowid = new.rowid;
END;
"""
Zuletzt geändert von tabellar am Dienstag 20. Dezember 2005, 21:39, insgesamt 1-mal geändert.
tabellar
User
Beiträge: 186
Registriert: Mittwoch 4. September 2002, 15:28

Sonntag 18. Dezember 2005, 12:05

Neue Delete() Funktion für DB Objekt implementiert. Änderungen und
Anwendung s.o.

Tabellar
tabellar
User
Beiträge: 186
Registriert: Mittwoch 4. September 2002, 15:28

Dienstag 20. Dezember 2005, 10:38

Kein Kommentar? Ich dachte, das Thema DB Zugriffsschicht wäre hier von
Interesse... :roll: Nächster Schritt bei PyPerCom wird die Umstellung des
ObjectPools auf ein DICT Object sein, das bei entsprechender Indizierung
deutlich schneller ist als die LISTE. Dafür muss ich die Pool Objekte aber
eindeutig indizieren. Sprich, jedes DB Object bekommt eine eindeutige
Objekt ID, über die das Object im Pool DICT indiziert wird :wink:.

Tabellar
Benutzeravatar
jens
Moderator
Beiträge: 8482
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Dienstag 20. Dezember 2005, 12:26

tabellar hat geschrieben:Kein Kommentar?
Ich find's schon interessant... Hab allerdings in PyLucid eine ähnliche Variante... Ich weiß nicht, ob deins Vorteile hat...

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

Dienstag 20. Dezember 2005, 23:37

tabellar hat geschrieben:Nächster Schritt bei PyPerCom wird die Umstellung des ObjectPools auf ein DICT Object sein, das bei entsprechender Indizierung deutlich schneller ist als die LISTE.
Meinst Du die Liste `objects` bei der Klasse `Pool`? Ich habe den Quelltext nur kurz überflogen, sehe da aber keinen Vorteil für ein Dictionary. Nur um sicherzugehen: Dictionaries sind nicht automatisch immer und überall "schneller" als Listen.

Ansonsten ist mir aufgefallen, das Du eine Menge "builtins" überschreibst: object, id, type.
Antworten