SQLAlchemy: Arbeiten mit mehreren QThreads?

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich denke, du machst es immer noch nicht so wie vorgesehen. Dieses ManagedSessionScope() ist schon komisch gemacht, weil es anscheinend die Original-Session und die ScopedSession zusammen ausliefert (wovon du später die "falsche" verwendest). Du packst quasi etwas in Schutzfolie, holst es aber sofort wieder aus der Folie raus und glaubst, dass der Versand jetzt sicher sei. Lass die ScopedSession doch einfach komplett weg. Das hätte bei deinem Vorgehen den selben Effekt.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@snafu: Wo wird die Originale Session ausgeliefert? ManagedSessionScope() liefert beim Aufruf der __init__() einen Tuple. Einmal die Rückgabe von scoped_session() und dann die Klasse selbst. Aber die Klasse selbst verwende ich erst mal nicht. Ich habe keine Ahmung, wie du es sonst meinst. Vielleicht denke ich auch nur verquert.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@snafu: Ich glaube, du bist von der falschen Namensgebung meinerseits irritiert. Also mein ManagedSessionScope()-Objekt liefert folgendes zurück: (extrem verkürzt)

Code: Alles auswählen

class ManagedSessionScope(object):
    def __init__(self, url=None,
                 echo_verbose=True,):
        [...]
        self._Engine = create_engine(self.url, echo=self.echo_verbose) 

        self._Session = scoped_session(sessionmaker(bind=self._Engine))

    def __enter__(self):
        self.session = self._Session     # this is now a scoped session
                                                      # sqlalchemy.orm.scoping.scoped_session
        return self.session, self

    [...]
Du siehst, ich bekomme nicht die originale Session zurück. Warum ich mir dann auch noch self mit zurückschicken lasse? Eigentlich könnte ich sie auch weglassen. Aber verwenden tue ich die self nicht. Ich glaube, das hat dich ein wenig verwirrt. War mein Fehler.

Gedankenproblem:
Aber ich habe ein kleines Gedankenproblem. In meiner zurvor vorgestellten Skizze wird ManagedSessionScope() durch die with-Anweisung direkt am Anfang des Programmstart geöffnet. Dadurch habe ich dann eine session, die ich applikationsweit benutzen könnte. Aber das war ja nur ein Beispiel. In meinem richtigen Projekt ist es so, dass sich der Benutzer später in die Datenbank anmelden kann - wann immer er/sie will. Folgende Situation, der Benutzer startet das Programm, geht zu einem späteren Zeitpunkt in die Menüleiste, und geht auf "Anmelden", ein neues Fenster, geerbt von QDialog(), wird erzeugt. Benutzer gibt seine Anmelde-Daten ein, klickt dann auf die "Ok"-Schaltfläche. Anmeldung läuft durch. Wenn alles gut ging, bekommt der Benutzer eine MessageBox angezeigt, mit der Information, dass alles wunderbar verlaufen ist, ansonsten werden entsprechende Fehlermeldungen über die MessageBox ausgegeben. Sobald der Benutzer diese MessageBox, mit der erfolgreichen Meldung, gesehen hat, schließt sich das Anmelde-Fenster. Und genau JETZT habe ich ein Problem. Wo halte ich die Session während dessen am "leben"? Anders gefragt: wie/wo soll ich die with-Anweisung die ganze Zeit geöffnet lassen, damit die Applikation auch nur mit einer gültigen Session arbeitet? Ich meine, in meiner vorherigen Skizze war die with-Anweisung durch sys.exit(app.exec_()) offen geblieben, denn dies wirkte fast wie eine while-Schleife. Meine erste Überlegung ist, dass ich einen weiteren Thread konstruiere, der nur dafür da ist, die with-Anweisung offen zu halten, damit wir eine gültige Session applikationsweis haben? In diesem besagten Thread müsste ich dann einen QTimer() einbauen, diesen Timer dann in den Korpus der with-Anweisung packen. Ansonsten fällt mir keine Idee ein, wie ich eine with-Anweisung zum späteren Zeitpunkt ununterbrochen(solange wie die Applikation läuft) geöffnet halten soll. Sonst hätte ich ja den gleichen Effekt, die ich bisher gemacht habe, dass jedesmal, bei Gebrauch erneut die with-Anweisung durchgeführt wurde. Würde ich dies tun, hätte ich jedesmal eine andere Session, und keine einzig gültige Session. Ich stecke gerade gedanklich fest.
BlackJack

@Sophus: Du gibst bei `__enter__()` ein Tupel zurück? Aber das hat doch dann gar keine `__exit__()`-Methode!
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Ich werde den Tuple wieder entfernen. Ihr habt Recht, es war eine Dumme Idee :) Ich gebe nur noch die scoped_session zurück.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Die Frage ist, warum du überhaupt die scoped_session nochmals wrappst, denn das ist ja schon ein Wrapper. Wäre es nicht einfacher, eine "normale" Funktion zu programmieren, die eine vorkonfigurierte Sitzung als scoped_session liefert?

Im Übrigen ist es keine so gute Idee, eine Session während des gesamten Programmlaufs offen zu halten. Du solltest besser die Nutzereingaben aus der GUI sammeln und wenn er auf "Übermitteln" oder sowas klickt, dann wird die Session geöffnet und mit den Daten gefüttert, die commited werden sollen. Danach schließt man sie wieder. Und während dieses Ablaufs kann die offene Session (ScopedSession) dann halt auch - abhängig von deiner Programmstruktur - herumgereicht werden.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@snafu: Aber dann lande ich ja bei dem Ausgangspunkt, den du kritisiert hast. Ursprünglich sah die Struktur wie folgt aus, in der GUI-Klasse (MyCustomDialog()) gab es eine start_all_selection()-Methode, die so konstruiert war, dass bei jedem Klick auf die Schaltfläche die ManagedSessionScope() geöffnet wurde, die Rückgabe von scoped_session() an die Trheads weiterreichte, und nach getaner Arbeit wurde die ManagedSessionScope() automatisch wieder beendet. Und das hast du unter anderem doch kritisiert?

Code: Alles auswählen

class MyCustomDialog(QDialog):
 
    finish = pyqtSignal()
 
    def __init__(self, scoped_session=None, parent=None):
        QDialog.__init__(self, parent)

        [...]

    def start_all_selection(self):
    [...]
        with ManagedSessionScope(url=self._url) as session_that_scoped:
            
            for category, combobox in list_tuple:
                combobox.clear()
                
                self.start_thread(combo_box=combobox,session=session_that_scoped, category=category)
Daher war meine Idee, einen Thread zu konstruieren, der die Session solange offen hält, ehe der Benutzer in der Menüleiste auf "Abmelden" klickt, und der laufende Thread beendet wird. Im Thread wäre dann eine while-Schleife, die dann vor dem Beenden des Threads unterbrochen wird, so dass man den Korpus der with-Anweisung verlässt. Klickt also der Anwender auf "Abmelden", wird die while-Schleife im Thread beendet und der Thread wird beendet, und die Session ist geschlossen. Beim nächsten Mal, wenn er sich entschließt, sich anzumelden, wird wieder ein neuer Thread erzeugt, eine neue Session eröffnet und so weiter und so fort.

Ungefähr so: Die Signale habe ich jetzt mal der Übersichthalber ausgelassen, wollte jetzt nicht den vollständigen Quelltext kopieren. Aber so dachte ich mir, könnte das aussehen?

Code: Alles auswählen

class SessionTask(QObject):
 
    def __init__(self, parent=None):
        QObject.__init__(self, parent)
        self.stop_loop = False
 
    def run(self):
        with ManagedSessionScope(url=self._url) as session:
            # do stuff with given scoped_session()

            while not self.stop_loop:  pass           
 
    def stop(self):
        self.stop_loop = True
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Was hat denn eine Session bei *Bedarf* zu oeffnen damit zu tun, das die dann ploetzlich (und fehlerhafterweise) von allen moeglichen Threads geteilt werden muss? Dieser Schluss ist Unsinn.

Jeder Thread braucht seine eigene Session, aber die muss er bei Bedarf oeffnen, und dann wieder schliessen, wenn er sie nicht braucht. Und das ist nicht ueber seine ganze Lebenszeit, sondern waehrend einer bestimmten semantisch sinnvollen Aktion, zB dem auslesen von Werten fuer die Dropdowns, oder dem eintragen eines neuen Datensatzes. Kann also durchaus eine handvoll Statements beinhalten, aber eben nicht ueber die ganze Lebenszeit des Programms.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@__deets__: Wenn ich dich richtig verstehe, für jeden Thread eine with-Anweisung konstruieren, und nicht so wie ich es bisher gemacht habe, eine with-Anweisung für alle Threads, die dann am Ende wieder geschlossen wird, wenn der letzte Thread fertig ist? Das heißt in meinem Beispiel, dass ich ganze 8 Twith-Anweisungen anwende?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ja, wobei du das komisch ausdrueckst. Je nach Programmablauf und Code-Struktur kann das auch nur *eine* with-Anweisung sein, aber die wird halt oft aufgerufen. Oder es sind 2000, weil du eine um jeden SQL-code-block schreibst. Aber das entscheidende ist: kein Thread teilt seine Session mit einem anderen, das fuehrt zur Katastrophe.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@__deets__: Das ich mich "komisch" ausdrücke, ist wohl meine Schwäche. Wobei ich es mir eher komisch vorstelle. Sagen wir mal, ich habe 100 Threads (nur rein hypothetisch), dann habe ich auch 100 with-Anweisungen. Der Quelltext bläht sich dadurch auf? Und wenn die session als scoped_session() vorbereitet wurde, dann kann man doch diese Session unter den Threads teilen, also die scoped_session() sich darum kümmern, dass die Threads miteiander nicht in Berührung kommen. Daher nahm ich an, dass man eine with-Anweisung anbringen kann, und im Korpus der with-Anweisung die ganzen Threads abarbeiten und die scoped_session() an die jeweilige Threads verteilen?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wieso blähen denn 100 threads den Quellcode auf? Hier starte ich nicht nur hypothetisch 1000 Threads, und das in weniger als 1000 Zeilen, womit deine These ja ad absurdum gefuehrt wird:

Code: Alles auswählen

import threading

def tuwas():
      with scoped_session() as session:
             session.execute("etwas")

threads = [threading.Thread(target=tuwas) for _ in xrange(1000)]
[t.start() for t in threads]
[t.join() for t in threads]
Und zum gefuehlt 100sten mal: Sessions duerfen NICHT zwischen Threads geteilt werden. Was glaubst du denn, was eine Session eigentlich *ist*? Du kannst auch keine einzelne Datenbankverbindung nicht zwischen 1000 Threads teilen (normalerweise), weil eine solche Verbindung sich dann in jedem Thread gegenseitig auf die Fuesse tritt. Offene Cursor, Position auf der die stehen etc.

Also, noch einmal: EINE SESSION PRO THREAD.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@__deets__: Zwei Dinge. Erstens: Das sich dein Quelltext nicht aufbläht ist doch klar. Du erstellst zwar 1000 Threads, aber all die Threads führen nur ein und die selbe Aufgabe aus. In meinem Fall ist es so, dass jeder Thread jedes Eingabefeld (QComboBox(), QLineEdit()... etc.) befüllen. Wesen Aussage führt ad absurdum? Zweitens: Aus deinen Ausführungen werde ich auch nicht schlau, daher zum 101. Male. Du redest die ganze Zeit von Session, demzufolge weiß ich nicht ob du von scoped_session() redest oder von den normalen Session? Denn scoped_session(), die als Rückgabe <class 'sqlalchemy.orm.scoping.scoped_session'> liefert, ist thread-sicher, als im Gegensatz einer normalen Session. So meinem Verständnis: Solange ich NUR die Rückgabe von scoped_session() nicht aufrufe, habe ich keine normale Session, demzufolge frage ich mich, wieso man an dieser Stelle nicht die Rückgabe der scoped_session() an die Threads verteilen kann? Ist nicht gerade der Vorteil der scoped_session(), dass sie sich darum kümmert, dass sich die Threads nicht gegenseitig in die Knie schießen?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn man 100 verschiedene Funktionalitaeten erstellt, dann muss man 100 verschiedene Dinge programmieren, mit diversene Statements, Funktionen, Klassen, Modulen, Paketen sogar. Nichts davon ist besonders. Warum ist dann ein solches with-Statement etwas, das dir solche Sorgen bereitet, und "den Code aufbläht"?

Und die scoped_session ist thread-sicher, weil sie einen thread-local storage verwendet um eine Session anzulegen.

Du redest die ganze Zeit davon, dass du den Wert, der dir durch den Aufruf von eben dieser scoped_session zurueckgegeben wird *verteilen* willst. DAS ist FALSCH.

Du kannst problemlos das hier machen:

Code: Alles auswählen

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

session_factory = sessionmaker(bind=some_engine)
Session = scoped_session(session_factory)

def tuwas():
      # RICHTIG WEIL IMPLIZIT EIN SESSION-OBJEKT PRO THREAD
      print(Session.query(MyClass).all())

threads = [threading.Thread(target=tuwas) for _ in xrange(1000)]
[t.start() for t in threads]
[t.join() for t in threads]
Das hier ist, wovon du die ganze Zeit redest (zumindest klingt es so, wenn du "die Rückgabe von scoped_session() an die Trheads weiterreichte"

Code: Alles auswählen

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

session_factory = sessionmaker(bind=some_engine)
Session = scoped_session(session_factory)

def tuwas(session):
      # GANZ DOLLE FALSCH, WEIL GLEICHES OBJEKT IN ALLEN THREADS
      print(session.query(MyClass).all())

my_session = Session()
threads = [threading.Thread(target=tuwas, args=(my_session,)) for _ in xrange(1000)]
[t.start() for t in threads]
[t.join() for t in threads]
BlackJack

Als Ergänzung: Wenn man das zweite Beispiel von __deets__ in Zeile 12 folgerndermassen ändert…

Code: Alles auswählen

threads = [threading.Thread(target=tuwas, args=(Session(),)) for _ in xrange(1000)]
…also im Hauptthread 1000 mal `Session` aufruft und das Ergebnis an die Threads verteilt, hat man genau das *gleiche* Problem, denn `Session()` liefert in ein und dem selben Thread immer das *selbe* `Session`-Exemplar, nämlich das für den Hauptthread.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@__deets__: Ich habe meine grobe Skizze etwas umstrukturiert, weil ich eine bessere Teilung vornehmen möchte. Der Quelltext ist extrem gekürzt und nicht lauffähig! Wenn ich also __deets__s, snafus und BlackJacks Ratschläge streng verfolge, dann sieht meine Umstrukturierung und mein Gedankenproblem wie folgt aus.

Zunächst ein paar Anmerkungen zu meiner Umstrukturierung. Ich habe eine Teilung zwischen Session und Engine vorgenommen. Es gibt die ManagedEgine()-Klasse, die nur einmalig aufgerufen wird. In dieser Klasse wird in einem Atemzug die create_engine() und auch der sessionmaker() erstellt. Es sind beides Objekte, die man nur einmal erstellt. Dann haben wir die ManagedSessionScope()-Klasse, die bei Bedarf immer wieder aufgerufen werden kann.

Dann haben wir die MyCustomDialog()-Klasse, die für die View verantwortlich ist. In dieser Klasse sehen wir einmal die log_in_database()-Methode. Klickt der Benutzer auf die Schaltfläche "LogIn" wird er/sie angemeldet. Es wird eine Instanzt der ManagedEgine()-Klasse erstellt und sessionmaker() wird klassenweit in das self.managed_engine-Attribut gespeichert. Nach dem erfolgreichen LogIn klickt der Benutzer auf die Schaltfläche "Daten abfragen" und ruft dadurch die start_all_selection()-Methode auf. Und genau dort habe ich das Problem. Aber dazu komme ich noch.

Darüber hinaus habe ich eine Worker()-Klasse, die durch die aufgerufenen Threads verwaltet werden. In dieser Worker-Klasse sehen wir, dass dort mit der MasterDataManipulation()-Klasse gewerkelt wird.

Un zu guter Letzt haben wir eine MasterDataManipulation() -Klasse, in welcher die ganzen Abfragen und weitere Operatoren (Update, Add, Delete) befinden. Diese Klasse wird bei jedem Thread erneut instanziiert.

Gedankenproblem:
Ich habe bisher die Session deshalb stets an die Threads übergeben, weil die Sessions in den MasterDataManipulation()-Klassen gebraucht werden, in welcher die Operationen stattfinden. Ursprünglich war es so gedacht: Bei jedem Thread-Start wird jeweils eine Instanz von der MasterDataManipulation()-Klasse erstellt, die Sessions werden dorthin übergeben und anschließend werden die Abfragen bearbeitet. Wenn ich das wie __deets__ und snafu mache, dann müsste ich die ganzen Abfragen in meinen Hauptthread (in dem Falle wäre es die GUI-Klasse) verlegen. Jedoch wollte ich es vermeiden, weil ich nicht will, dass die GUI einfriert, wenn die ganzen Abfragen im Hautthread ausgeführt werden. Und genau da liegt mein Gedankenproblem.

Code: Alles auswählen

[...] # All imports are here

[...]

class ManagedEgine(object):
    def __init__(self,
                 dbms=None,
                 dbdriver=None,
                 dbuser=None,
                 dbuser_pwd=None,
                 db_server_host=None,
                 dbport=None,
                 db_name=None,
                 echo_verbose=True):
        
        self.dbms = dbms
        self.dbdriver = dbdriver
        self.dbuser = dbuser
        self.dbuser_pwd = dbuser_pwd
        self.db_server_host = db_server_host
        self.dbport = dbport
        self.db_name = db_name
        self.echo_verbose = echo_verbose
        url = '{}+{}://{}:{}@{}:{}/{}'.format(
            self.dbms, self.dbdriver, self.dbuser, self.dbuser_pwd, self.db_server_host, self.dbport, self.db_name)

        _engine = create_engine(url, echo=self.echo_verbose)
        
        # I have to persist all tables and create them
        Base.metadata.create_all(_engine)

        # Create the session factory
        self.session_factory = sessionmaker(bind=_engine)
		
class ManagedSessionScope(object):
    def __init__(self, engine=None):
 
        self._enginge = engine

        self.session = None

        self._Session = scoped_session(self._enginge)
 
    def __enter__(self):
        self.session = self._Session # this is now a scoped session
                                     # sqlalchemy.orm.scoping.scoped_session
        return self.session
 
    def __exit__(self, exception, exc_value, traceback):
        try:
            if exception:
                self.session.rollback()
            else:
                self.session.commit()
        finally:
            self.session.close()

class MasterDataManipulation(object):

    def __init__(self):

    def select_all(self, category):

        dict_store_session_query = {
									'person_gender': lambda: self._session.query(PERSON_GENDER),
                                     [...]
									 }

        try:

            for record in dict_store_session_query[category]():
                if category == 'person_gender':
                    yield record.id, record.gender
            self._session.commit()

        except Exception:
            self._session.rollback()
			
class Worker(QObject):
 
    [...] # Signals are here   
    def __init__(self,
                 combo_box=None,
                 category=None,
                 time_interval=None,
                 parent=None):
        QObject.__init__(self, parent)

        [...]

    def init_object(self):
        self.timer = QTimer()

        master_data_manipulation = MasterDataManipulation(session_object=self.new_scope)
        query_data=master_data_manipulation.select_all
        self._element = query_data(self.category)

        self.timer.setSingleShot(False)
        self.timer.setInterval(int(self.time_interval))
        self.timer.timeout.connect(self.populate_item)
        self.timer.start()
	
	[...]
			
class MyCustomDialog(QDialog):
 
    [...] Here are some signals
 
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
		[...]
		self.managed_engine = None
		[...]
    def log_in_database(self):
		# User wants to program to log in
        dbms ="mysql"
        dbdriver="pymysql"
        dbuser="root"
        dbuser_pwd="xxx"
        db_server_host="localhost"
        dbport=3306
        db_name="test"
        echo_verbose=True

        try:

            managed_engine = ManagedEgine(dbms=dbms,
                                               dbdriver=dbdriver,
                                               dbuser=dbuser,
                                               dbuser_pwd=dbuser_pwd,
                                               db_server_host=db_server_host,
                                               dbport=dbport,
                                               db_name=db_name,
                                               echo_verbose=echo_verbose)
            
            self.managed_engine = managed_engine.session_factory
            
        except SQLAlchemyError:
            desired_trace = format_exc(exc_info())
            print "desired_trace", desired_trace

	def start_thread(self,
                     combo_box=None,
                     session=None,
                     time_interval=None,
                     category=None):

        task_thread = QThread(self)

        task_thread.work = Worker(new_scope=session,
                                  time_interval=time_interval,
                                  category=category,
                                  combo_box=combo_box)

        task_thread.work.moveToThread(task_thread)
		[...]
		task_thread.started.connect(task_thread.work.init_object)
		[...]
		
	[...]
    def start_all_selection(self):
		[...]
        list_tuple = [
                    ("person_salutation", self.combo_person_salutation), 
                    ("person_title", self.combo_person_title), 
                    ("person_gender", self.combo_person_gender), 
                    ("person_religion", self.combo_person_religion),
                    ("person_eye_color", self.combo_person_eye_color),
                    ("person_hair_color", self.combo_person_hair_color),
                    ("person_relationship_status", self.combo_person_relationship_status),
                    ("person_nationality", self.combo_person_nationality)
                       ]

        try:
            #with ManagedSessionScope(engine=self.managed_engine) as session_that_scoped:
			# Here we don't want to share one session with other threads
			# What should I do?
                
			for category, combobox in list_tuple:

				combobox.clear()

				self.start_thread(combo_box=combobox,
								  session=session_that_scoped,
								  time_interval=100,
								  category=category)

        except SQLAlchemyError as err:
            [...] # Do stuff with Exception
        except OperationalError as OpErr:
            [...] # Do stuff with Exception
Zuletzt geändert von Sophus am Montag 14. August 2017, 17:04, insgesamt 4-mal geändert.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Sophus hat geschrieben: Wenn ich das wie __deets_ und snafu mache, dann müsste ich die ganzen Abfragen in meinen Hauptthread (in dem Falle wäre es die GUI-Klasse) verlegen. Jedoch wollte ich es vermeiden, weil ich nicht will, dass die GUI einfriert, wenn die ganzen Abfragen im Hautthread ausgeführt werden. Und genau da liegt mein Gedankenproblem.
Wie *kommst* du denn auf sowas? Ich habe dir doch Code gepostet, der belegt, dass ich Abfragen ueber die Session in verschiedenen Threads mache. JEDER hier hat verstanden, was du willst.

Du benutzt die scoped session einfach komplett falsch. Du brauchst davon genau *eine*, und die kannst du auch ausnahmsweise auf Modul-Ebene anlegen (so macht SQLAlchemy das selbst, und erklaert ausfuehrlich, warum das ausnahmsweise nicht boese(tm) ist)

Ich habe jetzt hier schon mehrfach code gepostet, der das illustriert. Schmeiss mal deinen ganzen Managed*-Wahnsinn weg, und erstell dir ein ganz einfaches Qt-Programm welches

- *eine* scoped session auf Modul-Ebene erzeugt.
- einen Dialog hat, der auf Knopfdruck eine Query absetzt und die Ergebnisse einfach ausdruckt mit print
- einen Hintergrund-Thread started, welcher periodisch dasselbe macht, und einfach die Ergebnisse ausdruckt mit print.

Und streu ein "print(id(Session())" ein, um dich zu ueberzeugen, dass der Thread eine andere Session benutzt.

Natuerlich kann man statt einer globalen Variablen auch das scoped_session-Objekt in die Threads (und jede moegliche andere Stelle, die SQL taetigt) reinreichen. Aber das ist nicht die Art, wie man das in SQLAlchemy ueblicherweise macht. Und *wenn* man es macht, dann reicht man die eine scoped_session rein, und legt nicht ueberall eine neue an. So wie du in Zeile 43.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Hier mal ein multi-threaded und self-contained Beispiel das belegt, dass der Ansatz funktioniert und auch wunderbar parallel Daten abholt:

Code: Alles auswählen

import time
import random
import threading

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker


session_factory = sessionmaker()
Session = scoped_session(session_factory)


def work():
    print(id(Session()))
    for _ in xrange(5):
        time.sleep(random.random() * 3)
        res = Session.execute("SELECT * FROM test LIMIT 3")
        for row in res:
            print(threading.currentThread().getName(), row)
            time.sleep(.1)


def main():
    eng = create_engine('sqlite:////tmp/test.db')
    Session.configure(bind=eng)

    Session.execute('CREATE TABLE IF NOT EXISTS test ( data int)')
    for i in xrange(1000):
        Session.execute('INSERT INTO test (data) values (:param)',  { "param": i})

    Session.commit()

    print(id(Session()))
    threads = [threading.Thread(target=work) for _ in xrange(10)]
    [t.start() for t in threads]
    [t.join() for t in threads]

if __name__ == '__main__':
    main()
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@__deets__: Ich will nicht auf Modul-Ebene arbeiten, denn jede Klasse, die ich hier extrem verkürzt dargestellt habe, präsentiert bei mir jedes Modul. Für jede Klasse ein Modul. Demzufolge wüsste ich nicht woich die scoped_session(), zusammen mit create_engine() und sessionmaker() auf Modulebene anlegen soll? In einem Modul (GUI) findet die Anmeldung zur Datenbank statt, in ein anderes Modul (ebenfalls GUI) sind dann diese Schaltflächen, dass ManagedEngine() und ManagedSessionScope() sind je in einem Modul untergebracht. Und auch die MasterDataManipulation() ist in einem Modul. Deswegen habe ich auch die strikte Teilung zwischen Engine und Session vorgenommen, damit ich die Engine klassenweit benutze, und wenn ich in eine andere GUI gehe, reiche ich die bereits erstellte Engine einfach weiter. Ich komme mir gerade mächtig dumm for, als jemand, dem man sagt "1+1=2" und mich dann fragt "was sind 2+2=?" und ich dann sage "na 2".

Und das threading-Modul bringt mir leider nichts, weil mir das mit den Klassen vollkommen auf der Strecke bleibt. Ich versuche mal das Beispiel zu verstehen, und dann auf meinen Klassen anzuwenden.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es ist ueblich fuer SQLAlchemy die Session in einem Modul zu definieren und dann in anderen Modulen eben einfach zu importieren. Und in main() bindet man die konkrete Engine (die ja zB von Benutzereinstellungen abhaengen kann etc) an die Session. Wie vorgemacht.

Und das threading-Modul unterscheided sich bezueglich des gesagten nicht von QThreads, insofern verstehe ich nicht, warum dir "das nichts bringt". Das man fuer Qt QThreads braucht ist schoen, SQLAlchemy ist das egal.
Antworten