SQLAlchemy - Problem mit Querys

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
hoppelchen
User
Beiträge: 18
Registriert: Mittwoch 13. April 2011, 17:45

Hallo Zusammen,

ich möchte mir für eine kleine Anwendung Daten in eine SQLite-Datenbank speichern. Habe mir (wegen parallelen Zugriffen) eine Klasse "DBManager.py" geschrieben, die mir die Arbeit mit den Query erleichtern soll. Klappt soweit auch gut - wenn ich keine Bedingungen an meine Suche stelle. Sonst bekomme ich einen "NotImplementedError: Operator 'getitem' is not supported on this expression" in der Klasse "all.py" in Zeile 27. Habe google konsultiert, werde aber daraus nicht schlau...

Bitte um Rat bzw. auch generell um Tipps, wie ich mein kleines Projekt verbessern kann. Lauffähiges Beispiel mit 4 Klassen im Folgenden - Startdatei: "all.py"

Edit: Fehler gefunden (Beispiel unten sollte nun funktionieren) - nehme aber dennoch gern Rat an, wie man sowas vielleicht eleganter lösen könnte :-) :D

Vielen Dank für Eure Hilfe!

Code: Alles auswählen

#BaseClass.py
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base() # Basisklasse für die Vererbung einer Klasse, die in der DB gespeichert werden soll

Code: Alles auswählen

#Inhaber.py
from sqlalchemy import Column
from sqlalchemy import String


from BaseClass import Base


class Inhaber(Base):
    __tablename__ = "inhaber"
    name = Column(String, nullable=False, primary_key=True)
    vorname = Column(String, nullable=False, primary_key=True)

    def __str__(self):
        return "Inhaber mit Name: %s %s"%(self.vorname, self.name)

Code: Alles auswählen

#DBManager.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from BaseClass import Base

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]
        
class DBManager(object):
    __metaclass__ = Singleton
    session = None 
    engine = None
    
    def __init__(self):
        self.engine = create_engine('sqlite:///sqlalchemy5.db', echo=False) 
        Session = sessionmaker(bind=self.engine)
        self.session=Session()
            
    def get_session(self):
        if self.session == None:
            DBManager()        
        return self.session

    def get_engine(self):
        if self.engine == None:
            DBManager()        
        return self.engine 

    def erzeugeAlleTabellen(self):
        Base.metadata.create_all(DBManager().get_engine())    
        
    def add(self, objekt):
        self.session.add(objekt)

    def rollback(self):
        self.session.rollback()
        
    def flush(self):
        self.session.flush()
        
    def commit(self):
        self.session.commit()
 
    def delete(self, objekt, tabelle):
        print("Noch nicht implementiert...")

    def suchqueryOhneBedinung(self, klasse):
        return self.session.query(klasse).all()

    def suchqueryMitBedinung(self, klasse, bedingung):
        return self.session.query(klasse).filter(bedingung).all()
            
    def suchqueryMitBedinungen(self, klasse, bedingungen):
        ownQuery = self.session.query(klasse)
        for bedingung in bedingungen:
            ownQuery = ownQuery.filter(bedingung)    
        return ownQuery.all()

Code: Alles auswählen

#all.py
from Inhaber import Inhaber
from DBManager import DBManager
from sqlalchemy.exc import IntegrityError
from sqlalchemy.exc import InvalidRequestError

if __name__ == "__main__":

    dbmanager = DBManager()
    try:
        dbmanager.erzeugeAlleTabellen()

        inhaber1 = Inhaber(name="Meier", vorname = "Hans")
        inhaber2 = Inhaber(name="Meier", vorname = "Herbert")
        inhaber3 = Inhaber(name="Huber", vorname = "Herbert")  

        dbmanager.add(inhaber1)
        dbmanager.add(inhaber2)
        dbmanager.add(inhaber3)

        dbmanager.commit()
    except (IntegrityError,InvalidRequestError):
        dbmanager.rollback()

    #suche = dbmanager.suchqueryOhneBedinung(inhaber1.__class__) # mit dieser Suche klappts

    suche = dbmanager.suchqueryMitBedinung(inhaber1.__class__, inhaber1.__class__.name == "Meier") # da hakts
    #suche = dbmanager.suchqueryMitBedinungen(inhaber1.__class__, [inhaber1.__class__.name == "Meier", inhaber1.__class__.vorname == "Hans"]) # hier hakts auch


    for i in suche:
        print(i)
BlackJack

@hoppelchen: Das sieht mir alles ein wenig „unpythonisch” aus. Eher wie Java. Pro Modul nur eine Klasse ist sehr unschön.

`DBManager` ist gruselig. Erst die Magie mit dem `Singleton` und dann die Verwendung. Dauernd werden Objekte davon erstellt und wieder verworfen. Das sind globale Variablen auf eine möglichst wirre Art implementiert wie das aussieht. Und auch teilweise fehlerhaft, denn `DBManager.get_session()` und auch die `get_engine()` funktionieren so nicht. Module sind ”natürliche” Singletons in Python, da braucht man sich keine Magie basteln. Es gibt ``global``. Ich denke nicht das man es verwenden sollte, aber wenn man schon globalen Zustand bastelt, dann doch bitte so einfach wie möglich.

Die Namensschreibweisen entsprechen nicht dem Style Guide for Python Code.

`suchquery` ist doppel gemoppelt und ausserdem „Denglisch”. Im Wort „Bedingung” fehlt jeweils ein „g”. `i` ist kein geeigneter Name für etwas anderes als ganze Zahlen. Insbesondere in Schleifen.

Der Zugriff auf das `__class__`-Attribut bei den Abfragen ist unsinnig. Man *weiss* an der Stelle das es sich um `Inhaber` handelt. Diese Indirektion macht keinen Sinn.

Da es sich um Python 2 handelt machen Klammern um das ”Argument” bei der ``print``-Anweisung keinen Sinn. Da sollte man lassen, oder den `__future__`-Import durchführen der die Anweisung zu einer Funktion macht.
hoppelchen
User
Beiträge: 18
Registriert: Mittwoch 13. April 2011, 17:45

@BlackJack: Danke für die Kritik. War in der Tat einige Jahre dem Einfluss von Java hilflos ausgeliefert ;-)

Ist es für dich generell "gruselig" ausschließlich über eine Klasse wie "DBManager" mit der Datenbank zu kommunizieren oder deren Umsetzung?
Hast du irgendwo ein kleines Beispiel rumliegen, wie man das besser machen könnte?
BlackJack

@hoppelchen: Erstes Warnzeichen bei `DBManager` ist der Name, nämlich das Manager darin. Das pappt in Java an so vielen Namen dran und es sagt nicht wirklich etwas aus weil letztendlich jede Klasse einen inneren Zustand verwaltet. Wenn man keinen besseren Namen findet, dann stimmt wahrscheinlich etwas mit dem Entwurf nicht.

Dann ist der innere Zustand von der Klasse eine `Engine` und eine `Session` wobei die `Session` die `engine` kennt. Also eigentlich ist es nur eine `Session`. Und viele Methoden delegieren einfach nur an dieses `session`-Objekt. Das bisschen was übrig bleibt wenn man das entfernt kann man auch einfach als Funktionen in ein Modul stecken oder gleich ganz bleiben lassen. Die Frage ist jetzt wie lange so ein `Session`-Objekt existieren sollte. Bei Dir ja anscheinend über die gesamte Laufzeit des Programms hinweg. Was zum Beispiel Threads schon mal davon abhält nebenläufig die Datenbank zu verwenden. Eben das typische Problem mit globalem Zustand. Das andere ”extrem”, ein `Session`-Objekt pro Datenbanktransaktion ist bei vielen eher erste Instinkt. Wenn man „thread-local”-Globale `Session`-Objekte haben möchte kann man `sqlalchemy.orm.scoped_session()` verwenden.
hoppelchen
User
Beiträge: 18
Registriert: Mittwoch 13. April 2011, 17:45

@Blackjack: Danke für den Hinweis. Ich erzeuge stellenweise mit einer Instanz von Klasse A noch eine andere Instanz von Klasse B. Hatte da massive Probleme ohne eine globale Session - deswegen bin ich dazu übergegangen, weils so funktioniert hat :-)

Werde nun mal sehen, wie ich deine Anmerkungen in meinem Code umsetzen kann...
Antworten