I'm attempting to dump a pickle file in my PyQt app only its seems to be completely ignoring the statement.
import cPickle as pickle
class MyActions(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
#Create blank dictionary and save it.
self.employee_dict = {}
pickle.dump(self.employee_dict, open("pickle file", 'wb'))
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
QtCore.QObject.connect(self.ui.pushButton, QtCore.SIGNAL('clicked()'), self.emp_display )
QtCore.QObject.connect(self.ui.pushButton_2, QtCore.SIGNAL('clicked()'), self.admin_display)
am i doing something wrong here or is pickle just not compatible with dumping dictionaries in side a PyQt gui.
Even though your question doesn't have enough information to go on, I'm willing to bet I know the answer.
You're creating a file called pickle file in whatever the current directory happens to be.
Then you're going and looking for the pickle file right next to the script, which is in general not going to be the current directory.
In the special case where you're running a script from the command line as ./foo.py or python ./foo.py, of course, the two happen to be the same. But when you double-click a PyQt app, it's… well, that depends on what platform you're on, and a variety of Explorer/Finder/Nautilus/etc. settings (either global, or per-app), but it's generally not going to be the same place the script is.
If you just want to force it to write the file next to the script (which is usually a bad idea), you can save the script directory at startup, like this:
import os
import sys
scriptdir = os.path.dirname(sys.argv[0])
Then, instead of writing to 'pickle file', you can write to os.path.join(scriptdir, 'pickle file').
However, a better solution is to use a platform-appropriate "app data" directory or "home" or "documents" directory (depending on whether you want the file to be visible to novice users); Qt has nice wrappers that make it easy to do that in the platform-appropriate way with platform-independent code.
Related
This question already has an answer here:
QLabel is not updated unless the mainWindow is unfocused
(1 answer)
Closed 1 year ago.
I've been building a fairly simple program using PyQt5 for the GUI for the past few days and I have the following problem which I can't seem to find a solution for.
Here's an oversimplified version of my code (kept the absolute basics to keep things short):
def run_blueprint(file):
# doing stuff to the file and returning the path to the output file
return full_path
class Window(QMainWindow, Ui_MainWindow):
# some variables here
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.connectSignalsSlots()
def connectSignalsSlots(self):
self.pushButton.clicked.connect(self.processFiles)
def processFiles(self):
self.toggleElements()
for i, file in enumerate(self.selected_files_directories):
temp_full_path = run_blueprint(file)
self.listWidget_3.insertItem(i, temp_full_path)
self.toggleElements()
def toggleElements(self):
self.pushButton_2.setEnabled(not self.pushButton_2.isEnabled())
self.listWidget.setEnabled(not self.listWidget.isEnabled())
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())
Let's assume that I have selected an item from the listWidget and also have a list of file paths stored in the selected_files_directories[] variable.
If I run the code as is, when it's time for processFiles(self) to run I get some weird behavior. For some reason the for loop is ran first, then the first toggleElements() and after that the second toggleElements(). This results in the two elements that I want to temporarily disable until the for loop is over staying enabled the whole time.
However if I don't call the run_blueprint(file) method at all, but instead run any other code inside the for loop or even completely discard the loop, those two elements are disabled first, then the rest of the code runs and finally they are enabled again.
I believe the problem has something to do with me calling a static method outside the class. Do you have any ideas on how to solve this issue? Thanks in advance!
Edit: Below is the simplified version of the run_blueprint() function.
The basic functionality of this program is as follows: I select a template file (.docx) and input file(s) (.pdf). Then using the docxtpl and pdfplumber libraries, for each of the selected input files I get specific text pieces from them and place them inside the template document and save that as a .docx file.
In the following code assume that template_path and bp_path show the path to the template file and blueprint file respectively.
def run_blueprint(file):
doc = DocxTemplate(template_path) # docxtpl method
global lns
lns = export_data(file) # function I made that uses pdfplumber to get all the text from a pdf file
global context
context = {}
with open(bp_path, 'r', encoding='utf8') as f:
blueprint = f.read() # blueprint contains python code that populates the context variable. I do it this way to easily change between blueprints
exec(blueprint, globals(), locals()) # I know it's not a good practice to use exec but it's intended for personal use
doc.render(context) # docxtpl method
output_path = "D:\\Desktop" # could be any path
doc.save(output_path) # docxtpl method
return output_path
The blueprint code usually contains API calls that take a few milliseconds to send a response. Apart from that it's mostly regex used to locate the text pieces I'm looking for.
This is an issue with the canvas being redrawn. Add a repaint call after the toggleElements call.
def processFiles(self):
self.toggleElements()
self.repaint()
for i, file in enumerate(self.selected_files_directories):
temp_full_path = run_blueprint(file)
self.listWidget_3.insertItem(i, temp_full_path)
self.toggleElements()
For more context based off the suggestions of the comments by #musicamante and to help clarify:
There isn't an issue with the canvas being repainted. The issue is that the function blocks the main thread. A better solution, given the context, would be to offload the blocking task to a QThread or QProcess to unblock the main thread.
We are looking at deploying a PyQt application on an Azure server, and the application works well enough, albeit a little slow to respond to user actions.
We have a problem, however, and that is that the QFileDialog allows pretty much any explore action: copy a file from the virtual machine to the user's local drive, open a file within 'Program Files (x86)' in Notepad, etc.
Approaches already considered:
As the python application has to have read and write permissions to
run under 'Program Files (x86)', we can't use file permissions to
control access.
We can turn the Python into an inscrutable .exe, but this could
still be copied using the context menus in the file dialog.
We could use the file filters and then hide them, so you can only
see (and mess with) the relevant files, but the user could still
copy entire directories.
The only thing we can think of is to create our own file dialog from scratch, but that's very tedious. Are there any 'out of the box' solutions?
The QFileDialog class already has this functionality:
dialog = QtGui.QFileDialog()
dialog.setOption(QtGui.QFileDialog.ReadOnly, True)
dialog.exec_()
This only seems to work with Qt's built-in file-dialog, though. If you use the static functions to open a native file-dialog, the ReadOnly option seems to be ignored (I've only tested this on Linux, though).
looking at exemple of qtreeview they show a file explorer so i think it's actually not a big task to implement a simple file system explorer. it's specialy easy thanks to QFileSystemModel http://doc.qt.io/qt-5/model-view-programming.html#using-models-and-views
Here's what I actually did, based on #ekhumoro's advice:
from PyQt4 import QtGui
import guidata
import re
class _DirectoryFilterProxyModel(QtGui.QSortFilterProxyModel):
""" A basic filter to be applied to the file items to be displayed.
Based on C++ example at:
https://stackoverflow.com/questions/2101100/qfiledialog-filtering-folders. """
def __init__(self, ignore_directories=[], *args, **kw):
""" Constructor
:param ignore_directories: A list of directories to exclude. These
can be regular expressions or straight names. """
QtGui.QSortFilterProxyModel.__init__(self, *args, **kw)
self.ignore_directories = ignore_directories
def filterAcceptsRow(self, sourceRow, sourceParent):
fileModel = self.sourceModel()
index0 = fileModel.index(sourceRow, 0, sourceParent)
if fileModel:
if fileModel.isDir(index0):
for directory in self.ignore_directories:
if re.match(directory, fileModel.fileName(index0)):
return False
return True
else: # For files
return True
else:
return False
And instantiated:
app = guidata.qapplication()
dialog = QtGui.QFileDialog()
proxyModel = _DirectoryFilterProxyModel(ignore_directories=["Program Files", "Program Files (x86)", "Windows"])
dialog.setProxyModel(proxyModel)
dialog.setOption(QtGui.QFileDialog.ReadOnly, True)
dialog.setOption(QtGui.QFileDialog.HideNameFilterDetails, True)
dialog.exec_()
My thanks to #serge_gubenko and #Gayan on the page qfiledialog - Filtering Folders? for providing the C++ implementation, from which I derived the above.
I've built a (Linux) GUI application that can be launched from a terminal and accepts an undefined number of files as arguments. The app reads sys.argv and lists the name of these files in a QListWidget.
The code is something like:
import sys
from PyQt4.QtGui import QApplication, QMainWindow, QCoreApplication
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
# parse command line arguments
for i in QCoreApplication.argv()[1:]:
...
def main():
app = QApplication(sys.argv)
...
What I want to do is to be able to select multiple files from a file manager and open them with my app through the "Open with..." option provided by file managers. How this can be achieved?
With the current code, when I try it only one of the selected files is shown on the QListWidget.
Edit:
It finally seems that it depends to the file manager.
I tried with a few file managers and...
pcmanfm: It only opens one of the selected files.
spacefm: Works properly!
dolphin: It opens each file to a different instance of my program. If
I select 3 files it will open my app 3 times, one for each file.
nautilus: I didn't manage to open any files with it. My program is not listed in the suggested applications and I didn't find any way to do it.
There's not really enough information to give a definite answer, but...
First, have you checked that a print sys.argv at the top of the code looks like you were expecting?
If so, does it work if you change the line...
for i in QCoreApplication.argv()[1:]:
...to...
for i in sys.argv[1:]:
For debugging purposes, you might also like to include the line...
assert QCoreApplication.argv()[1:] == sys.argv[1:]
...just before you start the for-loop.
Use a QFileDialog: Documentation
I am relatively new to python (already did some 1h scripts like a little webserver or a local network chat) and want to program a plugin manager in it.
My idea is, that there is an interface for plugins, that has the following features:
getDependencies -> all dependencies of the plugin to other plugins
getFunctions -> all functions that this plugin introduces
initialize -> a function that is called when loading the plugin
(I could imagine to have a topological sorting algorithm on the dependencies to decide the order in which the plugins are initialized.)
I would like to implement multithreading, meaning that each plugin runs in its own thread, that has a working queue of function-calls that will be executed serially. When a plugin calls the function of another plugin it calls the manager who will in turn insert the function-call into the queue of the other plugin.
Further the manager should provide some kind of event system in which the plugins can register their own events and become listeners to the events of others.
Also I want to be able to reload a plugin if the code has changed or its thread crashed, without shutting down the manager/application. I already read How do I unload (reload) a Python module? in conjunction with this.
To make it clear once more: The manager should not provide any other functionality than supporting its plugins with a common communication interface to each other, the ability to run side by side (in a multithreaded manner without requiring the plugins to be aware of this) and restoring updated/crashed plugins.
So my questions are: Is it possible to do this in python? And if yes are there design mistakes in this rough sketch? I would appreciate any good advice on this.
Other "literature":
Implementing a Plugin System in Python
At the most basic level, first of all, you want to provide a basic Plugin class which is a base for all plugins written for your application.
Next we need to import them all.
class PluginLoader():
def __init__(self, path):
self.path = path
def __iter__(self):
for (dirpath, dirs, files) in os.walk(self.path):
if not dirpath in sys.path:
sys.path.insert(0, dirpath)
for file in files:
(name, ext) = os.path.splitext(file)
if ext == os.extsep + "py":
__import__(name, None, None, [''])
for plugin in Plugin.__subclasses__():
yield plugin
In Python 2.7 or 3.1+, instead of __import__(name, None, None, ['']), consider:
import importlib # just once
importlib.import_module(name)
This loads every plugin file and gives us all plugins. You would then select your plugins as you saw fit, and then use them:
from multiprocessing import Process, Pipe
plugins = {}
for plugin in PluginLoader("plugins"):
... #select plugin(s)
if selected:
plugins[plugin.__name__], child = Pipe()
p = Process(target=plugin, args=(child,))
p.start()
...
for plugin in plugins.values():
plugin.put("EventHappened")
...
for plugin in plugins.values():
event = plugin.get(False)
if event:
... #handle event
This is just what comes to mind at first. Obviously much more would be needed for flesh this out, but it should be a good basis to work from.
Check yapsy plugin https://github.com/tibonihoo/yapsy. This should work for you
It seems that if I want to create a very basic Cocoa application with a dock icon and the like, I would have to use Xcode and the GUI builder (w/ PyObjC).
The application I am intending to write is largely concerned with algorithms and basic IO - and thus, not mostly related to Apple specific stuff.
Basically the application is supposed to run periodically (say, every 3 minutes) .. pull some information via AppleScript and write HTML files to a particular directory. I would like to add a Dock icon for this application .. mainly to showing the "status" of the process (for example, if there is an error .. the dock icon would have a red flag on it). Another advantage of the dock icon is that I can make it run on startup.
Additional bonus for defining the dock right-click menu in a simple way (eg: using Python lists of callables).
Can I achieve this without using Xcode or GUI builders but simply using Emacs and Python?
Install the latest py2app, then make a new directory -- cd to it -- in it make a HelloWorld.py file such as:
# generic Python imports
import datetime
import os
import sched
import sys
import tempfile
import threading
import time
# need PyObjC on sys.path...:
for d in sys.path:
if 'Extras' in d:
sys.path.append(d + '/PyObjC')
break
# objc-related imports
import objc
from Foundation import *
from AppKit import *
from PyObjCTools import AppHelper
# all stuff related to the repeating-action
thesched = sched.scheduler(time.time, time.sleep)
def tick(n, writer):
writer(n)
thesched.enter(20.0, 10, tick, (n+1, writer))
fd, name = tempfile.mkstemp('.txt', 'hello', '/tmp');
print 'writing %r' % name
f = os.fdopen(fd, 'w')
f.write(datetime.datetime.now().isoformat())
f.write('\n')
f.close()
def schedule(writer):
pool = NSAutoreleasePool.alloc().init()
thesched.enter(0.0, 10, tick, (1, writer))
thesched.run()
# normally you'd want pool.drain() here, but since this function never
# ends until end of program (thesched.run never returns since each tick
# schedules a new one) that pool.drain would never execute here;-).
# objc-related stuff
class TheDelegate(NSObject):
statusbar = None
state = 'idle'
def applicationDidFinishLaunching_(self, notification):
statusbar = NSStatusBar.systemStatusBar()
self.statusitem = statusbar.statusItemWithLength_(
NSVariableStatusItemLength)
self.statusitem.setHighlightMode_(1)
self.statusitem.setToolTip_('Example')
self.statusitem.setTitle_('Example')
self.menu = NSMenu.alloc().init()
menuitem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
'Quit', 'terminate:', '')
self.menu.addItem_(menuitem)
self.statusitem.setMenu_(self.menu)
def writer(self, s):
self.badge.setBadgeLabel_(str(s))
if __name__ == "__main__":
# prepare and set our delegate
app = NSApplication.sharedApplication()
delegate = TheDelegate.alloc().init()
app.setDelegate_(delegate)
delegate.badge = app.dockTile()
delegate.writer(0)
# on a separate thread, run the scheduler
t = threading.Thread(target=schedule, args=(delegate.writer,))
t.setDaemon(1)
t.start()
# let her rip!-)
AppHelper.runEventLoop()
Of course, in your real code, you'll be performing your own periodic actions every 3 minutes (rather than writing a temp file every 20 seconds as I'm doing here), doing your own status updates (rather than just showing a counter of the number of files written so far), etc, etc, but I hope this example shows you a viable starting point.
Then in Terminal.App cd to the directory containing this source file, py2applet --make-setup HelloWorld.py, python setup.py py2app -A -p PyObjC.
You now have in subdirectory dist a directory HelloWorld.app; open dist and drag the icon to the Dock, and you're all set (on your own machine -- distributing to other machines may not work due to the -A flag, but I had trouble building without it, probably due to mis-installed egg files laying around this machine;-). No doubt you'll want to customize your icon &c.
This doesn't do the "extra credit" thingy you asked for -- it's already a lot of code and I decided to stop here (the extra credit thingy may warrant a new question). Also, I'm not quite sure that all the incantations I'm performing here are actually necessary or useful; the docs are pretty latitant for making a pyobjc .app without Xcode, as you require, so I hacked this together from bits and pieces of example code found on the net plus a substantial amount of trial and error. Still, I hope it helps!-)
PyObjC, which is included with Mac OS X 10.5 and 10.6, is pretty close to what you're looking for.
Chuck is correct about PyObjC.
You should then read about this NSApplication method to change your icon.
-(void)setApplicationIconImage:(NSImage *)anImage;
For the dock menu, implement the following in an application delegate. You can build an NSMenu programmatically to avoid using InterfaceBuilder.
-(NSMenu *)applicationDockMenu:(NSApplication *)sender;