PyQt5 Cannot Test for ToolTip in Headless Mode on Windows - pyqt5

I use GitLab for CI and need to test my PyQt5 GUI in headless mode with pytest-qt (I use python 3.8 in Windows 10). To that end, I can run in headless mode by setting the environment variable QT_QPA_PLATFORM to "offscreen" in my pyproject.toml:
[tool.pytest.ini_options]
env = [
"D:QT_QPA_PLATFORM=offscreen"
]
and the following test passes when run in windowed mode, but the tooltip test fails in headless mode. How can I make this pass in headless mode?:
tests/test_view.py
def test_toolbar_statusbar_and_tooltip_messages(app: MainApp, qtbot: QtBot) -> None:
"""Test for correct status bar message when a toolbar item is hovered.
For example, when the user clicks 'File' in the menubar and hovers over 'New', the
statusbar tooltip should read 'Create a new project...'.
Args:
app (MainApp): (fixture) Qt main application
qtbot (QtBot): (fixture) Bot that imitates user interaction
"""
# Arrange
window = app.view
statusbar = window.statusbar
toolbar = window.toolbar
new_action = window.new_action
new_rect = toolbar.actionGeometry(new_action)
tooltip = QtWidgets.QToolTip
# Assert - Precondition
assert statusbar.currentMessage() == ''
assert tooltip.text() == ''
# Act
qtbot.mouseMove(toolbar, new_rect.center())
# Assert - Postcondition
def check_status():
assert statusbar.currentMessage() == 'Create a new project...'
def check_tooltip():
assert tooltip.text() == 'New Project'
qtbot.waitUntil(check_status)
qtbot.waitUntil(check_tooltip)
Here is the remaining code for the MRE:
tests/conftest.py
from typing import Generator, Union, Sequence
import pytest
from pytestqt.qtbot import QtBot
from qtpy import QtCore
from myproj.main import MainApp
# Register plugins to use in testing
pyteset_plugins: Union[str, Sequence[str]] = [
'pytestqt.qtbot',
]
#pytest.fixture(autouse=True)
def clear_settings() -> Generator[None, None, None]:
"""Fixture to clear ``Qt`` settings."""
yield
QtCore.QSettings().clear()
#pytest.fixture(name='app')
def fixture_app(qtbot: QtBot) -> Generator[MainApp, None, None]:
"""``pytest`` fixture for ``Qt``.
Args:
qtbot (QtBot): pytest fixture for Qt
Yields:
Generator[QtBot, None, None]: Generator that yields QtBot fixtures
"""
# Setup
root = MainApp()
root.show()
qtbot.addWidget(root.view)
# Run
yield root
# Teardown
# None
myproj/main.py
from pyvistaqt import MainWindow
from qtpy import QtCore, QtGui, QtWidgets
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication
import resources
class View(MainWindow):
def __init__(
self,
controller: 'MainApp',
) -> None:
"""Display GUI main window.
Args:
controller (): The application controller, in the model-view-controller (MVC)
framework sense
"""
super().__init__()
self.controller = controller
self.setWindowTitle('My Project')
self.container = QtWidgets.QFrame()
self.layout_ = QtWidgets.QVBoxLayout()
self.layout_.setSpacing(0)
self.layout_.setContentsMargins(0, 0, 0, 0)
self.container.setLayout(self.layout_)
self.setCentralWidget(self.container)
self._create_actions()
self._create_menubar()
self._create_toolbar()
self._create_statusbar()
def _create_actions(self) -> None:
"""Create QAction items for menu- and toolbar."""
self.new_action = QtWidgets.QAction(
QtGui.QIcon(resources.NEW_ICO),
'&New Project...',
self,
)
self.new_action.setShortcut('Ctrl+N')
self.new_action.setStatusTip('Create a new project...')
def _create_menubar(self) -> None:
"""Create the main menubar."""
self.menubar = self.menuBar()
self.file_menu = self.menubar.addMenu('&File')
self.file_menu.addAction(self.new_action)
def _create_toolbar(self) -> None:
"""Create the main toolbar."""
self.toolbar = QtWidgets.QToolBar('Main Toolbar')
self.toolbar.setIconSize(QtCore.QSize(24, 24))
self.addToolBar(self.toolbar)
self.toolbar.addAction(self.new_action)
def _create_statusbar(self) -> None:
"""Create the main status bar."""
self.statusbar = QtWidgets.QStatusBar(self)
self.setStatusBar(self.statusbar)
class MainApp:
def __init__(self) -> None:
"""GUI controller."""
self.view = View(controller=self)
def show(self) -> None:
"""Display the main window."""
self.view.showMaximized()
if __name__ == '__main__':
app = QApplication([])
app.setStyle('fusion')
app.setAttribute(Qt.AA_DontShowIconsInMenus, True)
root = MainApp()
root.show()
app.exec_()
Results
Here is the error message I receive in headless mode:
PS> pytest
================================================================================================ test session starts =================================================================================================
platform win32 -- Python 3.8.10, pytest-7.1.2, pluggy-1.0.0
PyQt5 5.15.6 -- Qt runtime 5.15.2 -- Qt compiled 5.15.2
Using --randomly-seed=1234
rootdir: %USERPROFILE%\Code\myproj, configfile: pyproject.toml, testpaths: tests
plugins: hypothesis-6.46.9, cov-3.0.0, doctestplus-0.12.0, env-0.6.2, memprof-0.2.0, qt-4.0.2, randomly-3.12.0, typeguard-2.13.3
collected 5 items
tests\docs_tests\test_index_page.py . [ 20%]
tests\test_view.py ...F [100%]
====================================================================================================== FAILURES ======================================================================================================
____________________________________________________________________________________ test_toolbar_statusbar_and_tooltip_messages _____________________________________________________________________________________
self = <pytestqt.qtbot.QtBot object at 0x000001C4858B78E0>, callback = <function test_toolbar_statusbar_and_tooltip_messages.<locals>.check_tooltip at 0x000001C4858BB9D0>
def waitUntil(self, callback, *, timeout=5000):
"""
.. versionadded:: 2.0
Wait in a busy loop, calling the given callback periodically until timeout is reached.
``callback()`` should raise ``AssertionError`` to indicate that the desired condition
has not yet been reached, or just return ``None`` when it does. Useful to ``assert`` until
some condition is satisfied:
.. code-block:: python
def view_updated():
assert view_model.count() > 10
qtbot.waitUntil(view_updated)
Another possibility is for ``callback()`` to return ``True`` when the desired condition
is met, ``False`` otherwise. Useful specially with ``lambda`` for terser code, but keep
in mind that the error message in those cases is usually not very useful because it is
not using an ``assert`` expression.
.. code-block:: python
qtbot.waitUntil(lambda: view_model.count() > 10)
Note that this usage only accepts returning actual ``True`` and ``False`` values,
so returning an empty list to express "falseness" raises a ``ValueError``.
:param callback: callable that will be called periodically.
:param timeout: timeout value in ms.
:raises ValueError: if the return value from the callback is anything other than ``None``,
``True`` or ``False``.
.. note:: This method is also available as ``wait_until`` (pep-8 alias)
"""
__tracebackhide__ = True
import time
start = time.time()
def timed_out():
elapsed = time.time() - start
elapsed_ms = elapsed * 1000
return elapsed_ms > timeout
timeout_msg = f"waitUntil timed out in {timeout} milliseconds"
while True:
try:
> result = callback()
.venv\lib\site-packages\pytestqt\qtbot.py:510:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
def check_tooltip():
> assert tooltip.text() == 'New Project'
E AssertionError: assert '' == 'New Project'
E - New Project
tests\test_view.py:104: AssertionError
The above exception was the direct cause of the following exception:
app = MainApp(), qtbot = <pytestqt.qtbot.QtBot object at 0x000001C4858B78E0>
def test_toolbar_statusbar_and_tooltip_messages(app: MainApp, qtbot: QtBot) -> None:
"""Test for correct status bar message when a toolbar item is hovered.
For example, when the user clicks 'File' in the menubar and hovers over 'New', the
statusbar tooltip should read 'Create a new project...'.
Args:
app (MainApp): (fixture) Qt main application
qtbot (QtBot): (fixture) Bot that imitates user interaction
"""
# Arrange
window = app.view
statusbar = window.statusbar
toolbar = window.toolbar
new_action = window.new_action
new_rect = toolbar.actionGeometry(new_action)
tooltip = QtWidgets.QToolTip
# Assert - Precondition
assert statusbar.currentMessage() == ''
assert tooltip.text() == ''
# Act
qtbot.mouseMove(toolbar, new_rect.center())
# Assert - Postcondition
def check_status():
assert statusbar.currentMessage() == 'Create a new project...'
def check_tooltip():
assert tooltip.text() == 'New Project'
qtbot.waitUntil(check_status)
> qtbot.waitUntil(check_tooltip)
E pytestqt.exceptions.TimeoutError: waitUntil timed out in 5000 milliseconds
tests\test_view.py:107: TimeoutError
------------------------------------------------------------------------------------------------ Captured Qt messages ------------------------------------------------------------------------------------------------
QtWarningMsg: This plugin does not support propagateSizeHints()
============================================================================================ memory consumption estimates ============================================================================================
tests/test_view.py::test_toolbar_statusbar_and_tooltip_messages - 13.0 MB
tests/test_view.py::test_menubar_statusbar_messages - 136.0 KB
docs_tests::test_index_page.py::test_index[index.html] - 128.0 KB
tests/test_view.py::test_window_appears - 68.0 KB
tests/test_view.py::test_title_correct - 68.0 KB
---------- coverage: platform win32, python 3.8.10-final-0 -----------
Name Stmts Miss Branch BrPart Cover Missing
-------------------------------------------------------------------------
docs\source\conf.py 33 0 2 0 100%
myproj\__init__.py 2 0 0 0 100%
myproj\main.py 11 1 0 0 91% 15
myproj\model.py 3 0 2 0 100%
myproj\view.py 42 1 4 1 96% 13
resources\__init__.py 1 0 0 0 100%
resources\icons\__init__.py 4 0 0 0 100%
-------------------------------------------------------------------------
TOTAL 96 2 8 1 97%
Coverage HTML written to dir logs/coverage/html
Coverage XML written to file logs/coverage/coverage.xml
============================================================================================== short test summary info ===============================================================================================
FAILED tests/test_view.py::test_toolbar_statusbar_and_tooltip_messages - pytestqt.exceptions.TimeoutError: waitUntil timed out in 5000 milliseconds
============================================================================================ 1 failed, 4 passed in 6.41s =============================================================================================
Update
I also run a similar test for my menubar items and found if I didn't run this that the test does pass in headless mode. I wonder if there is something wrong with my fixtures...
The test also passes if I run it individually like so:
PS> pytest ./tests/test_view.py::test_toolbar_statusbar_and_tooltip_messages
Here is the other test I commented out:
def test_menubar_statusbar_messages(app: MainApp, qtbot: QtBot) -> None:
"""Test for correct status bar message when a menu item is hovered.
For example, when the user clicks 'File' in the menubar and hovers over 'New', the
statusbar tooltip should read 'Create a new project...'.
Args:
app (MainApp): (fixture) Qt main application
qtbot (QtBot): (fixture) Bot that imitates user interaction
"""
# Arrange
window = app.view
menubar = window.menubar
statusbar = window.statusbar
file_menu = window.file_menu
new_action = window.new_action
file_rect = menubar.actionGeometry(file_menu.menuAction())
new_rect = file_menu.actionGeometry(new_action)
# Act
qtbot.mouseMove(menubar, file_rect.center())
qtbot.mouseClick(menubar, qt_api.QtCore.Qt.LeftButton, pos=file_rect.center())
qtbot.mouseMove(file_menu, new_rect.center())
qtbot.mousePress(file_menu, qt_api.QtCore.Qt.LeftButton, pos=new_rect.center())
# Assert
def check_status():
assert statusbar.currentMessage() == 'Create a new project...'
qtbot.waitUntil(check_status)

Related

Pool apply function hangs and never executes

I am trying to fetch Rally data by using its python library pyral. Sequentially the same code works, but its slow.
I thought of using python multiprocess package, however my pool.apply method gets stuck and never executes. I tried running it in Pycharm IDE as well as the windows cmd prompt.
import pandas as pd
from pyral import Rally
from multiprocessing import Pool, Manager
from pyral.entity import Project
def process_row(sheetHeaders: list, item: Project, L: list):
print('processing row : ' + item.Name) ## this print never gets called
row = ()
for header in sheetHeaders:
row.append(process_cell(header, item))
L.append(row)
def process_cell(attr, item: Project):
param = getattr(item, attr)
if param is None:
return None
try:
if attr == 'Owner':
return param.__getattr__('Name')
elif attr == 'Parent':
return param.__getattr__('ObjectID')
else:
return param
except KeyError as e:
print(e)
# Projects
# PortfolioItem
# User Story
# Hierarchical Req
# tasks
# defects
# -------------MAIN-----------------
def main():
# Rally connection
rally = Rally('rally1.rallydev.com', apikey='<my_key>')
file = 'rally_data.xlsx'
headers = {
'Project': ['Name', 'Description', 'CreationDate', 'ObjectID', 'Parent', 'Owner', 'State'],
}
sheetName = 'Project'
sheetHeaders = headers.get(sheetName)
p = Pool(1)
result = rally.get(sheetName, fetch=True, pagesize=10)
with Manager() as manager:
L = manager.list()
for item in result:
print('adding row for : ' + item.Name)
p.apply_async(func=process_row, args=(sheetHeaders, item, L)) ## gets stuck here
p.close()
p.join()
pd.DataFrame(L).to_excel(file, sheet_name=sheetName)
if __name__ == '__main__':
main()
Also tried without Manager list without any difference in the outcome
def main():
# Rally connection
rally = Rally('rally1.rallydev.com', apikey='<key>')
file = 'rally_data.xlsx'
headers = {
'Project': ['Name', 'Description', 'CreationDate', 'ObjectID', 'Parent', 'Owner', 'State'],
}
sheetName = 'Project'
sheetHeaders = headers.get(sheetName)
result = rally.get(sheetName, fetch=True, pagesize=10)
async_results = []
with Pool(50) as p:
for item in result:
print('adding row for : ' + item.Name)
async_results.append(p.apply_async(func=process_row, args=(sheetHeaders, item)))
res = [r.get() for r in async_results]
pd.DataFrame(res).to_excel(file, sheet_name=sheetName)
I dont know why, but replacing multiprocessing
with multiprocessing.dummy in the import statement worked.

Pybind11 multiprocessing hangs

I'm writing an application that use Pybind11 to embed Python interpreter (Windows, 64 bit, Visual C++ 2017). From Python, I need to spawn multiple processes, but it seems doesn't works. I try the following code as test:
import multiprocessing
import os
import sys
import time
print("This is the name of the script: ", sys.argv[0])
print("Number of arguments: ", len(sys.argv))
print("The arguments are: " , str(sys.argv))
prefix=str(os.getpid())+"-"
if len(sys.argv) > 1:
__name__ = "__mp_main__"
def print_cube(num):
"""
function to print cube of given num
"""
print("Cube: {}".format(num * num * num))
def print_square(num):
"""
function to print square of given num
"""
print("Square: {}".format(num * num))
print(__name__)
if __name__ == "__main__":
print(prefix, "checkpoint 1")
# creating processes
p1 = multiprocessing.Process(target=print_square, args=(10, ))
p1.daemon = True
p2 = multiprocessing.Process(target=print_cube, args=(10, ))
# starting process 1
p1.start()
print(prefix, "checkpoint 2")
# starting process 2
p2.start()
print(prefix, "checkpoint 3")
# wait until process 1 is finished
print(prefix, "checkpoint 4")
p1.join()
print(prefix, "checkpoint 5")
# wait until process 2 is finished
p2.join()
print(prefix, "checkpoint 6")
# both processes finished
print("Done!")
print(prefix, "checkpoint 7")
time.sleep(10)
Running it with the Python from command prompt, I obtain:
This is the name of the script: mp.py
Number of arguments: 1
The arguments are: ['mp.py']
__main__
12872- checkpoint 1
12872- checkpoint 2
This is the name of the script: C:\tmp\mp.py
Number of arguments: 1
The arguments are: ['C:\\tmp\\mp.py']
__mp_main__
7744- checkpoint 7
Square: 100
12872- checkpoint 3
12872- checkpoint 4
12872- checkpoint 5
This is the name of the script: C:\tmp\mp.py
Number of arguments: 1
The arguments are: ['C:\\tmp\\mp.py']
__mp_main__
15020- checkpoint 7
Cube: 1000
12872- checkpoint 6
Done!
12872- checkpoint 7
which is correct. If I try the same from a C++ project with Pybind11, the output is:
This is the name of the script: C:\AGPX\Documenti\TestPyBind\x64\Debug\TestPyBind.exe
Number of arguments: 1
The arguments are: ['C:\\AGPX\\Documenti\\TestPyBind\\x64\\Debug\\TestPyBind.exe']
__main__
4440- checkpoint 1
This is the name of the script: C:\AGPX\Documenti\TestPyBind\x64\Debug\TestPyBind.exe
Number of arguments: 4
The arguments are: ['C:\\AGPX\\Documenti\\TestPyBind\\x64\\Debug\\TestPyBind.exe', '-c', 'from multiprocessing.spawn import spawn_main; spawn_main(parent_pid=4440, pipe_handle=128)', '--multiprocessing-fork']
__mp_main__
10176- checkpoint 7
Note that, in this case, the variable __name__ is always set to '__main__', so I have to change it manually (for the spawned processes) to '__mp_main__' (I can detect the child processes thanks to the sys.argv). This is the first strange behaviour.
The parent process have pid 4440 and I can see the process in process explorer.
The first child process have pid 10176 and it reach the end 'checkpoint 7' and process disappears from process explorer. However, the main process doesn't print 'checkpoint 2', that is looks like it hangs on 'p1.start()' and I cannot understand why.
The complete C++ code is:
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
#include <pybind11/embed.h>
#include <iostream>
namespace py = pybind11;
using namespace py::literals;
int wmain(int argc, wchar_t **argv)
{
py::initialize_interpreter();
PySys_SetArgv(argc, argv);
std::string pyCode = std::string(R"(
import multiprocessing
import os
import sys
import time
print("This is the name of the script: ", sys.argv[0])
print("Number of arguments: ", len(sys.argv))
print("The arguments are: " , str(sys.argv))
prefix=str(os.getpid())+"-"
if len(sys.argv) > 1:
__name__ = "__mp_main__"
def print_cube(num):
"""
function to print cube of given num
"""
print("Cube: {}".format(num * num * num))
def print_square(num):
"""
function to print square of given num
"""
print("Square: {}".format(num * num))
print(__name__)
if __name__ == "__main__":
print(prefix, "checkpoint 1")
# creating processes
p1 = multiprocessing.Process(target=print_square, args=(10, ))
p1.daemon = True
p2 = multiprocessing.Process(target=print_cube, args=(10, ))
# starting process 1
p1.start()
print(prefix, "checkpoint 2")
# starting process 2
p2.start()
print(prefix, "checkpoint 3")
# wait until process 1 is finished
print(prefix, "checkpoint 4")
p1.join()
print(prefix, "checkpoint 5")
# wait until process 2 is finished
p2.join()
print(prefix, "checkpoint 6")
# both processes finished
print("Done!")
print(prefix, "checkpoint 7")
time.sleep(10)
)");
try
{
py::exec(pyCode);
} catch (const std::exception &e) {
std::cout << e.what();
}
py::finalize_interpreter();
}
Can anyone explain to me how to overcome this problem, please?
Thanks in advance (and I apologize for my english).
Ok, thanks to this link: https://blender.stackexchange.com/questions/8530/how-to-get-python-multiprocessing-module-working-on-windows, I solved this strange issue (that seems to be Windows related).
It's not a Pybind11 issue, but a Python C API itself.
You can solve the issue by setting sys.executable equals to the path of the python interpreter executable (python.exe) and by writing the python code to a file and setting the path to the __file__ variable. That is, I have to add:
import sys
sys.executable = "C:\\Users\\MyUserName\\Miniconda3\\python.exe"
__file__ = "C:\\tmp\\run.py"
and I need to write the python code to the file specified by __file__, that is:
FILE *f = nullptr;
fopen_s(&f, "c:\\tmp\\run.py", "wt");
fprintf(f, "%s", pyCode.c_str());
fclose(f);
just before execute py::exec(pyCode).
In addition the code:
if len(sys.argv) > 1:
__name__ = "__mp_main__"
is no longer necessary. However, note that in this way the runned processes are not embedded anymore and, unfortunately, if you want to directly pass a C++ module to them, you cannot do it.
Hope this can help someone else.

PyGtk Serialization

I am currently working on a Note taking app in pyGtk and have set up a TextView where a user can type and add text tags for Bold Underline and Italics.
However, when it comes to saving the formatted text I cannot figure out how to do so.
I am trying to save in Gtk's native tagset format however after using
tag_format = TextBuffer.register_serialize_tagset()
content = TextBuffer.serialize(self, tag_format, start,end)
I cannot write this to a file with
open(filename, 'w').write(content)
because I get an error which states that it cannot write in bytes and needs a string instead.
I am currently working on a Note taking app in pyGtk and have set up a TextView where a user can type and add text tags for Bold Underline and Italics.
However, when it comes to saving the formatted text I cannot figure out how to do so.
I am trying to save in Gtk's native tagset format however after using
tag_format = TextBuffer.register_serialize_tagset()
content = TextBuffer.serialize(self, tag_format, start,end)
I cannot write this to a file with
open(filename, 'w').write(content)
because I get an error which states that it cannot write in bytes and needs a string instead.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Pango
I am currently working on a Note taking app in pyGtk and have set up a TextView where a user can type and add text tags for Bold Underline and Italics.
However, when it comes to saving the formatted text I cannot figure out how to do so.
I am trying to save in Gtk's native tagset format however after using
tag_format = TextBuffer.register_serialize_tagset()
content = TextBuffer.serialize(self, tag_format, start,end)
I cannot write this to a file with
open(filename, 'w').write(content)
because I get an error which states that it cannot write in bytes and needs a string instead.
File "example.py", line 87, in save_file
open(filename, 'w').write(content)
TypeError: write() argument must be str, not bytes
Here is sample code you can run and test by typing and then saving
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Pango
class MainWindow(Gtk.ApplicationWindow):
def __init__(self):
Gtk.Window.__init__(self, title = "TwoNote")
self.grid = Gtk.Grid()
self.toolbar = Gtk.Toolbar()
self.grid.add(self.toolbar)
#buttons for toolbar
self.button_bold = Gtk.ToggleToolButton()
self.button_italic = Gtk.ToggleToolButton()
self.button_underline = Gtk.ToggleToolButton()
self.button_save = Gtk.ToolButton()
self.button_open = Gtk.ToolButton()
self.mytext = TextSet(self.button_bold, self.button_italic, self.button_underline)
self.button_bold.set_icon_name("format-text-bold-symbolic")
self.toolbar.insert(self.button_bold, 0)
self.button_italic.set_icon_name("format-text-italic-symbolic")
self.toolbar.insert(self.button_italic, 1)
self.button_underline.set_icon_name("format-text-underline-symbolic")
self.toolbar.insert(self.button_underline, 2)
self.toolbar.insert(self.button_save, 3)
self.toolbar.insert(self.button_open, 4)
self.button_open.set_icon_name("document-open-data")
self.button_save.set_icon_name("document-save")
self.button_save.connect("clicked", self.save_file)
self.button_open.connect("clicked", self.open_file)
self.button_bold.connect("toggled", self.mytext.on_button_clicked, "Bold", self.button_italic, self.button_underline)
self.button_italic.connect("toggled", self.mytext.on_button_clicked, "Italic", self.button_bold, self.button_underline)
self.button_underline.connect("toggled", self.mytext.on_button_clicked, "Underline", self.button_bold, self.button_italic)
self.grid.attach_next_to(self.mytext, self.toolbar, Gtk.PositionType.BOTTOM, 10,30)
self.add(self.grid)
filename = "Untitled"
def open_file(self, widget):
open_dialog = Gtk.FileChooserDialog("Open an existing file", self, Gtk.FileChooserAction.OPEN,(Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL,Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
open_response = open_dialog.run()
if open_response == Gtk.ResponseType.OK:
filename = open_dialog.get_filename()
text = open(filename).read()
self.mytext.get_buffer().set_text(text)
open_dialog.destroy()
elif open_response == Gtk.ResponseType.CANCEL:
print("Cancel clicked")
open_dialog.destroy()
def save_file(self, widget):
savechooser = Gtk.FileChooserDialog('Save File', self, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
allfilter = Gtk.FileFilter()
allfilter.set_name('All files')
allfilter.add_pattern('*')
savechooser.add_filter(allfilter)
txtFilter = Gtk.FileFilter()
txtFilter.set_name('Text file')
txtFilter.add_pattern('*.txt')
savechooser.add_filter(txtFilter)
response = savechooser.run()
if response == Gtk.ResponseType.OK:
filename = savechooser.get_filename()
print(filename, 'selected.')
buf = self.mytext.get_buffer()
start, end = buf.get_bounds()
tag_format = buf.register_serialize_tagset()
content = buf.serialize(buf, tag_format, start, end)
try:
open(filename, 'w').write(content)
except SomeError as e:
print('Could not save %s: %s' % (filename, err))
savechooser.destroy()
elif response == Gtk.ResponseType.CANCEL:
print('Closed, file not saved.')
savechooser.destroy()
class TextSet(Gtk.TextView):
def __init__(self, buttonBold, buttonItalic, buttonUnderline, interval = 1 ):
# Textview Setup
Gtk.TextView.__init__(self)
self.set_vexpand(True)
self.set_indent(10)
self.set_top_margin(90)
self.set_left_margin(20)
self.set_right_margin(20)
self.set_wrap_mode(Gtk.WrapMode.CHAR)
self.tb = TextBuffer()
self.set_buffer(self.tb)
# Thread setup
self.button_bold = buttonBold
self.button_italic = buttonItalic
self.button_underline = buttonUnderline
def on_button_clicked(self, widget, tagname, widget1, widget2):
state = widget.get_active()
name = widget.get_icon_name()
bounds = self.tb.get_selection_bounds()
self.tagname = tagname
if(state):
widget1.set_active(False)
widget2.set_active(False)
#highlighting
if(len(bounds) != 0):
start, end = bounds
myIter = self.tb.get_iter_at_mark(self.tb.get_insert())
myTags = myIter.get_tags()
if(myTags == [] and state == True):
self.tb.apply_tag_by_name(tagname, start, end)
elif(myTags != [] and state == True):
self.tb.remove_all_tags(start, end)
self.tb.apply_tag_by_name(tagname, start, end)
else:
for i in range(len(myTags)):
if(myTags[i].props.name == tagname):
self.tb.remove_tag_by_name(tagname,start,end)
myTags = []
self.tb.markup(widget, tagname)
def mouse_clicked(self, window, event):
self.button_bold.set_active(False)
self.button_italic.set_active(False)
self.button_underline.set_active(False)
class TextBuffer(Gtk.TextBuffer):
def __init__(self):
Gtk.TextBuffer.__init__(self)
self.connect_after('insert-text', self.text_inserted)
# A list to hold our active tags
self.taglist_on = []
# Our Bold tag.
self.tag_bold = self.create_tag("Bold", weight=Pango.Weight.BOLD)
self.tag_none = self.create_tag("None", weight=Pango.Weight.NORMAL)
self.tag_italic = self.create_tag("Italic", style=Pango.Style.ITALIC)
self.tag_underline = self.create_tag("Underline", underline=Pango.Underline.SINGLE)
def get_iter_position(self):
return self.get_iter_at_mark(self.get_insert())
def markup(self, widget, tagname):
self.tag_name = tagname
self.check = True
''' add "bold" to our active tags list '''
if(widget.get_active() == True):
if(self.tag_name == 'Bold'):
if 'Bold' in self.taglist_on:
del self.taglist_on[self.taglist_on.index('Bold')]
else:
self.taglist_on.append('Bold')
if(self.tag_name == 'Italic'):
if 'Italic' in self.taglist_on:
del self.taglist_on[self.taglist_on.index('Italic')]
else:
self.taglist_on.append('Italic')
if(self.tag_name == 'Underline'):
if 'Underline' in self.taglist_on:
del self.taglist_on[self.taglist_on.index('Underline')]
else:
self.taglist_on.append('Underline')
else:
self.check = False
def text_inserted(self, buffer, iter, text, length):
# A text was inserted in the buffer. If there are ny tags in self.tags_on, apply them
#if self.taglist_None or self.taglist_Italic or self.taglist_Underline or self.taglist_Bold:
if self.taglist_on:
# This sets the iter back N characters
iter.backward_chars(length)
# And this applies tag from iter to end of buffer
if(self.check == True):
if(self.tag_name == 'Italic'):
self.apply_tag_by_name('Italic', self.get_iter_position(), iter)
if(self.tag_name == 'Bold'):
self.apply_tag_by_name('Bold', self.get_iter_position(), iter)
if(self.tag_name == 'Underline'):
self.apply_tag_by_name('Underline', self.get_iter_position(), iter)
else:
self.remove_all_tags(self.get_iter_position(), iter)
win = MainWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
I figured it out rather than using
open(filename, 'w').write(content)
to save the content I imported GLib and used
GLib.file_set_contents(filename, content)

QMessage is working fine in a class function but not in separate function

Here is my code :
def er():
print("connection error")
from PyQt5.QtWidgets import QMessageBox
msg = QMessageBox()
msg.setIcon(QMessageBox.Information)
msg.setText("Some text ")
msg.setInformativeText("info text")
msg.setWindowTitle("title")
msg.setStandardButtons(QMessageBox.Ok)
retval = msg.exec_()
print(retval)
if __name__ == '__main__':
mac = (':'.join(['{:02x}'.format((getnode() >> i) & 0xff) for i in range(0,8 * 6, 8)][::-1]))
if mac == 'b8:e8:56:24:96:30':
print("OK")
some_function
else:
er()
and the error is 'QWidget: Must construct a QApplication before a QWidget'
You have to init the qt event loop with QApplication first, like the error message said
def er():
print("connection error")
from PyQt5.QtWidgets import QMessageBox,QApplication
import sys
app = QApplication(sys.argv)
msg = QMessageBox()
msg.setIcon(QMessageBox.Information)
msg.setText("Some text ")
msg.setInformativeText("info text")
msg.setWindowTitle("title")
msg.setStandardButtons(QMessageBox.Ok)
retval = msg.exec_()
print(retval)
if __name__ == '__main__':
mac = (':'.join(['{:02x}'.format((getnode() >> i) & 0xff) for i in range(0, 8 * 6, 8)][::-1]))
if mac == 'b8:e8:56:24:96:30':
print("OK")
some_function
else:
er()

How to refer to the text entry widget`s input in a subprocess.call() in Python GTK?

How to refer to the text entry widget`s input in a subprocess.call() in Python GTK? App for calling bioinformatics tool from PyGTK:
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
import subprocess
class EntryExample:
def enter_callback(self, widget, entry):
entry_text = entry.get_text()
print "Entry contents: %s\n" % entry_text
def entry_toggle_editable(self, checkbutton, entry):
entry.set_editable(checkbutton.get_active())
def entry_toggle_visibility(self, checkbutton, entry):
entry.set_visibility(checkbutton.get_active())
def __init__(self):
# create a new window
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
window.set_size_request(200, 100)
window.set_title("GTK Entry")
window.connect("delete_event", lambda w,e: gtk.main_quit())
vbox = gtk.VBox(False, 0)
window.add(vbox)
vbox.show()
entry = gtk.Entry()
entry.set_max_length(50)
entry.connect("activate", self.enter_callback, entry)
entry.set_text("Insert")
entry.insert_text(" SRA accession number", len(entry.get_text()))
entry.select_region(0, len(entry.get_text()))
vbox.pack_start(entry, True, True, 0)
entry.show()
hbox = gtk.HBox(False, 0)
vbox.add(hbox)
hbox.show()
# Create a new button for running Linux Shell script
buttonscript = gtk.Button(label="Download", stock=None)
# Connect the "clicked" signal of the button to the function
buttonscript.connect("clicked", runlinuxshell )
vbox.pack_start(buttonscript, True, True, 0)
buttonscript.set_flags(gtk.CAN_DEFAULT)
buttonscript.grab_default()
buttonscript.show()
button = gtk.Button(stock=gtk.STOCK_CLOSE)
button.connect("clicked", lambda w: gtk.main_quit())
vbox.pack_start(button, True, True, 0)
button.set_flags(gtk.CAN_DEFAULT)
button.grab_default()
button.show()
window.show()
def runlinuxshell ():
subprocess.call('$i=len(entry.get_text()) # Error is here
echo $i
./fastq-dump --split-files $i -v')
def main():
gtk.main()
return 0
if __name__ == "__main__":
EntryExample()
main()
How to pass text input from a widget into the suprocess.call()?
Is there any good example on how to call bioinformatics linux tools in PyGTK?
disclaimer: the sample uses pygobject with introspection and not pygtk which is deprecated for years and should not be used in new code.
disclaimer 2: the sample can be greatly improved to say the least, it's just an adaption of your original script.
You probably would do some like the following:
import gi
from gi.repository import Gtk
import subprocess
class EntryExample:
def __init__(self):
window = Gtk.Window()
window.set_size_request(200, 100)
window.set_title("GTK Entry")
window.connect("delete_event", Gtk.main_quit)
vbox = Gtk.VBox(False, 0)
window.add(vbox)
self.entry = Gtk.Entry()
self.entry.set_max_length(50)
self.entry.set_text("SRA accession number")
vbox.pack_start(self.entry, True, True, 0)
buttonscript = Gtk.Button(label="Download", stock=None)
buttonscript.connect("clicked", self.runlinuxshell)
vbox.pack_start(buttonscript, True, True, 0)
button = Gtk.Button(stock=Gtk.STOCK_CLOSE)
button.connect("clicked", Gtk.main_quit)
vbox.pack_start(button, True, True, 0)
window.show_all()
def runlinuxshell (self, widget):
mylen = len(self.entry.get_text())
# Here you will execute your subprocess with mylen
def main(self):
Gtk.main()
if __name__ == "__main__":
sub = EntryExample()
sub.main()