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: 2356
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon DasIch » 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: 998
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon Sophus » 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.

  1. class ManagedSessionScope(object):
  2.     def __init__(self,
  3.                  dbms=None,
  4.                  dbdriver=None,
  5.                  dbuser=None,
  6.                  dbuser_pwd=None,
  7.                  db_server_host=None,
  8.                  dbport=None,
  9.                  db_name=None,
  10.                  verbose_echo=True,
  11.                  admin_database=None):
  12.  
  13.         self.dbms = dbms
  14.         self.dbdriver = dbdriver
  15.         self.dbuser = dbuser
  16.         self.dbuser_pwd = dbuser_pwd
  17.         self.db_server_host = db_server_host
  18.         self.dbport = dbport
  19.         self.db_name = db_name
  20.         self.admin_database = admin_database
  21.         self.verbose_echo=verbose_echo
  22.        
  23.         url = '{}+{}://{}:{}@{}:{}/{}'.format(
  24.            self.dbms, self.dbdriver, self.dbuser, self.dbuser_pwd, self.db_server_host, self.dbport, self.db_name)
  25.  
  26.         # Currently the echo is turned on to see the auto-generated SQL.
  27.  
  28.         # That is, the Engine is a factory for connections as well as a pool of connections,
  29.         # not the connection itself. When you say in this case close(),
  30.         # the connection is returned to the connection pool within the Engine, not actually closed.
  31.  
  32.         # So the self._Engine will not use connection pool if you set poolclass=NullPool.
  33.         # So the connection (SQLAlchemy session) will close directly after session.close()
  34.         # that means, if you set poolclass=NullPool each call to close() will close the underlying DBAPI connection.
  35.  
  36.         self._Engine = create_engine(self.url, pool_size=10, encoding='utf8', echo=self.verbose_echo)
  37.        
  38.         self.session = None
  39.        
  40.         # Set up the session and store a sessionmaker for this db connection object
  41.         # Session registry is established
  42.  
  43.         self._Session = scoped_session(sessionmaker(bind=self._Engine))
  44.         self._Session.remove()
  45.  
  46.         # I have to persist all tables and create them
  47.         Base.metadata.create_all(self._Engine)
  48.  
  49.  
  50.     def __enter__(self):
  51.         # In this magic function all calls to Session() will create a thread-local session.
  52.         # That means, you can now use self.session to run multiple queries, etc.
  53.         # The registry is *optionally* starts called upon explicitly to create
  54.         # a Session local to the thread and/or request. That why we return self.session
  55.        
  56.         self.session = self._Session # this is now a scoped session
  57.                                      # sqlalchemy.orm.scoping.scoped_session
  58.                                      
  59.         #self.session = self._Session() # this is sqlalchemy.orm.session.Session
  60.  
  61.         return self.session, self
  62.  
  63.     def __exit__(self, exception, exc_value, traceback):
  64.  
  65.         try:
  66.             if exception:
  67.  
  68.                 self.session.rollback()
  69.             else:
  70.  
  71.                 self.session.commit()
  72.  
  73.         finally:
  74.             self.session.close()
  75.  
  76.  
  77.     def disconnect(self):
  78.         ''' Make sure the dbconnection gets closed '''
  79.         self.session = self._Session
  80.  
  81.         # close() will give the connection back to the connection
  82.         # pool of Engine and doesn't close the connection.
  83.         self.session.close()
  84.  
  85.  
  86.         # dispose() will close all connections of the connection pool.
  87.         # Note that a new pool is created when you dispose the engine;
  88.         # the database is not perminantly disconnected, but any open connections
  89.         # are closed, and no new connections are drawn from the new pool by the dispose
  90.         # operation itself.
  91.         # That way the connection pool is flushed out and new connections begin
  92.  
  93.         self._Engine.dispose()
  94.  
  95.         self.session = None
  96.         self._Engine = None
Benutzeravatar
snafu
User
Beiträge: 5156
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon snafu » 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: 998
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon Sophus » 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: 998
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon Sophus » 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?

  1. # -*- coding: cp1252 -*-
  2. import sys
  3.  
  4. from contextlib import contextmanager
  5.  
  6. # Here we have to import all PyQt stuff for working with GUI
  7.  
  8. import sqlalchemy
  9. from sqlalchemy.ext.declarative import declarative_base
  10. from sqlalchemy import create_engine
  11. from sqlalchemy.exc import SQLAlchemyError, OperationalError, DisconnectionError
  12. from sqlalchemy import exc
  13. from sqlalchemy.orm import sessionmaker, scoped_session
  14. from sqlalchemy import Table, Column, Integer, String, MetaData
  15. from sqlalchemy import event
  16.  
  17. from traceback import format_exc
  18. from sys import exc_info
  19.  
  20. ''' setting up root class for declarative declaration '''
  21. Base = declarative_base()
  22.  
  23. # Here you can see the models of the tables
  24.  
  25. class MasterDataManipulation(object):
  26.  
  27.     def __init__(self, session_object=None):
  28.  
  29.         self._session = session_object
  30.                  
  31.     def select_all(self, category):
  32.  
  33.         dict_store_session_query = {'person_gender':               lambda: self._session.query(PERSON_GENDER),
  34.                                      'person_nationality':          lambda: self._session.query(PERSON_NATIONALITY),
  35.                                      'person_salutation':           lambda: self._session.query(PERSON_SALUTATION),
  36.                                      'person_title':                lambda: self._session.query(PERSON_TITLE),
  37.                                      'person_hair_color':           lambda: self._session.query(PERSON_HAIR_COLOR),
  38.                                      'person_eye_color':            lambda: self._session.query(PERSON_EYE_COLOR),
  39.                                      'person_religion':             lambda: self._session.query(PERSON_RELIGION),
  40.                                      'person_relationship_status':  lambda: self._session.query(PERSON_RELATIONSHIP_STATUS)}      
  41.  
  42.         for record in dict_store_session_query[category]():
  43.             if category == 'person_gender':
  44.                 yield record.id, record.gender
  45.             if category == 'person_nationality':
  46.                 yield record.id, record.nationality
  47.             if category == 'person_salutation':
  48.                 yield record.id, record.salutation
  49.             if category == 'person_title':
  50.                 yield record.id, record.title
  51.             if category == 'person_hair_color':
  52.                 yield record.id, record.hair_color
  53.             if category == 'person_eye_color':
  54.                 yield record.id, record.eye_color
  55.             if category == 'person_religion':
  56.                 yield record.id, record.religion
  57.             if category == 'person_relationship_status':
  58.                 yield record.id, record.relationship_status
  59.  
  60.  
  61.         return
  62.  
  63. class Worker(QObject):
  64.  
  65.     finish_progress = pyqtSignal()
  66.     populate_item_signal = pyqtSignal(object, object)
  67.  
  68.     stop_loop = pyqtSignal(unicode, unicode)
  69.    
  70.     def __init__(self,
  71.                  combo_box=None,
  72.                  new_scope=None,
  73.                  category=None,
  74.                  time_interval=None,
  75.                  operation=None,
  76.                  parent=None):
  77.         QObject.__init__(self, parent)
  78.  
  79.         self.new_scope=new_scope
  80.         self.category = category
  81.         self.time_interval=time_interval
  82.         self.operation = operation
  83.  
  84.         if self.time_interval is None:
  85.             self.time_interval = 100
  86.  
  87.         #self.master_data_manipulation = MasterDataManipulation(session_object=self.new_scope)
  88.         self.combo_box=combo_box
  89.  
  90.         ''' Create attributes '''
  91.         self._run_semaphore = 1
  92.  
  93.         self._element = None
  94.  
  95.     def init_object(self):
  96.  
  97.         if self.operation == "select":
  98.  
  99.             self.timer = QTimer()
  100.            
  101.             '''
  102.                Storing new generator object, will reuse it.
  103.                That means you have to create one generator.
  104.            '''
  105.             master_data_manipulation = MasterDataManipulation(session_object=self.new_scope)
  106.             query_data=master_data_manipulation.select_all
  107.             self._element = query_data(self.category)
  108.  
  109.             # assoziiert select_all_data() mit TIMEOUT Ereignis
  110.             self.timer.setSingleShot(False)
  111.             self.timer.setInterval(int(self.time_interval))
  112.             self.timer.timeout.connect(self.populate_item)
  113.             self.timer.start()
  114.  
  115.         if self.operation == 'population':
  116.             print "hier"
  117.             master_data_manipulation = MasterDataManipulation(session_object=self.new_scope)
  118.             master_data_manipulation.populate_data()
  119.            
  120.     def populate_item(self):
  121.         try:
  122.  
  123.             if self._run_semaphore == 0:
  124.    
  125.                 self._run_semaphore = 1
  126.  
  127.                 raise StopIteration
  128.  
  129.             else:
  130.  
  131.                 self.populate_item_signal.emit(next(self._element), self.combo_box)
  132.  
  133.         except StopIteration:
  134.  
  135.             self.finish_progress.emit()
  136.             self.timer.stop()
  137.  
  138.         except SQLAlchemyError as err:
  139.             server_said = "The server said: {server_said}".format(server_said=str(err[0]))
  140.             #print "SQLAlchemyError, populate_item", format_exc(exc_info())
  141.             desired_trace = format_exc(exc_info())
  142.             self.stop_loop.emit(desired_trace, server_said)
  143.             self.finish_progress.emit()
  144.             self.timer.stop()
  145.  
  146.         except OperationalError as err:
  147.             server_said = "The server said: {server_said}".format(server_said=str(err[0]))
  148.             #print "OperationalError, populate_item", format_exc(exc_info())
  149.             desired_trace = format_exc(exc_info())
  150.             self.timer.stop()
  151.             self.stop_loop.emit(desired_trace, server_said)
  152.             self.finish_progress.emit()
  153.             ##self.timer.stop()
  154.        
  155.     def stop(self):
  156.         self.timer.stop()
  157.         self._run_semaphore = 0
  158.         self._element = None
  159.          
  160. [...]
  161.  
  162. class MyCustomDialog(QDialog):
  163.  
  164.     finish = pyqtSignal()
  165.  
  166.     def __init__(self, url=None, parent=None):
  167.         QDialog.__init__(self, parent)
  168.  
  169.         self._url = url
  170.  
  171. [...]
  172.  
  173.     def start_all_selection(self):
  174.  
  175.         list_tuple = [
  176.                     ("person_salutation", self.combo_person_salutation),
  177.                     ("person_title", self.combo_person_title),
  178.                     ("person_gender", self.combo_person_gender),
  179.                     ("person_religion", self.combo_person_religion),
  180.                     ("person_eye_color", self.combo_person_eye_color),
  181.                     ("person_hair_color", self.combo_person_hair_color),
  182.                     ("person_relationship_status", self.combo_person_relationship_status),
  183.                     ("person_nationality", self.combo_person_nationality)
  184.                        ]
  185.  
  186.         '''
  187.            use one session per thread, share nothing between threads.  The  
  188.            scoped_session should make this pretty straightforward.
  189.        '''
  190.         try:
  191.             # I know each session is thread-local, that means there is a separate session for each thread.
  192.             # So I decide to pass some instances/sessions to another thread,
  193.             # I think they will become "detached" from the session.
  194.             # According to documentation we should use different instance of engine for every subprocess,
  195.             # in our case we have one engine for all subprocesses, because connection pool between subprocesses
  196.             # cannot be shared (as i understand).
  197.            
  198.             with session_scope(dburi=self._url, verbose=False) as session:
  199.                 for category, combobox in list_tuple:
  200.  
  201.                     combobox.clear()
  202.  
  203.                     self.start_thread(combo_box=combobox,
  204.                                       session=session,
  205.                                       time_interval=10,
  206.                                       operation='select',
  207.                                       category=category)
  208.  
  209.         except SQLAlchemyError as err:
  210.             # do stuff with this error
  211.  
  212.         except OperationalError as OpErr:
  213.             # do stuff with this error
  214.  
  215.        
  216.     def start_thread(self,
  217.                      combo_box=None,
  218.                      session=None,
  219.                      time_interval=None,
  220.                      operation=None,
  221.                      category=None):
  222.  
  223.         task_thread = QThread(self)
  224.  
  225.         task_thread.work = Worker(new_scope=session,
  226.                                   time_interval=time_interval,
  227.                                   category=category,
  228.                                   operation=operation,
  229.                                   combo_box=combo_box)
  230.  
  231.         ''' We need to store threads '''
  232.         #self._list_threads.append(task_thread)  
  233.         task_thread.work.moveToThread(task_thread)
  234.        
  235.         task_thread.work.finish_progress.connect(task_thread.quit)
  236.  
  237.         task_thread.work.stop_loop.connect(self.message_out)
  238.  
  239.         task_thread.work.populate_item_signal.connect(self.fill_combo_boxt)
  240.  
  241.         self.finish.connect(task_thread.work.stop)
  242.  
  243.         task_thread.started.connect(task_thread.work.init_object)
  244.  
  245.         task_thread.finished.connect(task_thread.deleteLater)
  246.  
  247.         ''' This will emit 'started' and start thread's event loop '''
  248.         task_thread.start()
  249.  
  250. [...]
  251.  
  252. @contextmanager
  253. def session_scope(dburi=None, echo_verbose=True):
  254.     """
  255.        Provide a transactional scope around a series of operations.
  256.        Creates a context with an open SQLAlchemy session.
  257.    """                                    
  258.     engine = create_engine(dburi,
  259.                            pool_size=10,
  260.                            max_overflow=10,
  261.                            pool_timeout=60,
  262.                            echo=echo_verbose)
  263.  
  264.     # create a session maker for factory
  265.     session_factory = sessionmaker(bind=engine)
  266.    
  267.     # scoped_session create one connection per each thread
  268.     #Session  = scoped_session(sessionmaker(bind=engine))#, twophase=True))
  269.     Scoped_session = scoped_session(session_factory)
  270.    
  271.     # Now all calls to Session() will create a thread-local session
  272.     try:
  273.         yield Scoped_session()
  274.         Scoped_session.commit()
  275.     except:
  276.         Scoped_session.rollback()
  277.         raise
  278.     finally:
  279.         Scoped_session.close()
  280. [...]
Benutzeravatar
snafu
User
Beiträge: 5156
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon snafu » 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: 998
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon Sophus » 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.

  1. ### this is the **wrong way to do it** ###
  2.  
  3. class ThingOne(object):
  4.     def go(self):
  5.         session = Session()
  6.         try:
  7.             session.query(FooBar).update({"x": 5})
  8.             session.commit()
  9.         except:
  10.             session.rollback()
  11.             raise
  12.  
  13. class ThingTwo(object):
  14.     def go(self):
  15.         session = Session()
  16.         try:
  17.             session.query(Widget).update({"q": 18})
  18.             session.commit()
  19.         except:
  20.             session.rollback()
  21.             raise
  22.  
  23. def run_my_program():
  24.     ThingOne().go()
  25.     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?
  1. ### another way (but again *not the only way*) to do it ###
  2.  
  3. from contextlib import contextmanager
  4.  
  5. @contextmanager
  6. def session_scope():
  7.     """Provide a transactional scope around a series of operations."""
  8.     session = Session()
  9.     try:
  10.         yield session
  11.         session.commit()
  12.     except:
  13.         session.rollback()
  14.         raise
  15.     finally:
  16.         session.close()
  17.  
  18.  
  19. def run_my_program():
  20.     with session_scope() as session:
  21.         ThingOne().go(session)
  22.         ThingTwo().go(session)
Benutzeravatar
snafu
User
Beiträge: 5156
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon snafu » Sonntag 13. August 2017, 04:25

Habe hier mal grob skizziert wie man scoped_session() benutzen kann:
  1. def do_work(sessiongetter, value):
  2.     shared_session = sessiongetter()
  3.     try:
  4.         # do stuff
  5.     except Exception:
  6.         shared_session.rollback()
  7.         raise
  8.     # NOTE: do not close the shared session!
  9.  
  10.  
  11. def main():
  12.     # ...
  13.     engine = create_engine(dburi,
  14.                            pool_size=10,
  15.                            max_overflow=10,
  16.                            pool_timeout=60,
  17.                            echo=echo_verbose)
  18.     sessiongetter = scoped_session(sessionmaker(bind=engine))
  19.     try:
  20.         for bla in blupp:
  21.             do_work(sessiongetter, bla)
  22.     finally:
  23.         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: 5156
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon snafu » 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: 5156
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon snafu » 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: 998
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon Sophus » 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: 5156
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon snafu » 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: 5156
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon snafu » 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: 998
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon Sophus » 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.

  1. class MyCustomDialog(QDialog):
  2.  
  3.     finish = pyqtSignal()
  4.  
  5.     def __init__(self, url=None, parent=None):
  6.         QDialog.__init__(self, parent)
  7.  
  8.         self._url = url
  9.  
  10.         [...]
  11.  
  12.     def start_all_selection(self):
  13.         # User clicks on button, which is connected with this method.
  14.         # He wants to see all data.
  15.         # let us start all threads - currently there a 8 threads.
  16.  
  17.         try:
  18.             # We know that our ManagedSessionScope()-object is a custom context manager with sperate class.
  19.             # Our custom context manager returns <class 'sqlalchemy.orm.scoping.scoped_session'>,
  20.             # no normal session.  In this case, we use one session per thread, I think.
  21.             # We don't want to share nothing between threads, right?
  22.             # Well, the user wants the program to query all data. lets do it.
  23.             with ManagedSessionScope(url=self._url, echo_verbose=False) as (session, ScopeSession):
  24.  
  25.                 [...]
  26.                 for category, combobox in list_tuple:
  27.                     self.start_thread(combo_box=combobox,
  28.                                       session=session,
  29.                                       time_interval=100,
  30.                                       category=category)
  31.  
  32.                 # After all 8 threads have completed tasks and finished,
  33.                 # the created session will close automatically. We don't need the created session anymore.
  34.                 # When the user wants the program to load the data again, we can create a new session again.
  35.  
  36.         except SQLAlchemyError as err:
  37.             # do stuff with raised exception
  38.  
  39.         except OperationalError as OpErr:
  40.             # du stuff with raised exception
  41.  
  42. def main():
  43.     # Image, somewhere in the program there is a login window
  44.     # where the user can enter his login data. In this example,
  45.     # the user enters his login data at start start of the program.
  46.     dbms = raw_input('Enter database type: ')
  47.     dbdriver = raw_input('Enter database driver: ')
  48.     dbuser = raw_input('Enter user name: ')
  49.     dbuser_pwd = raw_input('Enter user password: ')
  50.     db_server_host = raw_input('Enter server host: ')
  51.     dbport = raw_input('Enter port: ')
  52.     db_name = raw_input('Enter database name: ')
  53.  
  54.     url = '{}+{}://{}:{}@{}:{}/{}'.format(
  55.            dbms, dbdriver, dbuser, dbuser_pwd, db_server_host, dbport, db_name)
  56.  
  57.     # Now we have the required url for the login. We pass
  58.     # the url as an argument to the gui window
  59.  
  60.     [...]  
  61.  
  62.     app = QApplication(sys.argv)
  63.     window = MyCustomDialog(url = url)
  64.     [...]
  65.        
  66. if __name__ == "__main__":
  67.     main()
Benutzeravatar
Sophus
User
Beiträge: 998
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon Sophus » 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:

  1. class MasterDataManipulation(object):
  2.  
  3.     def __init__(self, session_object=None):
  4.         # This class  is created on each thread and communicates with the database.
  5.         #self._session = session_object
  6.         self._session = session_object()
  7.                  
  8.     def select_all(self, category):
  9.  
  10.         dict_store_session_query = {'person_gender':               lambda: self._session.query(PERSON_GENDER),
  11.                                      'person_nationality':          lambda: self._session.query(PERSON_NATIONALITY),}
  12.  
  13.         try:
  14.  
  15.             for record in dict_store_session_query[category]():
  16.                 if category == 'person_gender':
  17.                     yield record.id, record.gender
  18.                 if category == 'person_nationality':
  19.  
  20.             # I use commit() on given session for flushing the connection.
  21.             # That means, the connetion is flushed, the querys
  22.             # are committed, the connection object closed
  23.             # and discarded, the underlying DBAPI connection
  24.             # returned to the connection pool.
  25.             self._session.commit()
  26.  
  27.         except Exception:
  28.             self._session.rollback()
  29.  
  30. class MyCustomDialog(QDialog):
  31.  
  32.     finish = pyqtSignal()
  33.  
  34.     def __init__(self, session=None, parent=None):
  35.         QDialog.__init__(self, parent)
  36.         # Now we save <class 'sqlalchemy.orm.scoping.scoped_session'> in the attribute named 'self._session'
  37.         self._session = session
  38.        [...]
  39.  
  40.     def start_all_selection(self):
  41.         [...]
  42.         try:
  43.                
  44.             for category, combobox in list_tuple:
  45.  
  46.                 combobox.clear()
  47.  
  48.                 self.start_thread(combo_box=combobox,
  49.                                   session=self._session,
  50.                                   time_interval=100,
  51.                                   category=category)
  52.  
  53.         except SQLAlchemyError as err:
  54.             # Do stuff with this raised exception
  55.  
  56. def main():
  57.     # Image, somewhere in the program there is a Log_In window
  58.     # where the user can enter his LoGi data. In this example,
  59.     # the user enters his Login data at start start of the program.
  60.     # The user can LogIn to the database-server
  61.     dbms = raw_input('Enter database type: ')
  62.     dbdriver = raw_input('Enter database driver: ')
  63.     dbuser = raw_input('Enter user name: ')
  64.     dbuser_pwd = raw_input('Enter user password: ')
  65.     db_server_host = raw_input('Enter server host: ')
  66.     dbport = raw_input('Enter port: ')
  67.     db_name = raw_input('Enter database name: ')
  68.  
  69.     url = '{}+{}://{}:{}@{}:{}/{}'.format(
  70.            dbms, dbdriver, dbuser, dbuser_pwd, db_server_host, dbport, db_name)
  71.  
  72.     # Now we have the required url for the login. We pass
  73.     # the url as an argument to the gui window
  74.  
  75.     try:
  76.         with ManagedSessionScope(url=url, echo_verbose=True) as (session, ScopeSession):
  77.            
  78.             app = QApplication(sys.argv)
  79.             window = MyCustomDialog(session = session)
  80.             window.show()
  81.             sys.exit(app.exec_())
  82.            
  83.     except TypeError:
  84.        
  85.         print "ERROR", format_exc(exc_info())
  86.        
  87. if __name__ == "__main__":
  88.     main()

Zurück zu „Datenbankprogrammierung mit Python“

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder