Re: SQLAlchemy: Arbeiten mit mehreren QThreads?
Verfasst: Montag 14. August 2017, 16:46
@__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.
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