SQLAlchemy: Arbeiten mit mehreren QThreads?

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
DasIch
User
Beiträge: 2435
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Freitag 11. August 2017, 20:37

Überleg mal was passiert wenn zwei unterschiedliche Threads nacheinander SessionScope.__enter__() aufrufen bevor einer der beiden __exit__() aufruft.
Benutzeravatar
Sophus
User
Beiträge: 1049
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Freitag 11. August 2017, 22:51

@DasIch: Nach drei Nächten bin ich auch auf diese Idee gekommen, Erst habe ich geglaubt, ich handhabe die Threads falsch, dann war ich im Glauben, dass ich die Sessions falsch verteile etc. Ich habe nochmals mein SessionScope()-Objekt hierher kopiert. Schaut man in den nachfolgenden Quelltext, so sehen wir in Zeile 56 und Zeile 59, dass in der magischen __enter__()-Methode zwei Session-Varianten zu sehen sind. Die Session, die ausgeklammert wurde, war diejenige Session, mit der ich bisher gearbeitet habe. Und genau da lag der Fehler. Ich habe die ganze Zeit mit der falschen Session gearbeitet. Die Session in Zeile 59 war eine ganz normale Session, die keineswegs thread-sicher ist. Die Session in Zeile 56 ist die Session die ich brauchte. Ich hatte einfach die Funktions-Klammern übersehen. Bis ich mich irgendwann fragte "Sag mal, arbeite ich mit der richtigen Session? und da sah ich, dass es die normale Session war. Also entfernte ich die Funktions-Klammern, et volà nun die jetztige Session durch die scoped_session()-Methode thread-sicher. Ich will nicht vorgreifen und meinen, dies sei die Lösung. Nach einigen Testläufen bekam ich keine Fehlermeldungen in dieser Richtung. Einzig und allein die Fehlermeldungen, dass irgendwann zu viele Verbindungen (Too many connections) vorhanden sind.

Code: Alles auswählen

class ManagedSessionScope(object):
    def __init__(self,
                 dbms=None,
                 dbdriver=None,
                 dbuser=None,
                 dbuser_pwd=None,
                 db_server_host=None,
                 dbport=None,
                 db_name=None,
                 verbose_echo=True,
                 admin_database=None):
 
        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.admin_database = admin_database
        self.verbose_echo=verbose_echo
       
        url = '{}+{}://{}:{}@{}:{}/{}'.format(
           self.dbms, self.dbdriver, self.dbuser, self.dbuser_pwd, self.db_server_host, self.dbport, self.db_name)

        # Currently the echo is turned on to see the auto-generated SQL.

        # That is, the Engine is a factory for connections as well as a pool of connections,
        # not the connection itself. When you say in this case close(),
        # the connection is returned to the connection pool within the Engine, not actually closed.
 
        # So the self._Engine will not use connection pool if you set poolclass=NullPool.
        # So the connection (SQLAlchemy session) will close directly after session.close()
        # that means, if you set poolclass=NullPool each call to close() will close the underlying DBAPI connection.

        self._Engine = create_engine(self.url, pool_size=10, encoding='utf8', echo=self.verbose_echo)
       
        self.session = None
        
        # Set up the session and store a sessionmaker for this db connection object
        # Session registry is established

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

        # I have to persist all tables and create them
        Base.metadata.create_all(self._Engine)


    def __enter__(self):
        # In this magic function all calls to Session() will create a thread-local session.
        # That means, you can now use self.session to run multiple queries, etc.
        # The registry is *optionally* starts called upon explicitly to create
        # a Session local to the thread and/or request. That why we return self.session
        
        self.session = self._Session # this is now a scoped session
                                     # sqlalchemy.orm.scoping.scoped_session
                                     
        #self.session = self._Session() # this is sqlalchemy.orm.session.Session

        return self.session, self
 
    def __exit__(self, exception, exc_value, traceback):

        try:
            if exception:
 
                self.session.rollback()
            else:
 
                self.session.commit()
 
        finally:
            self.session.close()


    def disconnect(self):
        ''' Make sure the dbconnection gets closed '''
        self.session = self._Session

        # close() will give the connection back to the connection 
        # pool of Engine and doesn't close the connection.
        self.session.close()


        # dispose() will close all connections of the connection pool.
        # Note that a new pool is created when you dispose the engine;
        # the database is not perminantly disconnected, but any open connections
        # are closed, and no new connections are drawn from the new pool by the dispose
        # operation itself.
        # That way the connection pool is flushed out and new connections begin

        self._Engine.dispose()

        self.session = None
        self._Engine = None
Benutzeravatar
snafu
User
Beiträge: 5440
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Samstag 12. August 2017, 06:12

scoped_session() bereitet ein aufrufbares und thread-sicheres Objekt vor. Immer dann wenn eine neue Session gestartet werden soll, ruft man das von scoped_session() gelieferte Objekt auf. Deine __enter__()-Methode sollte also sowas wie self._scoped_session() zurückgeben.
shcol (Repo | Doc | PyPi)
Benutzeravatar
Sophus
User
Beiträge: 1049
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Samstag 12. August 2017, 13:28

@snafu: Meine jetzt aktualisierte __enter__()-Methode liefert die sqlalchemy.orm.scoping.scoped_session bei jedem Aufruf. Und diese Session wollte ich. Leider hatte ich die Funktionsklammern übersehen 8) Oder wolltest du mich auf einen weiteren Fehler hinweisen, den ich übersehen habe? Denn wenn ich die Funktionsklammern benutzte, dann bekomme ich wieder die normale Session, siehe Kommentar.
Benutzeravatar
Sophus
User
Beiträge: 1049
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Samstag 12. August 2017, 15:43

@snafu: Ich muss mich nochmals melden. Meinst du etwa so? Zur Vereinfachung habe ich den gesamten Beispiel extrem gekürzt und ist damit nicht ausführbar und den Context-Manager habe ich mal eben umstrukturiert. Diese Umstrukturierung findest du in Zeile 252-280. Das eigentliche Herzstück meines Anliegen sind die Zeilen 198-207. Da öffne ich den Context Manager einmalig, und verteile die sessions an die Threads, die durch die For-Schleife nach und nach geöffnet werden. Ich hoffe, dass ich bis hierher richtig verfahre. Denn ich habe mich weitestgehend an die SQLAlchemy-Dokumentation gehalten: When do I construct a Session, when do I commit it, and when do I close it?. In diesem Beispiel werden insgesamt drei Beispiele gezeigt, eines davon sollte man auf keinen Fall verwenden, und die anderen beiden Beispiel zeigen, wie man die Session handhabt. Ich habe mich für das zweite Beispiel entschieden - sieht übersichtlicher aus. In in der MasterDataManipulation()-Klasse (Zeile 25-61) findest du derzeit einige Abfragen, für jede Kategorie. Später werden in dieser Klasse mehrere Abfragen vorhanden sein. Und die Worker()-Klasse (Zeilen 63-158) dient als Sub-Klasse, welche später zum QThread() hinzugefügt wird.

Und wie du in der session_scope()-Funtion (Zeilen 252-280) siehst, habe ich die Scoped_session, die durch die scoped_session aufbereitet wird, nun mit der Funktionsklammer aufgerufen, gleich im Zuge der yield. Meinst du das etwa so?

Code: Alles auswählen

# -*- coding: cp1252 -*-
import sys

from contextlib import contextmanager

# Here we have to import all PyQt stuff for working with GUI

import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.exc import SQLAlchemyError, OperationalError, DisconnectionError
from sqlalchemy import exc 
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy import Table, Column, Integer, String, MetaData
from sqlalchemy import event

from traceback import format_exc
from sys import exc_info

''' setting up root class for declarative declaration '''
Base = declarative_base()

# Here you can see the models of the tables

class MasterDataManipulation(object):

    def __init__(self, session_object=None):

    	self._session = session_object
                  
    def select_all(self, category):

        dict_store_session_query = {'person_gender':               lambda: self._session.query(PERSON_GENDER),
                                     'person_nationality':          lambda: self._session.query(PERSON_NATIONALITY),
                                     'person_salutation':           lambda: self._session.query(PERSON_SALUTATION),
                                     'person_title':                lambda: self._session.query(PERSON_TITLE),
                                     'person_hair_color':           lambda: self._session.query(PERSON_HAIR_COLOR),
                                     'person_eye_color':            lambda: self._session.query(PERSON_EYE_COLOR),
                                     'person_religion':             lambda: self._session.query(PERSON_RELIGION),
                                     'person_relationship_status':  lambda: self._session.query(PERSON_RELATIONSHIP_STATUS)}      

        for record in dict_store_session_query[category]():
            if category == 'person_gender':
                yield record.id, record.gender
            if category == 'person_nationality':
                yield record.id, record.nationality
            if category == 'person_salutation':
                yield record.id, record.salutation
            if category == 'person_title':
                yield record.id, record.title
            if category == 'person_hair_color':
                yield record.id, record.hair_color
            if category == 'person_eye_color':
                yield record.id, record.eye_color
            if category == 'person_religion':
                yield record.id, record.religion
            if category == 'person_relationship_status':
                yield record.id, record.relationship_status


        return

class Worker(QObject):
 
    finish_progress = pyqtSignal()
    populate_item_signal = pyqtSignal(object, object)

    stop_loop = pyqtSignal(unicode, unicode)
   
    def __init__(self,
                 combo_box=None,
                 new_scope=None,
                 category=None,
                 time_interval=None,
                 operation=None,
                 parent=None):
        QObject.__init__(self, parent)

        self.new_scope=new_scope
        self.category = category
        self.time_interval=time_interval
        self.operation = operation

        if self.time_interval is None:
            self.time_interval = 100

        #self.master_data_manipulation = MasterDataManipulation(session_object=self.new_scope)
        self.combo_box=combo_box

        ''' Create attributes '''
        self._run_semaphore = 1

        self._element = None

    def init_object(self):

        if self.operation == "select":

            self.timer = QTimer()
            
            '''
                Storing new generator object, will reuse it.
                That means you have to create one generator.
            '''
            master_data_manipulation = MasterDataManipulation(session_object=self.new_scope)
            query_data=master_data_manipulation.select_all
            self._element = query_data(self.category)

            # assoziiert select_all_data() mit TIMEOUT Ereignis
            self.timer.setSingleShot(False)
            self.timer.setInterval(int(self.time_interval))
            self.timer.timeout.connect(self.populate_item)
            self.timer.start()

        if self.operation == 'population':
            print "hier"
            master_data_manipulation = MasterDataManipulation(session_object=self.new_scope)
            master_data_manipulation.populate_data()
           
    def populate_item(self):
        try:

            if self._run_semaphore == 0:
    
                self._run_semaphore = 1

                raise StopIteration

            else:

                self.populate_item_signal.emit(next(self._element), self.combo_box)

        except StopIteration:

            self.finish_progress.emit()
            self.timer.stop()

        except SQLAlchemyError as err:
            server_said = "The server said: {server_said}".format(server_said=str(err[0]))
            #print "SQLAlchemyError, populate_item", format_exc(exc_info())
            desired_trace = format_exc(exc_info())
            self.stop_loop.emit(desired_trace, server_said)
            self.finish_progress.emit()
            self.timer.stop()

        except OperationalError as err:
            server_said = "The server said: {server_said}".format(server_said=str(err[0]))
            #print "OperationalError, populate_item", format_exc(exc_info())
            desired_trace = format_exc(exc_info())
            self.timer.stop()
            self.stop_loop.emit(desired_trace, server_said)
            self.finish_progress.emit()
            ##self.timer.stop()
       
    def stop(self):
        self.timer.stop()
        self._run_semaphore = 0
        self._element = None
         
[...]

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

        self._url = url

[...]

    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)
                       ]

        '''
            use one session per thread, share nothing between threads.  The  
            scoped_session should make this pretty straightforward.
        '''
        try:
            # I know each session is thread-local, that means there is a separate session for each thread.
            # So I decide to pass some instances/sessions to another thread,
            # I think they will become "detached" from the session.
            # According to documentation we should use different instance of engine for every subprocess,
            # in our case we have one engine for all subprocesses, because connection pool between subprocesses
            # cannot be shared (as i understand).
            
            with session_scope(dburi=self._url, verbose=False) as session:
                for category, combobox in list_tuple:

                    combobox.clear()

                    self.start_thread(combo_box=combobox,
                                      session=session,
                                      time_interval=10,
                                      operation='select',
                                      category=category)

        except SQLAlchemyError as err:
            # do stuff with this error

        except OperationalError as OpErr:
            # do stuff with this error

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

        task_thread = QThread(self)

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

        ''' We need to store threads '''
        #self._list_threads.append(task_thread)  
        task_thread.work.moveToThread(task_thread)
        
        task_thread.work.finish_progress.connect(task_thread.quit)

        task_thread.work.stop_loop.connect(self.message_out)

        task_thread.work.populate_item_signal.connect(self.fill_combo_boxt)

        self.finish.connect(task_thread.work.stop)

        task_thread.started.connect(task_thread.work.init_object)

        task_thread.finished.connect(task_thread.deleteLater)

        ''' This will emit 'started' and start thread's event loop '''
        task_thread.start()

[...]

@contextmanager
def session_scope(dburi=None, echo_verbose=True):
    """
        Provide a transactional scope around a series of operations.
        Creates a context with an open SQLAlchemy session.
    """                                    
    engine = create_engine(dburi,
                           pool_size=10,
                           max_overflow=10,
                           pool_timeout=60,
                           echo=echo_verbose)

    # create a session maker for factory
    session_factory = sessionmaker(bind=engine)
    
    # scoped_session create one connection per each thread
    #Session  = scoped_session(sessionmaker(bind=engine))#, twophase=True))
    Scoped_session = scoped_session(session_factory)
    
    # Now all calls to Session() will create a thread-local session
    try:
        yield Scoped_session()
        Scoped_session.commit()
    except:
        Scoped_session.rollback()
        raise
    finally:
        Scoped_session.close()
[...]
Benutzeravatar
snafu
User
Beiträge: 5440
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sonntag 13. August 2017, 03:31

Jetzt vergibst du wieder die Original-Session an verschiedene Threads. So sollte das ja eigentlich nicht sein. Ich meinte die Rückgabe von scoped_session() bloß im Rahmen eines Kontext-Managers. Wenn du diesen Kontext-Manager auf höherer Ebene letztlich nur einmal ausführst, dann hebelst du die Vorteile von scoped_session() ja wieder aus.

Die Idee ist ja, dass man von verschiedenen Stellen im Programm auf die gleiche Session zugreift ohne dass die Session explizit übergeben werden - und das auch optional über verschiedene Threads ohne dass sich die Threads in die Quere kommen. So wie du das jetzt benutzt, ist dir die Anwendung scheinbar noch nicht ganz klar geworden.
shcol (Repo | Doc | PyPi)
Benutzeravatar
Sophus
User
Beiträge: 1049
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Sonntag 13. August 2017, 03:38

@snafu: Ich bin verwirrt. Meintest du denn so: Scoped_session, anstatt so: Scoped_session()?

build_scope_session = Scoped_session --> # sqlalchemy.orm.scoping.scoped_session
build_scope_session = Scoped_session () --> # this is sqlalchemy.orm.session.Session

Ich verfolge dabei diesen Kapitel der SQLAlchemy-Dokumentation: When do I construct a Session, when do I commit it, and when do I close it?.
Dort wird eine Beispiel-Idee gezeigt.

Code: Alles auswählen

### this is the **wrong way to do it** ###

class ThingOne(object):
    def go(self):
        session = Session()
        try:
            session.query(FooBar).update({"x": 5})
            session.commit()
        except:
            session.rollback()
            raise

class ThingTwo(object):
    def go(self):
        session = Session()
        try:
            session.query(Widget).update({"q": 18})
            session.commit()
        except:
            session.rollback()
            raise

def run_my_program():
    ThingOne().go()
    ThingTwo().go()
Und dieses Beispiel habe ich verfolgt: Und so wie ich das verstehe, wird der Kontext-Manager einmalig geöffnet, die Session werden an die Threads übergeben?

Code: Alles auswählen

### another way (but again *not the only way*) to do it ###

from contextlib import contextmanager

@contextmanager
def session_scope():
    """Provide a transactional scope around a series of operations."""
    session = Session()
    try:
        yield session
        session.commit()
    except:
        session.rollback()
        raise
    finally:
        session.close()


def run_my_program():
    with session_scope() as session:
        ThingOne().go(session)
        ThingTwo().go(session)
Benutzeravatar
snafu
User
Beiträge: 5440
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sonntag 13. August 2017, 04:25

Habe hier mal grob skizziert wie man scoped_session() benutzen kann:

Code: Alles auswählen

def do_work(sessiongetter, value):
    shared_session = sessiongetter()
    try:
        # do stuff
    except Exception:
        shared_session.rollback()
        raise
    # NOTE: do not close the shared session!


def main():
    # ...
    engine = create_engine(dburi,
                           pool_size=10,
                           max_overflow=10,
                           pool_timeout=60,
                           echo=echo_verbose)
    sessiongetter = scoped_session(sessionmaker(bind=engine))
    try:
        for bla in blupp:
            do_work(sessiongetter, bla)
    finally:
        sessiongetter.close()
Zuletzt geändert von snafu am Sonntag 13. August 2017, 04:43, insgesamt 1-mal geändert.
shcol (Repo | Doc | PyPi)
Benutzeravatar
snafu
User
Beiträge: 5440
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sonntag 13. August 2017, 04:37

Sophus hat geschrieben:Und dieses Beispiel habe ich verfolgt: Und so wie ich das verstehe, wird der Kontext-Manager einmalig geöffnet, die Session werden an die Threads übergeben?
Wo siehst du da Threads? Wenn alles im selben Thread passiert, dann ist ja klar, dass die zu teilende Session ruhig auf einer höheren Ebene erstellt werden kann und dann z.B. in einer Schleife an die verarbeitenden Funktionen weitergereicht wird. scoped_session() kümmert sich laut Doku aber auch darum, dass die Threads sich bei der Verwendung der geteilten Session nicht in die Quere kommen. Vielleicht bin ich hier ja etwas übervorsichtig, aber ich würde die Rückgabe von scoped_session() in einer multi-threaded Anwendung erst so spät wie möglich aufrufen. Es tut nicht weh und vermeidet möglicherweise subtile Fehler. Muss aber jeder für sich selber wissen...
shcol (Repo | Doc | PyPi)
Benutzeravatar
snafu
User
Beiträge: 5440
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sonntag 13. August 2017, 04:50

Na du wirst mein Beispiel sicher nicht 1 zu 1 übernommen haben. Aber wenn es so für dich funktioniert, dann ist ja alles prima. :)

EDIT: Hier stand vorher noch ein Kommentar von Sophus...
shcol (Repo | Doc | PyPi)
Benutzeravatar
Sophus
User
Beiträge: 1049
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Sonntag 13. August 2017, 04:57

snafu hat geschrieben:Na du wirst mein Beispiel sicher nicht 1 zu 1 übernommen haben. Aber wenn es so für dich funktioniert, dann ist ja alles prima. :)

EDIT: Hier stand vorher noch ein Kommentar von Sophus...
Ich habe meinen Beitrag zurück gezogen. Mein Fehler war, dass ich den Rückgabewert von scoped_session() in den Threads zu früh aufgerufen habe und dadurch eine ellenlange Fehlermeldung bekam. Erst als ich bei dir las, dass man den Rückgabewert so spät wie möglich aufrufen soll, funktionierte deine Variante wieder. Wie gesagt, auch ohne den Rückgabewert aufzurufen hat geklappt. Das heißt, selbst mit dem Rückgabewert von scoped_session() wurden alle Daten ausgegeben, und das auch nach mehreren Male. Irgendwie scheint sowohl deine Version als auch meine Version zu klappen. Ich werde das mal beobachten 8)

Ich danke dir vielmals.

EDIT:

P.S. Hier (What is the difference between Session and db.session in SQLAlchemy?) wird sogar (zum Thema Flask) das thematisiert, was ich mit dem Rückgabewert von scoped_session() meine. Der Antworter meint selbst, dass man mit <class 'sqlalchemy.orm.scoping.scoped_session'> arbeiten kann, und demonstriert es.
Benutzeravatar
snafu
User
Beiträge: 5440
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sonntag 13. August 2017, 08:57

Aber dann mal ganz ehrlich: Wenn beides für dich aufs Gleiche hinausläuft (was es - nebenbei gesagt - ja gerade nicht tut), warum verwendest du dann den Umweg über scoped_session()? IMHO kannst du dann auch direkt die Original-Session herumreichen, denn welchen Vorteil soll dies deiner Meinung nach bringen?

Wie sieht denn dein jetziger Code aus? Und es wäre toll, wenn du nicht wieder 200 Zeilen postest, sondern einfach grob skizzierst, wie du die Session herumreichst...
shcol (Repo | Doc | PyPi)
Benutzeravatar
snafu
User
Beiträge: 5440
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sonntag 13. August 2017, 09:01

Sophus hat geschrieben:P.S. Hier (What is the difference between Session and db.session in SQLAlchemy?) wird sogar (zum Thema Flask) das thematisiert, was ich mit dem Rückgabewert von scoped_session() meine. Der Antworter meint selbst, dass man mit <class 'sqlalchemy.orm.scoping.scoped_session'> arbeiten kann, und demonstriert es.
Das hat auch niemand bestritten. Zumal es - wieder einmal - hier nicht um eine multi-threaded Anwendung geht. Irgendwie glaube ich, du wirst diesen Punkt erst verstanden haben, wenn du auf die Nase gefallen bist.
shcol (Repo | Doc | PyPi)
Benutzeravatar
Sophus
User
Beiträge: 1049
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Sonntag 13. August 2017, 14:48

@snafu: Ok, ich werde dir mal meine grobe Skizze zeigen. Diesmal ist mein Beispiel extrem gekürzt :) Kommentare schreibe ich für weitere Erläuterungen in dem Pseudo-Quelltext - auf englisch. Auf diese Weise kannst du meinen Gedankengang nachvollziehen.

Code: Alles auswählen

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

        self._url = url

        [...]

    def start_all_selection(self):
        # User clicks on button, which is connected with this method.
        # He wants to see all data.
        # let us start all threads - currently there a 8 threads.

        try:
            # We know that our ManagedSessionScope()-object is a custom context manager with sperate class.
            # Our custom context manager returns <class 'sqlalchemy.orm.scoping.scoped_session'>,
            # no normal session.  In this case, we use one session per thread, I think. 
            # We don't want to share nothing between threads, right?
            # Well, the user wants the program to query all data. lets do it.
            with ManagedSessionScope(url=self._url, echo_verbose=False) as (session, ScopeSession):

                [...]
                for category, combobox in list_tuple:
                    self.start_thread(combo_box=combobox,
                                      session=session,
                                      time_interval=100,
                                      category=category)

                # After all 8 threads have completed tasks and finished, 
                # the created session will close automatically. We don't need the created session anymore.
                # When the user wants the program to load the data again, we can create a new session again.

        except SQLAlchemyError as err:
            # do stuff with raised exception

        except OperationalError as OpErr:
            # du stuff with raised exception

def main():
    # Image, somewhere in the program there is a login window
    # where the user can enter his login data. In this example,
    # the user enters his login data at start start of the program.
    dbms = raw_input('Enter database type: ')
    dbdriver = raw_input('Enter database driver: ')
    dbuser = raw_input('Enter user name: ')
    dbuser_pwd = raw_input('Enter user password: ')
    db_server_host = raw_input('Enter server host: ')
    dbport = raw_input('Enter port: ')
    db_name = raw_input('Enter database name: ')

    url = '{}+{}://{}:{}@{}:{}/{}'.format(
           dbms, dbdriver, dbuser, dbuser_pwd, db_server_host, dbport, db_name)

    # Now we have the required url for the login. We pass
    # the url as an argument to the gui window

    [...]  

    app = QApplication(sys.argv)
    window = MyCustomDialog(url = url)
    [...]
        
if __name__ == "__main__":
    main()
Benutzeravatar
Sophus
User
Beiträge: 1049
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Sonntag 13. August 2017, 16:59

@snafu: Ich habe die zweite Version mitgebracht. Diesmal habe ich das ManagedSessionScope()-Objekt einmalig beim Start des Programm geöffnet. Die with-Anweisung bleibt in diesem Beispiel so lange am Leben, wie die Applikation läuft. Ich übergebe beispielsweise der GUI-Klasse die Klasse <class 'sqlalchemy.orm.scoping.scoped_session'>. Diese Klasse wird dann innerhalb dieser GUI-Klasse klassenweit benutzt. In der start_all_selection()-Methode verzichte ich diesmal auf die with-Anweisung, schließlich habe ich sie ja in den Start (main()-Funktion) verlegt. Ich übergebe jedem Thread die gegebene Klasse. Bei jedem Thread-Start wird auch die MasterDataManipulation()-Klasse erzeugt. In dieser Klasse wird die <class 'sqlalchemy.orm.scoping.scoped_session'> aufgerufen. Sobald die select_all()-Methode mit ihrer Arbeit fertig ist, wird ein commit() abgesetzt. Aus diesem Grund wird dann die Verbindung zum Verbindungspool zurückgegebenen und die Verbindung wird wieder freigegeben.

Sollte ich das immer noch falsch verstanden haben, dann kannst du mir ein Kinderbuch schreiben, mit Bilderchen, Sprechblasen, und mit den einfachsten Erklärungen. Denn ich bin mit meinen Latein am Ende :) Ich war nie gut in Latein :)

Zweite Version der Skizze:

Code: Alles auswählen

class MasterDataManipulation(object):

    def __init__(self, session_object=None):
    	# This class  is created on each thread and communicates with the database.
    	#self._session = session_object
    	self._session = session_object()
                 
    def select_all(self, category):

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

        try:

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

            # I use commit() on given session for flushing the connection.
            # That means, the connetion is flushed, the querys
            # are committed, the connection object closed
            # and discarded, the underlying DBAPI connection
            # returned to the connection pool.
            self._session.commit()

        except Exception:
            self._session.rollback()

class MyCustomDialog(QDialog):
 
    finish = pyqtSignal()
 
    def __init__(self, session=None, parent=None):
        QDialog.__init__(self, parent)
        # Now we save <class 'sqlalchemy.orm.scoping.scoped_session'> in the attribute named 'self._session'
        self._session = session
       [...]

    def start_all_selection(self):
        [...]
        try:
                
            for category, combobox in list_tuple:

                combobox.clear()

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

        except SQLAlchemyError as err:
            # Do stuff with this raised exception

def main():
    # Image, somewhere in the program there is a Log_In window
    # where the user can enter his LoGi data. In this example,
    # the user enters his Login data at start start of the program.
    # The user can LogIn to the database-server 
    dbms = raw_input('Enter database type: ')
    dbdriver = raw_input('Enter database driver: ')
    dbuser = raw_input('Enter user name: ')
    dbuser_pwd = raw_input('Enter user password: ')
    db_server_host = raw_input('Enter server host: ')
    dbport = raw_input('Enter port: ')
    db_name = raw_input('Enter database name: ')

    url = '{}+{}://{}:{}@{}:{}/{}'.format(
           dbms, dbdriver, dbuser, dbuser_pwd, db_server_host, dbport, db_name)

    # Now we have the required url for the login. We pass
    # the url as an argument to the gui window

    try:
        with ManagedSessionScope(url=url, echo_verbose=True) as (session, ScopeSession):
            
            app = QApplication(sys.argv)
            window = MyCustomDialog(session = session)
            window.show()
            sys.exit(app.exec_())
            
    except TypeError:
        
        print "ERROR", format_exc(exc_info())
        
if __name__ == "__main__":
    main()
Antworten