SQLAlchemy: Session richtig verstehen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Sophus hat geschrieben:@jerch: Dann habe ich folgendes Beispiel auf dieser Seite unter dem Kapitel Private variables nicht richtig verstanden:

Hier wird ja auch versucht schreibend auf das Attribut zuzugreifen, aber der Print-Output zeigt nach wie vor die Zahl 200 an.
Einfach wieder vergessen, dass es 'private' Attribute gibt, das ist komplett nutzloses Zeug (mit gaaaaaaaaaaaaanz wenigen Ausnahmen, die du erstmal die nächsten 5 Jahre nicht brauchst, Ich hab bisher in meinen 7+ Jahren Python nicht einmal eine private Variablen verwendet).

Zu den Datenbanken: Machs wie sonst auch, einfach nicht global ablegen sondern einfach mit übergeben, an Funktionen, an Klassen einfach durchreichen. Kannst das ganze evt. Zusammen mit der Konfiguration kapseln oder wenn du Flask verwendest ans app-Objekt anhängen.
the more they change the more they stay the same
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Wo sind die Namen "anders"? In der Klasse wird ein Attribut namens "__maxspeed" definiert, und von außen wird über die Klasse aud das Attribut zugegriffen: redcar.__maxspeed = 10. Es handelt sich in beiden Fällen um den Namen __maxspeed?

@Dav1d: Rein gedanklich habe ich Probleme damit, das Session()-Objekt rumzureichen. Damit wird es mal präsent haben:

Code: Alles auswählen

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

def connect_to_database():
    # an Engine, which the Session will use for connection
    # resources
    some_engine = create_engine('postgresql://scott:tiger@localhost/')

    # create a configured "Session" class
    Session = sessionmaker(bind=some_engine)

    # create a Session
    sess = Session()

    # Now I'm logged into the MySQL server and
    # have a session to call - so in order to manipulate the records later.

def insert_data():
    get_my_session = ses.get_session()
    myobject = MyObject('foo', 'bar')
    get_my_session.add(myobject)
Wir sehen hier, dass der Benutzer mittelst der connect_to_database()-Funktion anmeldet. Vergessen wir einmal den Moment, dass hier keine Zugangsdaten als Argumente übergeben werden. Nach der Anmeldung habe ich alles erzeugt was ich brauche. Jetzt müsste ich ein return setzen, um das Session()-Objekt rumzureichen. Aber wohin? Es kann ja sein, dass der Anwender sich anmeldet, und sagen wir mal 10 Minuten später eine Such-Abfrage startet. Oder das er 5 Minuten später einen Eintrag vornimmt. Hier sehen wir, dass wir die insert_data()-Funktion haben, falls der Anwender auf den Button "Einfügen" klickt. Aber woher weiß ich was der Anwender nach dem LogIn vorhat? Das Rumreichen der Argumente kann ja nur Sinn ergeben, wenn es eine vorgegebene "Richtung" hat. In meinem Fall bleibt das Handeln des Anwenders nach dem LogIn ein Geheimnis für den Entwickler. Also dachte ich mir, lege ich das Session()-Objekt erst einmal ab. Und wenn der Anwender sich irgendwann dazu entscheidet einen Datensatz anzuspeichern, dann hole ich mit dieses Session()-Objekt.
BlackJack

@Sophus: Das Attribut von aussen heisst tatsächlich so wie Du es schreibst, das Attribut innerhalb der Methoden heisst anders. Der Name wird von Python verändert bevor tatsächlich darauf zugegriffen wird. Und zwar nach Regeln die in der Dokumentation stehen. Und wenn man die manuell (oder mit einer kleinen Funktion) auf den Namen anwendet, dann kann man auch von aussen auf das Attribut zugreifen.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Die Doku zu Session beschreibt sehr genau, was auf welcher Ebene vorgehalten werden sollte (Modul- vs Klassen- vs Funktionsebene). Es gibt sogar Bsp. für DOs and DONTs. Vielleicht solltest Du das mal lesen --> http://docs.sqlalchemy.org/en/latest/or ... asics.html
BlackJack

@Sophus: Du weisst zwar nicht was der Benutzer in welcher Reihenfolge macht, aber Du weisst das er einen Datensatz einfügen können möchte. Und dahin wo entschieden wird was der Benutzer will, also da wo der Aufruf von der Einfügefunktion/-methode ist wenn der Benutzer sich dafür entschieden hat, musst Du auch die Datenbanksitzung übergeben, damit dort dann im Falle das der Benutzer einen neuen Datensatz einfügen möchte an diese Sitzung entsprechend weitergereicht werden kann.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Ich kann dir nicht ganz folgen. Stellen wir uns ein Anmelde-Fenster vor. Dort trägt der Anwender die benötigen Daten ein. Mit einem Klick oder Enter übergebe ich all die Daten an die Funktion, die für das Verbinden zuständig ist. Bisher alles kein Problem. Wenn alle Daten stimmen ist der Benutzer nun angemeldet. Und ab diesem Punkt komme ich und frage mich "Und weiter?". Wir gehen auch davon aus, dass der Benutzer vorher nicht versucht hat einen Datensatz abzuspeichern und erst DANN dazu aufgefordert wird seine Zugangsdaten einzugeben, sondern, er meldet sich direkt an. Und wohin nun das Session()-Objekt? Irgendwo muss das Objekt hin. Wenn ich weiß, dass er einen Datensatz einfügen will, kann ich das Objekt an die entspreche Funktion übergeben. Aber im Zeitraum zwischen Anmeldung und Datensatz einfügen befinde ich mich in einer Spannung, dass ich nicht weiß was ich mit dem erzeugten Objekt anstellen soll.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: Du redest hier von einer GUI? Dann ist der Ort, wo man sich einen SessionMaker speichert, das Objekt, das alles steuert, gemeinhin MainWindow genannt. Und wenn Du wissen willst, was bei Deinen Beispielen mit __ passiert, kannst Du ja mal die Attribute Deiner Objekte mit dir anzeigen lassen.
BlackJack

@Sophus: Der Code, der nach dem Anmeldedialog die Sitzung erstellt, wird doch auch von irgend woher aufgerufen. Und dort hin gibt er die Sitzung zurück. Der Code der den Anmeldedialog initiiert hat, wird danach ja das weitere Programm irgendwie anstossen. Und da wird irgendwann die Sitzung benötigt werden, also übergibt man sie dort hin weiter. Das alles natürlich nicht so direkt, weil man GUI und Programmlogik ja hoffentlich sauber getrennt hat. Aber wie genau Du Dein Programm nun strukturierst wissen wir nicht, also kann man da auch nicht wirklich viel zu sagen. Das sind eben keine 5 bis 10 Zeilen Code der immer gleich aussieht, sondern deutlich mehr und es hängt davon ab wie das Programm aufgebaut ist, und das ist halt so variabel und ungewiss, das man da schlecht *das* allgemeingültige Beispiel bringen kann.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3 und BlackJack:

Vielleicht sollte ich noch detaillierter vorgehen. (01) Ich habe ein sogenanntes "MainWindows" mit dem MDI-Widget. (02) Auf diesem MainWindows ist dann (wie üblich eigentlich) eine Menuleiste. (03) In dieser Menuleiste gibt es ein Menu mit der Beschriftung "Anmelden". (04) Dort klickt der Anwender auf das entsprechende MenuItem und (05) ihm erscheint ein GUI-Fenster, wo er all sein Zugangsdaten in die QLineEdits einträgst und (06) anschließend auf den QPushButton drückt - er kann auch Enter drücken. (07) Durch den QPushButton werden die Zugangsdaten in Form von Argumente zur Programmlogik geschickt - zur Anmelde-Funktion.(08) Dort wird dann eine Anmeldung vorgenommen. (09) War die Anmeldung korrekt, schließt sich das Anmelde-Fenster und der Anwender ist angemeldet. Fazit: Hier wurde die Anmeldung durch einen QPushButton-Klick angestoßen. Hier wird dann eine Funktion aufgerufen, die dann all Inhalte der QLineEdits schnappt, und sie dann zur Programmlogik schickt. Und diese Funktion kann ja nicht das Session()-Objekt behalten.

Das heißt, die Anmeldung wird vom Anwender allein aus initiiert. Der Anwender kann auch versuchen ohne Anmeldung einen Datensatz einzufügen, aber dann erhält der Anwender eine Fehlermeldung (QMessageBox), mit dem Hinweis, dass er nicht angemeldet ist und wird gleichzeitig gefragt ob er sich anzumelden möchte, und dann öffnet sich das Anmelde-Fenster.

Aber mein Problem besteht erst einmal darin, dass der Anwender über die Menuleiste die Anmeldung initiieren kann, ohne vorher versucht zu haben einen Datensatz zu manipulieren oder Anfragen an den Server abzusetzen. Und wenn er sich entschließt "Oh, ich melde mich erst einmal an, und sehen dann weiter, was ich mache - vielleicht gehe ich nach der Anmeldung erst einmal Kaffee trinken".
BlackJack

@Sophus: Die Anmeldedaten werden ja der Programmlogik übergeben, damit die dann die Anmeldung durchführen kann. Und an der Stelle kann sich die Programmlogik dann ja auch an geeigneter Stelle die Sitzung merken. Sie merkt ja anscheinend auch schon ob eine Sitzung existiert, denn sonst könnte man ja keine Fehlermeldung präsentieren wenn man sich noch nicht angemeldet hat. Dazu muss man die Programmlogik ja nach der Sitzung fragen können, oder eben zumindest danach ob es die gibt oder nicht.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Nur weil sich der User über das Popup anmelden kann, heisst das noch lange nicht, dass Du an dieser Stelle in der Programmlogik überhaupt eine DB-Session brauchst. Diese Trennung von Präsentations- und Programmlogik musst Du selber vornehmen und an dieser Stelle kann man nur allegemeine Hinweise geben, da keiner aus Du weiss, wie Du das getrennt/abgebildet hast.

Zu den Session - ich zitier mal die Doku:
A Session is typically constructed at the beginning of a logical operation where database access is potentially anticipated.

...

It’s usually not very hard to determine the best points at which to begin and end the scope of a Session, though the wide variety of application architectures possible can introduce challenging situations.

A common choice is to tear down the Session at the same time the transaction ends, meaning the transaction and session scopes are the same. This is a great choice to start out with as it removes the need to consider session scope as separate from transaction scope.

...

As mentioned before, for non-web applications there is no one clear pattern, as applications themselves don’t have just one pattern of architecture. The best strategy is to attempt to demarcate “operations”, points at which a particular thread begins to perform a series of operations for some period of time, which can be committed at the end. Some examples:

- A background daemon which spawns off child forks would want to create a Session local to each child process, work with that Session through the life of the “job” that the fork is handling, then tear it down when the job is completed.
- For a command-line script, the application would create a single, global Session that is established when the program begins to do its work, and commits it right as the program is completing its task.
- For a GUI interface-driven application, the scope of the Session may best be within the scope of a user-generated event, such as a button push. Or, the scope may correspond to explicit user interaction, such as the user “opening” a series of records, then “saving” them.

As a general rule, the application should manage the lifecycle of the session externally to functions that deal with specific data. This is a fundamental separation of concerns which keeps data-specific operations agnostic of the context in which they access and manipulate that data.
Dann folgt eine Reihe von Beispielen, wie es gehen kann. Bitte bitte die Doku lesen.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Es ist zwar unüblich, aber ich zeige dir mal in einem Auschnitt-Format die Funktion, die für die Anmeldung zuständig ist.

Code: Alles auswählen

def connect_to_mysql(dbm_system, dbm_driver, db_user, db_passwd, db_host, db_port, db_name):
    from sqlalchemy.exc import SQLAlchemyError

    try:

        engine = create_engine(dbm_system+'+'+dbm_driver+'://' + db_user + ':' + db_passwd + '@' + db_host + ':' + db_port + '/' + db_name,
             encoding='utf8', echo=True)

        Base.metadata.create_all(engine)
        Session = sessionmaker(bind=engine)
        sess = Sesstion()
        
        return True
    except SQLAlchemyError as err:
        return err[0]
        
Wenn alles richtig war, wird der Boolean-Wert True zurückgegeben, damit sich im View-Teil das Fenster geschlossen werden kann. Und hier sitze ich gedanklich in einer Sackgasse. Der Anwender hat nun seine Anmeldung vorgenommen, bekommt True, sein Fenster schließt sich. Und wohin mit dem Session()-Objekt? Auch dahin, wo True geschickt wird? Ergibt für mich keinen Sinn.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Die Funktion heisst connect_to_mysql kann sich aber zu beliebigen Datenbanken verbinden und stellt keine Verbindungen her sondern prüft letztendlich nur ob man einer herstellen kann. Du merkst das Problem?

Die Funktion sollte die Session zurückgeben und ansonsten eine Exception auslösen.
BlackJack

@Sophus: Die API ist ja schon mal unschön weil die Funktion entweder `True` oder eine Zeichenkette zurück gibt. Das ist hässlich asymmetrisch. Eine Funktion die `True` zurückgibt, sollte als einzige andere Alternative `False` zurück geben. Sinnvoller wäre es die Verbindung zurück zu geben, also entweder die Engine oder die Sitzung und wenn etwas schief läuft einfach die Ausnahme *nicht* behandeln, damit der Aufrufer mitbekommt das etwas schief gelaufen ist.

Die Funktion wird ja (hoffentlich) nicht aus der GUI aufgerufen, sondern aus einem Objekt das die Geschäftslogik modelliert, oder zumindest einen Teil davon der sich mit der Datenhaltung beschäftigt. Und dort kann man sich dann auch die Engine oder die Sitzung merken.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@DasIch: Du hast Recht, der Name der Funktion ist unglücklich gewählt worden.

Ich habe auf Rat von jerch die Dokumentation von SQLAlchemy gelesen (den Abschnit: When do I make s sessionmaker?), und da steht folgendes:
When do I make a sessionmaker?

Just one time, somewhere in your application’s global scope. It should be looked upon as part of your application’s configuration. If your application has three .py files in a package, you could, for example, place the sessionmaker line in your __init__.py file; from that point on your other modules say “from mypackage import Session”. That way, everyone else just uses Session(), and the configuration of that session is controlled by that central point.

If your application starts up, does imports, but does not know what database it’s going to be connecting to, you can bind the Session at the “class” level to the engine later on, using sessionmaker.configure().

In the examples in this section, we will frequently show the sessionmaker being created right above the line where we actually invoke Session. But that’s just for example’s sake! In reality, the sessionmaker would be somewhere at the module level. The calls to instantiate Session would then be placed at the point in the application where database conversations begin.
Hier wird sogar vorgeschlagen, dass die Sessions einmal "global" erstellt werde soll, von mir aus in einem __init__-Modul.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Da steht dass man SessionMaker nur einmal erstellt, eine Session() sollte man aber nicht global haben.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Im View-Bereich wird ein QPushButton angeklickt:

Code: Alles auswählen

self.ui_login.pushButton_connect.clicked.connect(self.log_in)
Immernoch im View-Bereich wird dann die vom QPushButton ausgelöste Funktion in die Gänge gesetzt:

Code: Alles auswählen

    def log_in(self):
        server_host = unicode(self.ui_login.lineEdit_sevrer_host.text())
        username = unicode(self.ui_login.lineEdit_username.text())
        password = unicode(self.ui_login.lineEdit_password.text())
        database_name = unicode(self.ui_login.lineEdit_database_name.text())
        port = unicode(self.ui_login.lineEdit_port.text())

        # Now its important to find out which database was selected, because
        # each database has own module.
        if self.set_get_settings.dict_set_settings["Database"] == "MySQL":
            dbms_driver = u'pymysql'
            dbm_system = u'mysql'
            
            mysql_connection_result = manage_mysql.connect_to_mysql(dbm_system,
                                                                    dbms_driver,
                                                                    username,
                                                                    password,
                                                                    server_host,
                                                                    port,                                                                   
                                                                    database_name)
Und die Programmlogik liegt nun im Modul namens manage_mysql und dort wird dann die Funktion connect_to_mysql() aufgerufen. Das ist die Funktion, die ich euhc eben vorgestellt habe.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:

Die Erstellung einer Datenbanksitzung verläuft mit SqlAlchemy 2-stufig:

1. Erstellung der Verbindung - das `engine`-Objekt in Deinem Code
2. Erstellung einer Sitzung für Datentransfer und Transaktionslogik - die `session`

Für Ersteres brauchst Du die Verbindungs- und Logindaten. Für Zweiteres brauchst Du Ersteres. Diese Trennung kannst Du wunderbar auf Dein Programm übertragen:

1. Sammle Verbindungsdaten (z.B. mit Deinem Popup), am besten mit Verbindungsprüfung (Dein True/False Ergebnis)
2. Speichere die `engine` bzw. den Sessionmaker so, dass Du jederzeit hieraus Sessions bei Bedarf erzeugen kannst (applikationsweit vermutlich in Deinem Falle)
3. Interaktionen mit der DB stehen an - jetzt brauchst Du die Session (z.B. app.get_session()), mit dieser Session behandlest Du alle logisch zusammengehörigen DB-Aktionen
4. Session verwerfen - session.close()

Edit: Ich finde übrigens das Bsp. mit dem Contextmanager ganz chic - damit ist sichergestellt, dass Deine Anwendung die DB nicht in einem Kraut-und-Rüben-Zustand zurücklässt, falls die Anwendung mittendrin abschmiert. Zusätzlich zwingt Dich der Contextmanager dazu, die DB-Aktionen in "Sinneinheiten" zusammenzufassen und nicht wild über den Code verteilt die DB (mit einem globalen Session-Objekt) aufzusuchen
Zuletzt geändert von jerch am Mittwoch 10. Februar 2016, 15:57, insgesamt 1-mal geändert.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jerch: Und die Frage ist, wohin das Session()-Objekt speichern? Ich brauche die Session applikationsweit. Nach dem Login habe ich das engine-Objekt, und dieses Objekt binde ich an Sessionmaker, und im Anschluss erzeuge ein "sess"-Objekt, in der das Session()-Objekt gespeichert ist. Und jetzt muss das sess-Objekt erst einmal irgendwo gespeichert werden, damit ich applikationsweit darauf zugreifen kann. Meine Idee war ja, das Session()-Objekt in eine Klasse zu speichern, und sie mir bei Bedarf per Getter geben zu lassen. Und die Session wird es dann verworfen, wenn der Anwender sich selbst über den "Abmelde"-QPushbutton abmeldet oder das Programm schließt. Es wäre ja unsinnig nach jeder Aktion die Session zu verwerfen - denke ich mal.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Nein Du brauchst kein globales Sessionobjekt.

Wie sieht denn Dein Datenmodelling in der Anwendung aus? Wie kommen die Daten aus der DB zur Ansicht und wie Änderungen zurück?
Antworten