SQLAlchemy: Arbeiten mit mehreren QThreads?
@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
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.
@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 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.
@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?
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()
[...]
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.
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.
@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.
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?
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()
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)
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.
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...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?
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 beobachtensnafu 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 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.
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...
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...
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.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.
@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()
@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:
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()
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.
@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.
@snafu: Ich glaube, du bist von der falschen Namensgebung meinerseits irritiert. Also mein ManagedSessionScope()-Objekt liefert folgendes zurück: (extrem verkürzt)
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.
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
[...]
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.
@Sophus: Du gibst bei `__enter__()` ein Tupel zurück? Aber das hat doch dann gar keine `__exit__()`-Methode!