PyQt5 + logging library - python-3.x

I want to redirect all the logging to QPlainTextEdit. I have been able to achieve that by following this thread. However, I have a bit of problem understand the actual code:
import logging
import sys
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5 import QtWidgets
class Handler(QObject, logging.Handler):
new_record = pyqtSignal(object)
def __init__(self):
super().__init__()
super(logging.Handler).__init__()
def emit(self, record):
msg = self.format(record)
self.new_record.emit(msg)
class Formatter(logging.Formatter):
def formatException(self, ei):
result = super(Formatter, self).formatException(ei)
return result
def format(self, record):
s = super(Formatter, self).format(record)
if record.exc_text:
s = s.replace('\n', '')
return s
class MyTextBox(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
container = QtWidgets.QGroupBox()
layout = QtWidgets.QVBoxLayout()
self.text_box = QtWidgets.QPlainTextEdit()
self.text_box.setReadOnly(True)
layout.addWidget(self.text_box)
self.btn = QtWidgets.QPushButton("Click me")
layout.addWidget(self.btn)
container.setLayout(layout)
self.setCentralWidget(container)
handler = Handler()
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)
handler.new_record.connect(self.text_box.appendPlainText)
self.btn.clicked.connect(self.heClickedMe)
def heClickedMe(self):
logging.debug('damn, a bug')
logging.info('something to remember')
logging.warning('that\'s not right')
logging.error('foobar')
app = QtWidgets.QApplication(sys.argv)
dlg = MyTextBox()
dlg.show()
sys.exit(app.exec_())
So I have trouble understanding the "emit" method in Handler class. For example, is "emit" a method of logging.Handler or QObject? When will "emit" be called?

is "emit" a method of logging.Handler or QObject?: To know the methods of the classes you have to use the docs and clearly emit is a method of logging.Handler.
When will "emit" be called?: This method is called internally when using the debug, info, warning and error methods.

Related

Replace built-in method of class instance. Python3.8. Pyside2

I want to replace the built-in method closeEvent of QMainWindow class instance that handles the form close event.
CODE #1
import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication
from PySide2.QtCore import QFile, QIODevice
app = QApplication(sys.argv)
ui_file_name = "ui\Main.ui"
ui_file = QFile(ui_file_name)
if not ui_file.open(QIODevice.ReadOnly):
print("Cannot open {}: {}".format(ui_file_name, ui_file.errorString()))
sys.exit(-1)
loader = QUiLoader()
window = loader.load(ui_file)
ui_file.close()
if not window:
print(loader.errorString())
sys.exit(-1)
def MainFormCloseEvent(event):
print(event)
event.ignore()
print(window.closeEvent)
window.closeEvent=MainFormCloseEvent
print(window.closeEvent)
window.show()
sys.exit(app.exec_())
This code does not cause the MainFormCloseEvent function to be called when the form closes.
This code print the following information:
<built-in method closeEvent of PySide2.QtWidgets.QMainWindow object at 0x000000000573BF80>
<function MainFormCloseEvent at 0x0000000002C37430>
But this code works well
CODE #2
import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication
from PySide2.QtCore import QFile, QIODevice
from PySide2.QtWidgets import QMainWindow
def MainFormCloseEvent(event):
print(event)
event.ignore()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
def closeEvent(self, event):
print('Original class method')
app = QApplication(sys.argv)
window = MainWindow()
print(window.closeEvent)
window.closeEvent=MainFormCloseEvent
print(window.closeEvent)
window.show()
sys.exit(app.exec_())
This code print the following information:
<bound method MainWindow.closeEvent of <main.MainWindow(0x51522b0) at 0x0000000004E8AF40>>
<function MainFormCloseEvent at 0x0000000002C37430>
<PySide2.QtGui.QCloseEvent object at 0x0000000004E8F340>
I can't understand the fundamental difference between these codes. I replace the class instance method in the same way, but in the first case it does not work, but in the second it works.
I only noticed the difference that in the first code, the closeEvent method is built-in and in the second code, the closeEvent method is bound. But I did not find in Google what it means and how to make the first code work.
No, you're not replacing the method in the same way.
While creating a method as class attribute and overwriting an instance method at runtime usually has similar results, at the lower level it's not the same, which is extremely important when using complex modules like python binding to libraries written in other languages.
In general, attribute overwriting for methods should be done with extreme care only when it's safe to do it (and you do know what you're doing). It's also important to note that doing it for event handlers is risky, it makes debugging confusing and it also makes calling the default implementation more complex and awkward (you cannot call super()).
Unfortunately, PySide doesn't directly supports setting the UI on an existing widget instance, which is what you would do with PyQt and using a proper class, like in your second example), but there is a possible workaround, as explained in this related post.
class UiLoader(QtUiTools.QUiLoader):
_baseinstance = None
def createWidget(self, classname, parent=None, name=''):
if parent is None and self._baseinstance is not None:
widget = self._baseinstance
else:
widget = super().createWidget(classname, parent, name)
if self._baseinstance is not None:
setattr(self._baseinstance, name, widget)
return widget
def loadUi(self, uifile, baseinstance=None):
self._baseinstance = baseinstance
widget = self.load(uifile)
QtCore.QMetaObject.connectSlotsByName(baseinstance)
return widget
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
ui_file_name = "ui\Main.ui"
ui_file = QFile(ui_file_name)
ui_file.open(QIODevice.ReadOnly)
loader = UiLoader()
loader.loadUi(ui_file, self)
def closeEvent(self, event):
print('Original class method')
Still, you shouldn't overwrite the closeEvent with a basic function, instead you should probably opt for signals, further subclassing, or implement alternate ways to change the behavior (ie, using instance attributes).

How to right use "Pool" in parallel downloading files?

I want use a parallel downloading videos from youtube, but my code ending with exception "PicklingError". Can you help guys with code, how it should be, please.
Another fixed variant:
import sys
#from pathos.multiprocessing import ProcessingPool as Pool
from multiprocessing import Pool
from pytube import YouTube
from youtubeMultiDownloader import UiMainWindow
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog
class YouTubeInstance:
def __init__(self, path):
self.youtube = YouTube
self.path = path
#self.ui_obj = ui_obj
def download_file(self, url):
self.youtube(url).streams.get_highest_resolution().download(self.path)
#self.ui.ui.youtube_outputs.setText(f'Video \'{self.youtube.title}\' has been downloaded successfully!')
class YouTubeMultiDownloader(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.pool = Pool
self.ui = UiMainWindow()
self.ui.setup_ui(self)
self.path_to_dir = None
self.urls = None
def _get_urls_from_form(self):
self.urls = self.ui.youtube_urls.toPlainText().split('\n')
return len(self.urls)
def choose_directory(self):
self.path_to_dir = str(QFileDialog.getExistingDirectory(self, "Select Directory"))
def run_multi_downloads(self):
youtube = YouTubeInstance(self.path_to_dir)
self.pool(self._get_urls_from_form()).map(youtube.download_file, self.urls)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
application = YouTubeMultiDownloader()
application.show()
sys.exit(app.exec_())
Updated:
My ui :)
Error 1 fixed:
Error 2 fixed:
Error 3 actual:
You've got the wrong side of the stick. Take a look at multiprocessing module documents. As it says, calling Pool method is for running multiple instance of same function simultaneously (in parallel). So call Pool method as many numbers you want, meanwhile your method does not any parameters, call it without any arguments:
with Pool(5) as p:
print(p.map(YouTubeMultiDownloader))
It create 5 parallel instance. You can change the code an refine your errors.

i want to implement progress bar for downloading git repo in pyqt5

how can I make progress bar for cloning git repository in pyqt5
git.Repo.clone_from('https://github.com/addddd123/Osdag', '_new_update')
You have to execute the task in another thread, connect to the callback and send the progress information through signals:
import threading
import sys
from dataclasses import dataclass, field
from typing import List, Optional, Any, Mapping, Dict
from PyQt5 import QtCore, QtWidgets
import git
class GitReply(QtCore.QObject):
pass
#dataclass
class GitCloneReply(GitReply):
progress_changed = QtCore.pyqtSignal(int)
started = QtCore.pyqtSignal()
finished = QtCore.pyqtSignal()
url: str
path: str
env: Optional[Mapping[str, Any]] = None
multi_options: Optional[List[str]] = None
kwargs: Dict[str, Any] = field(default_factory=dict)
def __post_init__(self):
super().__init__()
def start(self):
threading.Thread(target=self._execute, daemon=True).start()
def _execute(self):
self.started.emit()
repo = git.Repo.clone_from(
self.url,
self.path,
self.callback,
self.env,
self.multi_options,
**self.kwargs
)
self.finished.emit()
def callback(self, op_code, cur_count, max_count=None, message=""):
self.progress_changed.emit(int((cur_count / max_count) * 100))
#dataclass
class RepoManager(QtCore.QObject):
_replies: List[GitReply] = field(init=False, default_factory=list)
def __post_init__(self):
super().__init__()
def clone_from(self, url, path, env=None, multi_options=None, **kwargs):
reply = GitCloneReply(url, path, env, multi_options, kwargs)
reply.finished.connect(self.handle_finished)
reply.start()
self._replies.append(reply)
return reply
def handle_finished(self):
reply = self.sender()
if reply in self._replies:
self._replies.remove(reply)
def main():
app = QtWidgets.QApplication(sys.argv)
progressbar = QtWidgets.QProgressBar()
progressbar.show()
manager = RepoManager()
reply = manager.clone_from("https://github.com/addddd123/Osdag", "_new_update")
reply.progress_changed.connect(progressbar.setValue)
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main()
```

RuntimeError: Please destroy the QApplication singleton before creating a new QApplication instance

When i run python-pyside2 project server first, then it works well.
And the site also work well, but if i press F5 btn to refresh in browser.
Site shows error page Runtime at/
import sys
from urllib.request import urlopen
from bs4 import BeautifulSoup
from PySide2.QtGui import *
from PySide2.QtCore import *
from PySide2.QtWebKitWidgets import *
from PySide2.QtWidgets import QApplication
class dynamic_render(QWebPage):
def __init__(self, url):
self.frame = None
self.app = QApplication(sys.argv)
QWebPage.__init__(self)
self.loadFinished.connect(self._loadFinished)
self.mainFrame().load(QUrl(url))
QTimer.singleShot(0, self.sendKbdEvent)
QTimer.singleShot(100, app.quit)
self.app.exec_()
def _loadFinished(self, result):
self.frame = self.mainFrame()
self.app.quit()
self.app = None
Below, scaping code using pyside2:
I don't know how can i fix it?
Best regards.
Thanks.
Check if already an instance of QApplication is present or not as the error occurs when an instance is already running and you are trying to create a new one.
Write this in your main function:
if not QtWidgets.QApplication.instance():
app = QtWidgets.QApplication(sys.argv)
else:
app = QtWidgets.QApplication.instance()
For my pyside2 unit test the following worked fairly well
import PySide2
import unittest
class Test_qapplication(unittest.TestCase):
def setUp(self):
super(Test_qapplication, self).setUp()
if isinstance(PySide2.QtGui.qApp, type(None)):
self.app = PySide2.QtWidgets.QApplication([])
else:
self.app = PySide2.QtGui.qApp
def tearDown(self):
del self.app
return super(Test_qapplication, self).tearDown()
it was loosely based on stack overflow : unit-and-functional-testing-a-pyside-based-application

Python 3 threadings.Thread callback error when targeting abstract method

base_class.py
import threading
import websocket
from abc import ABCMeta, abstractmethod
class A:
__metaclass__ = ABCMeta
def __init__(self):
self.some_var = 0
#abstractmethod
def failed_method(self):
pass
def on_websocket_message(self, ws, msg):
var = self.failed_method()
print(var)
...
def open_websocket(self):
ws = websocket.WebSocketApp('http://some_url.com',
on_message=self.on_websocket_message, ...)
ws.run_forever()
def callback_method(self):
websocket_thread = threading.Thread(target=self.open_websocket, name='some_websocket_name')
another_var = self.failed_method()
print('Another variable\'s value is [{}]'.format(another_var))
child_class.py
from base_class import A
class B(A):
def failed_method(self):
return 3
other_class.py
import threading
from child_class import B
def main():
child_class_instance = B()
some_thread = threadings.Thread(target=child_class_instance.callback_method, name='some_name')
some_thread.start()
The result of main() is printed None, not 3, i.e., abstract class' method is called instead of child's one. (Assume all the modules are in the one place.)
Can anyone explain this behaviour? Or what is the thing I do not understand in inheritance in Python combined with threadings.Thread?
P.S. With the similar code I've met error from callback <bound method BaseClass... of < ...ChildClass...> > from websocket.WebSocketApp._callback().
P.P.S. Important to note that I use websocket-client, not websockets.

Resources