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.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

auf der Dokumentations-Seite von SQLAlchemy wird unter dem Kapitel Getting a Session beschrieben, wie man eine Session erhält. Alles super, nur was mich etwas nervt, ist, dass immer alles auf Modulebene erklärt wird. Das Ganze in Funktionen unterzubringen soll ja kein Problem sein. Aber mich beschäftigt folgendes Szenario:

Bei einer Arbeit mit MySQL ist es überlicherweise so, dass man sich erst anmeldet, und später mit den Datensätzen arbeitet. Selten, dass man seine Anmelde-Zugangsdaten durchgibt und gleich arbeiten geht. Dazu soll ja der Sessionmaker unter anderem gut sein.

Nun habe ich folgendes konzipiert und hätte eure Meinung:

Code: Alles auswählen

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

SESSION = ''

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
    SESSION = Session()
    

def insert_data():
    # work with sess
    myobject = MyObject('foo', 'bar')
    SESSION.add(myobject)
    SESSION.commit()

Wäre das so in Ordnung? Mir ging es bei der Überlegung darum, wie ich an die Session() komme. Man könnte ja Session() per return zurückgeben lassen, aber das will ich ja nicht. Denn wie ich bereits beschrieb, ist es ja so, dass ich nach der Anmeldung nicht gleich sofort arbeiten möchte. Also, warum soll ich mir was zurückgeben lassen, wenn ich es nicht brauche? Als dachte ich mir, speichere ich das Session()-Objekt in die Variable SESSION. Und so kann ich beim Manipulieren der Datensätze getrost darauf zugreifen. Allerdings glaube ich, dass dieser Weg wenig elegant.

Also freue mich mich über eure Meinung.
BlackJack

@Sophus: Das ist 'ne globale Variable. Nicht machen. Das hat auch nichts mit SQLAlchemy im besonderen zu tun, das ist ja generell keine gute Idee.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Wie und wo würdest du das Session()-Objekt unterbringen, so das zu zu einem späteren Zeitpunkt darauf zugreifen kannst um Datensätze zu manipulieren? Bedenke, dass ich mich weit vorher anmelde/einlogge, aber später mit den Datensätzen arbeite. Mir schwirrt da zum Beispiel das Wörterbuch im Kopf herum. So sind die Daten eher gekapselt?
BlackJack

@Sophus: Das kann man so allgemein nicht sagen. Kommt halt darauf an wie das Programm aufgebaut/strukturiert ist.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Neben der Variante mit dem Wörterbuch würde mir die Klassen-Arbeit einfallen und dies würde dann wie folgt aussehen:

Code: Alles auswählen

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

class SessionSaver(object):
    def __init__(self):
        self.my_session = ''

    def save_session(self, sess_obj):
        self.my_session = sess_obj

    def get_session(self):
        return self.my_session

ses = SessionSaver() 

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
    ses.get_session( Session())

def insert_data():
    get_my_session = ses.get_session()
    myobject = MyObject('foo', 'bar')
    get_my_session.add(myobject)
    get_my_session.commit()
Was mich nur etwas beunruhigt, ist, dass die Variable ses ebenso global ist, richtig? Wobei ich mich damit beruhige, ist, bevor man etwas in der SessionSaver()-Klasse was bewirken kann, muss man schon gezielter vorgeben, indem die entsprechende Funktionen in der Klasse aufgerufen werden. Und somit hätte ein auf Seiteneffekt basierender Fehler keinerlei Einfluss - zumindest behaupte ich das jetzt mal. Also ist somit eine gewisse Kapselung gegeben oder?
BlackJack

@Sophus: Das ist ganz einfach nur eine globale Variable die kein Stück besser ist als eine mit ``global`` gesetzte.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hmmmh.. Ich komme dann nicht weiter, wie ich eine erzeugte Session für einen späteren Zeitpunkt aufbewahren kann. Denn beim Login in die MySQL-Datenbank wird in meinem Beispiel die connect_to_database()-Funktion aufgerufen. Damit ich aber in einem späteren Zeitpunkt, ohne mich erneut um den Login kümmern zu müssen, mit den Datensätzen arbeiten kann muss ich auf das bereits zuvor erzeugte Session-Objekt zugreifen - ohne eine nee Session-Instanz aufbauen zu müssen.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Was mir dabei noch einfiel:

Ich kann ja das self.my_session-Attribut in meiner SessionSaver()-Klasse von außen auf Privat umstellen, indem ich das Attribut in self.__my_session umwandle. So ist zwar die ses-Variable in meinem Beispiel weiterhin global, aber das eben genannte Attribut kann von außen nicht ohne weiteres geändert werden. Der Zugriff ist von außen nicht direkt möglich. Dazu habe ich die beiden Methoden/Funktionen save_session() und get_session() öffentlich zugeänglich gelassen. Auf diesem Weg ist man dann gezielt und explizit dazu gezwungen über die Funktionen/Methonden innerhalb der Klasseninstanz zu arbeiten. Und somit hätte ich die sogenannte Datenkapselung erfüllt oder? Und es entsteht (hoffentlich) kein Chaos. Denn man greift dann bewusst auf etwas in der Klasse zu. Und ich kann man Session() für später aufheben.

Komplett würde dann der Code wie folgt aussehen:

Code: Alles auswählen

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

class SessionSaver(object):
    def __init__(self):
        self.__my_session = "" # This attribute is private now

    def save_session(self, sess_obj):
        self.my_session = sess_obj

    def get_session(self):
        return self.my_session

ses = SessionSaver() 

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
    ses.get_session( Session())

def insert_data():
    get_my_session = ses.get_session()
    myobject = MyObject('foo', 'bar')
    get_my_session.add(myobject)
    get_my_session.commit()
BlackJack

@Sophus: Es gibt in Python kein „private“ und doppelte führende Unterstriche haben hier schon mal gar nichts zu suchen. Letztendlich ist das auch weiterhin effektiv ”public” und nicht gekapselt weil es einen getter und einen setter gibt, womit die beiden Methoden überflüssig sind. Ob sich jemand nun bewusst dazu entscheidet eine Methode aufzurufen oder ein Attribut zu setzen ist egal solange er das immer und von überall her tun kann, eben weil das global zur Verfügung gestellt wird. Damit hast Du das Chaos.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Entschuldige, wenn ich dämlich frage, aber durch die doppelten Unterstriche vor dem Attribut habe ich die Zugriffsrechte festgelegt, und zwar, dass dieses Attribut nicht mehr ohne weiteres von außen zugänglich ist. Wenn ich das nach dem Erzeugen des Objektes der SessionSaver()-Klasse versuche das Attribut zu ändern, passiert gar nichts. Das Attribut bleibt unverändert. Nur über die Methoden/Funktionen ist der Zugriff gegeben.

EDIT: Ich sollte Änderungen auch komplett durchziehen. Daher den Quelltext nochmal, nur diesmal ohne syntaktische Fehler.

Code: Alles auswählen

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

class SessionSaver(object):
    def __init__(self):
        
        self.__my_session = "" # This attribute is private now

    def save_session(self, sess_obj):
        self.__my_session = sess_obj

    def get_session(self):
        return self.__my_session

ses = SessionSaver() 

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
    ses.get_session( 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)
    get_my_session.commit()
Zuletzt geändert von Sophus am Dienstag 9. Februar 2016, 23:39, insgesamt 1-mal geändert.
BlackJack

@Sophus: Damit hast Du keine Zugriffsrechte festgelegt. Das Attribut heisst nur anders. Diese Namensänderung ist aber dokumentiert, also kann man da trotzdem auch noch von ausserhalb ohne die Methoden drauf zugreifen. Das ist dazu gedacht um bei Mehrfachvererbung oder sehr tiefen Vererbungshierarchien Namenskollisionen zu vermeiden, nicht um Zugriffsschutz zu bieten. Da man beides in Python so gut wie nie hat/macht, sind doppelte führende Unterstriche zu 99% falsch verwendet weil der Autor sie für etwas anderes hält als sie sind.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Tatsächlich. Denn ich bin bisher falsch vorgegangen. Denn wenn ich wie folgt vorgegangen bin, bekam ich immer einen Traceback.

Code: Alles auswählen

ses = SessionSaver()
print "Output", ses.__my_session
Traceback (most recent call last):
File "D:\Dan\Python\xarphus\examples\var_class.py", line 61, in <module>
print "Output", ses.__my_session
AttributeError: 'SessionSaver' object has no attribute '__my_session'
Aber du hast Recht, ich kann schreibend darauf zugreifen, und dann hinterher lesend zugreifen, wenn es wie folgt aussieht:

Code: Alles auswählen

ses = SessionSaver()
ses.__my_session="Some_String"
print "Output", ses.__my_session
Hättest du denn eine Idee, wie man ein Session-Objekt unterbringen kann? Denn das Herumreichen des Objektes von Funktion zu Funktion, in Form eines Argumentes, scheint mir äußerst umständlich und unrentable. Denn sobald ich mich auf den Datenbank-Server eingeloggt habe, ist noch nicht sicher gestellt, dass dieses Session()-Objekt gerade gebraucht wird. Sobald ich in einem späteren Zeitpunkt einen Datensatz speichern möchte, muss ich auf das Objekt zugreifen können. Und da erscheint mir das mit der Argument-Strategie unmöglich.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Was Du mit dem SessionSaver machst, kann man tun, wenn Code zur Initalisierung der Anwendung gehört und dann unveränderlich bleibt (eine Konstante ist):

[Codebox=python file=Unbenannt.py]
# Datei a.py

def init_some_needed_stuff():
...

NEEDED_STUFF = init_some_needed_stuff()


# Datei b.py
from a import NEEDED_STUFF as WOTEVER

... usw.[/Codebox]
Damit ist NEEDED_STUFF aus a.py global verfügbar in b.py als WOTEVER. Eine DB-Verbindung würde ich da nicht dazu zählen, da diese eine beschränkte Ressource ist, welche im Verlauf fehlschlagen kann (veränderlicher Zustand).

NB: Was ist mit der Codebox passiert?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

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

Code: Alles auswählen

#!/usr/bin/env python
 
class Car:
 
    __maxspeed = 0
    __name = ""
 
    def __init__(self):
        self.__maxspeed = 200
        self.__name = "Supercar"
 
    def drive(self):
        print 'driving. maxspeed ' + str(self.__maxspeed)
 
redcar = Car()
redcar.drive()
redcar.__maxspeed = 10  # will not change variable because its private
redcar.drive()
Hier wird ja auch versucht schreibend auf das Attribut zuzugreifen, aber der Print-Output zeigt nach wie vor die Zahl 200 an.

Und zum Thema Datenbank-Zugriff. Leider bin ich aus deiner Ausführung nicht schlau - liegt daran, dass ich wohl nicht alles kapiere. Ich möchte dem Anwender nicht jedesmal, wenn er einen Datensatz manipulieren bzw. einfügen will, dazu zwingen dass er die Zugangsdaten eingibt. Wäre ein wenig unwirtschaftlich. Also arbeitet man ja mit dem Session()-Objekt. Und in einigen Beispielen wird das Arbeiten mit der Session gut dokumentiert, aber eben auf Modulebene. Bei den Beispielen wird gleich alles in einem Rutsch erledigt. Die Verbindung wird aufgebaut und gleich einen Datensatz eingetragen. Alles fein, aber dies entspricht nicht der Realität. Und da wir wissen, dass man Datensätze zeitlich wesentlich später bearbeitet, möchte ich das Session()-Objekt (solange man es nicht braucht) irgendwo hin verlegen. Ich könnte auch jedesmal ein neues Session()-Objekt erzeugen, aber dann hätte ich unterschiedliche Sessions, und genau das will ich vermeiden. Ich möchte mit dem am Anfang erzeugen Session()-Objekt weiterarbeiten solange, bis der Anwender sich wieder aus der Datenbank ausloggt.
BlackJack

@Sophus: Nein, so kannst Du nicht schreibend/lesend auf das Attribut zugreifen, das hat einen *anderen* Namen wenn man von ”aussen” kommt. Du erstellst einfach ein ganz normales zusätzliches Attribut. Wie das Beispiel in Deinem letzten Beitrag ja auch zeigt.

Die verlinkte Seite ist schlecht, weil der Autor offenbar unbeding glauben möchte es gäbe private Attribute auf die man nicht zugreifen könnte. Ausserdem sind die Zuweisungen auf Klassenebene in dem Beispiel total unsinnig. IMHO vermischt er auch Kapselung mit Zugriffsschutz. Das kann man zusammen nutzen, in Programmiersprachen die Zugriffsschutz bieten, aber Kapselung ist davon unabhängig. Das ginge in Python sonst ja überhaupt nicht, da es keinen Zugriffsschutz gibt.

Dir mag die Argument-Strategie unmöglich erscheinen. Dann machen viele Programmierer anscheinend etwas unmögliches. :-)
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.
Antworten