{ "info": { "author": "Simon Garisch", "author_email": "gatman946@gmail.com", "bugtrack_url": null, "classifiers": [], "description": "[![Build Status](https://travis-ci.org/simongarisch/safeqthreads.svg?branch=master)](https://travis-ci.org/simongarisch/safeqthreads)\r\n[![Coverage Status](https://coveralls.io/repos/github/simongarisch/safeqthreads/badge.svg?branch=master)](https://coveralls.io/github/simongarisch/safeqthreads?branch=master)\r\n[![PyPI version](https://badge.fury.io/py/safeqthreads.svg)](https://badge.fury.io/py/safeqthreads)\r\n\r\n# safeqthreads\r\n\r\nThe motivation behind safeqthreads is to allow Qthreads to finish before the application exits. If the application exits before a QThread is finished the Python garbage collector will release the QThread and potentially cause Python to crash.\r\n\r\n## Installation\r\nsafeqthreads is both python 2 and 3 compatible\r\n```bash\r\npip install safeqthreads\r\n```\r\n\r\n## QThreads and the Python garbage collector (gc)\r\nIf the Python gc releases a QThread before PyQt is finished with it then this can cause Python to crash. Here is a basic example:\r\n```python\r\n# how to crash PyQt 101 ...\r\nfrom PyQt5 import QtWidgets, QtCore\r\nimport time\r\n\r\ndef targetFunc():\r\n time.sleep(5)\r\n\r\ndef runThread():\r\n thread = QtCore.QThread()\r\n thread.started.connect(targetFunc)\r\n thread.start()\r\n\r\nrunThread()\r\n```\r\n![Python crashes](https://github.com/simongarisch/safeqthreads/blob/master/crash.png)\r\n\r\nThis same issue can occur when a PyQt application exits before QThreads are finished.\r\n\r\n## Some background\r\nThere are a couple of ways that you can run threads in PyQt:\r\n- The [threading module](https://docs.python.org/3/library/threading.html) within the Python standard library\r\n- Use [QThread](http://pyqt.sourceforge.net/Docs/PyQt4/qthread.html) from PyQt\r\n\r\nWhen working with PyQt the QThreads option gives you access to additional PyQt functionality such as signals and slots.
\r\n\r\nWe can create threads using the standard library by inheriting from the threading.Thread class and overriding the run method; you'll see many examples of QThread implemented in this way, but it's [not recommended](http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/). Instead, it's suggested that a worker (which inherits from QObject) be created and moved to a thread with its [moveToThread](http://pyqt.sourceforge.net/Docs/PyQt4/qobject.html#moveToThread) method. We'll provide examples of using safeqthreads with both.\r\n\r\n## Using safeqthreads with threads and workers\r\nsafeqthreads.close_all_threads() is the last line that runs at the end of the main function. If you comment this line out then there is a chance that you'll get either of:\r\n- A warning of 'QThread: Destroyed while ethread is still running'\r\n- Python has stopped working (a crash)\r\n```python\r\nimport time\r\nfrom PyQt5 import QtWidgets, QtCore\r\nimport safeqthreads\r\n\r\nclass UpdateSignal(QtCore.QObject):\r\n fire = QtCore.pyqtSignal(int)\r\n\r\nclass SomeWorker(safeqthreads.SafeWorker):\r\n ''' a worker that does something trivial like\r\n incrementing a counter every second\r\n '''\r\n def __init__(self, thread, signal):\r\n # this thread will be passed to the constructor of SafeWorker\r\n # which will register this worker with the thread\r\n super(SomeWorker, self).__init__(thread)\r\n self.signal = signal\r\n self.counter = 0\r\n\r\n def loop(self):\r\n while True:\r\n if self.stop_running:\r\n return\r\n else:\r\n self.counter += 1\r\n self.signal.fire.emit(self.counter)\r\n time.sleep(3)\r\n\r\n\r\nclass MainWindow(QtWidgets.QMainWindow):\r\n def __init__(self):\r\n super(MainWindow, self).__init__()\r\n self.setWindowTitle(\"The Main Window\")\r\n self.resize(300, 300)\r\n self.create_thread()\r\n\r\n def create_thread(self):\r\n # create the signal\r\n signal = self.signal = UpdateSignal()\r\n signal.fire.connect(self.signal_catcher)\r\n\r\n # then create an instance of SafeQThread\r\n thread = self.thread = safeqthreads.SafeQThread()\r\n worker = self.worker = SomeWorker(thread, signal)\r\n\r\n # http://pyqt.sourceforge.net/Docs/PyQt4/qobject.html#moveToThread\r\n worker.moveToThread(thread)\r\n thread.started.connect(worker.loop)\r\n thread.start()\r\n\r\n @QtCore.pyqtSlot(int)\r\n def signal_catcher(self, counter):\r\n self.setWindowTitle(str(counter))\r\n\r\n\r\ndef main():\r\n app = QtWidgets.QApplication([]) # create the app instance\r\n win = MainWindow()\r\n win.show() # show the window\r\n app.exec_() # enter the app mainloop\r\n safeqthreads.close_all_threads() # <-- comment me out and see what happens\r\n\r\n\r\nif __name__ == \"__main__\":\r\n main()\r\n```\r\n\r\nHere is my terminal before and after commenting out 'safeqthreads.close_all_threads()'.\r\n![QThread destroyed](https://github.com/simongarisch/safeqthreads/blob/master/commenting_out.png)\r\n\r\n## Using safeqthreads and inheriting from QThread\r\nAs mentioned earlier, this is not the recommended approach. Use worker and moveToThread() instead as detailed above. In any case, here is an example where we inherit from QThread and override the run method.\r\n```python\r\nimport time\r\nfrom PyQt5 import QtWidgets, QtCore\r\nimport safeqthreads\r\n\r\nclass UpdateSignal(QtCore.QObject):\r\n fire = QtCore.pyqtSignal(int)\r\n\r\n\r\nclass SomeThread(safeqthreads.SafeQThread):\r\n def __init__(self, signal):\r\n super(SomeThread, self).__init__()\r\n self.signal = signal\r\n self.counter = 0\r\n self.start()\r\n\r\n def run(self):\r\n ''' this performs like our worker.loop method \r\n from the previous example '''\r\n while True:\r\n if self.stop_running:\r\n return\r\n else:\r\n self.counter += 1\r\n self.signal.fire.emit(self.counter)\r\n time.sleep(3)\r\n\r\n\r\nclass MainWindow(QtWidgets.QMainWindow):\r\n def __init__(self):\r\n super(MainWindow, self).__init__()\r\n self.setWindowTitle(\"The Main Window\")\r\n self.resize(300, 300)\r\n self.create_thread()\r\n\r\n def create_thread(self):\r\n signal = self.signal = UpdateSignal()\r\n signal.fire.connect(self.signal_catcher)\r\n thread = self.thread = SomeThread(signal) # and create our thread\r\n\r\n @QtCore.pyqtSlot(int)\r\n def signal_catcher(self, counter):\r\n self.setWindowTitle(str(counter))\r\n\r\n\r\ndef main():\r\n app = QtWidgets.QApplication([]) # create the app instance\r\n win = MainWindow()\r\n win.show() # show the window\r\n app.exec_() # enter the app mainloop\r\n safeqthreads.close_all_threads()\r\n\r\n\r\nif __name__ == \"__main__\":\r\n main()\r\n```\r\n\r\n\r\n", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/simongarisch/safeqthreads", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "safeqthreads", "package_url": "https://pypi.org/project/safeqthreads/", "platform": "", "project_url": "https://pypi.org/project/safeqthreads/", "project_urls": { "Homepage": "https://github.com/simongarisch/safeqthreads" }, "release_url": "https://pypi.org/project/safeqthreads/0.0.2/", "requires_dist": [ "QtPy (>=1.5.0)" ], "requires_python": "", "summary": "Track and manage your QThreads.", "version": "0.0.2" }, "last_serial": 4401372, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "5fb34101c6ddd3687c2f602569dba9c1", "sha256": "27208a63cb768c856c4552843fba5ff5d2bc660801240cd1d96242a98aa93640" }, "downloads": -1, "filename": "safeqthreads-0.0.1-py2-none-any.whl", "has_sig": false, "md5_digest": "5fb34101c6ddd3687c2f602569dba9c1", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 9050, "upload_time": "2018-10-20T05:50:38", "url": "https://files.pythonhosted.org/packages/e5/80/e63e7a1cb9cc76578b467150e305280c24b3fb624494a2d54159ca51f571/safeqthreads-0.0.1-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "2f24106f97c2ee7c5e2f511633cdab66", "sha256": "d787d6c83c7f97a7c03fd98da3f94370c06ce4cb40a823c3ed7125beb56b0db7" }, "downloads": -1, "filename": "safeqthreads-0.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "2f24106f97c2ee7c5e2f511633cdab66", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 9027, "upload_time": "2018-10-20T05:52:38", "url": "https://files.pythonhosted.org/packages/c6/f5/91cc570cde692c6175051cce73e0789491124c76dd0863913e27d1ef2ec3/safeqthreads-0.0.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "b42ce6767968881a88443a9051248fd4", "sha256": "74feb6907bedd20c486ce193525e273aa55badf39957cabfa221701df8f5af09" }, "downloads": -1, "filename": "safeqthreads-0.0.1.tar.gz", "has_sig": false, "md5_digest": "b42ce6767968881a88443a9051248fd4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 6163, "upload_time": "2018-10-20T05:50:40", "url": "https://files.pythonhosted.org/packages/a5/a4/de5f2c04a5dc556706d2cc5c959c01e36cb3c2ff0e350c3820cba2648e31/safeqthreads-0.0.1.tar.gz" } ], "0.0.2": [ { "comment_text": "", "digests": { "md5": "659aa6fa129811617bf08e9d2f8367d0", "sha256": "5f2cb569350afd72a2926c703095d0254ba9e53482486ca052fb025aa0278937" }, "downloads": -1, "filename": "safeqthreads-0.0.2-py2-none-any.whl", "has_sig": false, "md5_digest": "659aa6fa129811617bf08e9d2f8367d0", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 9422, "upload_time": "2018-10-22T07:32:20", "url": "https://files.pythonhosted.org/packages/c0/c5/a6f327eddb3f407b86e84b08f74579ca06efad0381ec04316468b250c5ae/safeqthreads-0.0.2-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1b174129cc3e1118341392a4b415c898", "sha256": "f2d9fb6ffb5a9d7557b41d9a6376005d3c8668e310cab073a107ab80581a08d8" }, "downloads": -1, "filename": "safeqthreads-0.0.2-py3-none-any.whl", "has_sig": false, "md5_digest": "1b174129cc3e1118341392a4b415c898", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 9393, "upload_time": "2018-10-22T07:33:19", "url": "https://files.pythonhosted.org/packages/b6/7b/9f76f95e056d85faab1b6bc90f372776611839eb197afc59706654ec91ad/safeqthreads-0.0.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "380060542d65ac2a3ede8d9b5791b99a", "sha256": "1c91bd3ad0c19331ecfdcc9670b2a71123f38c5f11b84f47b35bf8575a305e4e" }, "downloads": -1, "filename": "safeqthreads-0.0.2.tar.gz", "has_sig": false, "md5_digest": "380060542d65ac2a3ede8d9b5791b99a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 6478, "upload_time": "2018-10-22T07:32:21", "url": "https://files.pythonhosted.org/packages/2d/60/3790db75fbadd50b5977b5926e899859e4bd14cb03735b7c56d7e8c121ab/safeqthreads-0.0.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "659aa6fa129811617bf08e9d2f8367d0", "sha256": "5f2cb569350afd72a2926c703095d0254ba9e53482486ca052fb025aa0278937" }, "downloads": -1, "filename": "safeqthreads-0.0.2-py2-none-any.whl", "has_sig": false, "md5_digest": "659aa6fa129811617bf08e9d2f8367d0", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 9422, "upload_time": "2018-10-22T07:32:20", "url": "https://files.pythonhosted.org/packages/c0/c5/a6f327eddb3f407b86e84b08f74579ca06efad0381ec04316468b250c5ae/safeqthreads-0.0.2-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1b174129cc3e1118341392a4b415c898", "sha256": "f2d9fb6ffb5a9d7557b41d9a6376005d3c8668e310cab073a107ab80581a08d8" }, "downloads": -1, "filename": "safeqthreads-0.0.2-py3-none-any.whl", "has_sig": false, "md5_digest": "1b174129cc3e1118341392a4b415c898", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 9393, "upload_time": "2018-10-22T07:33:19", "url": "https://files.pythonhosted.org/packages/b6/7b/9f76f95e056d85faab1b6bc90f372776611839eb197afc59706654ec91ad/safeqthreads-0.0.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "380060542d65ac2a3ede8d9b5791b99a", "sha256": "1c91bd3ad0c19331ecfdcc9670b2a71123f38c5f11b84f47b35bf8575a305e4e" }, "downloads": -1, "filename": "safeqthreads-0.0.2.tar.gz", "has_sig": false, "md5_digest": "380060542d65ac2a3ede8d9b5791b99a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 6478, "upload_time": "2018-10-22T07:32:21", "url": "https://files.pythonhosted.org/packages/2d/60/3790db75fbadd50b5977b5926e899859e4bd14cb03735b7c56d7e8c121ab/safeqthreads-0.0.2.tar.gz" } ] }