PyQt4: Interrupted system call while calling commands.getoutput() in timer - python

The problem appeared to be very simple, but I can not find any solution after a day of googling and looking at stackoverflow.
Originally I am developing a simple plasmoid which will send a specific request to local web-server every 30 minutes, parse output and display in a label on panel. I took an example of plasmoid - BWC-Balance - and modified it. Here is the code:
#!/usr/bin/env python
# coding: utf-8
"""
BWC Balance plasmoid
Site: http://bitbucket.org/svartalf/bwc-balance-plasmoid/
Author: SvartalF (http://svartalf.info)
Original idea: m0nochr0me (http://m0nochr0me.blogspot.com)
"""
import re
from urllib import urlencode
import urllib2
import cookielib
import datetime
import sys
import re
import string
import os
import gobject
import commands
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyKDE4.kio import *
from PyKDE4.kdeui import *
from PyKDE4.kdecore import *
from PyKDE4.plasma import Plasma
from PyKDE4 import plasmascript
from PyKDE4.solid import Solid
from settings import SettingsDialog
parsed_ok = 0
curr_day = ''
class BWCBalancePlasmoid(plasmascript.Applet):
"""Applet main class"""
def __init__(self, parent, args=None):
plasmascript.Applet.__init__(self, parent)
def init(self):
"""Applet settings"""
self.setHasConfigurationInterface(True)
self.setAspectRatioMode(Plasma.Square)
self.theme = Plasma.Svg(self)
# self.theme.setImagePath("widgets/background")
# self.setBackgroundHints(Plasma.Applet.DefaultBackground)
self.layout = QGraphicsLinearLayout(Qt.Horizontal, self.applet)
# Main label with balance value
self.label = Plasma.Label(self.applet)
self.label.setText(u'<b><font color=blue size=3>No data...</font></b>')
self.layout.addItem(self.label)
self.applet.setLayout(self.layout)
self.resize(350, 30)
self.startTimer(2500)
def postInit(self):
"""Start timer and do first data fetching
Fired only if user opened access to KWallet"""
self.setLabelText()
def update(self, value):
"""Update label text"""
self.label.setText(value)
def timerEvent(self, event):
"""Create thread by timer"""
self.setLabelText()
pass
def setLabelText(self):
login = 'mylogin'
request = 'curl --ntlm -sn http://some.local.resource'
out_exp = ""
out_exp = commands.getoutput(request)
table_name_exp = re.findall(r"some_regular_expression",out_exp)
tp = '| html2text | grep -i -A3 ' + login
out_exp = ''
try:
cmd_exp = 'curl --ntlm -sn ' + table_name_exp[0] + ' ' + tp
out_exp = commands.getoutput(cmd_exp)
except:
cmd_exp = ''
date_check = re.findall(r"one_more_regular_expression", out_exp)
times_exp = re.findall(r"[0-9][0-9]:[0-9][0-9]", out_exp )
if len(times_exp) != 0 and len(date_check) != 0:
self.label.setText(u'<b><font color=blue size=3>Start: ' + times_exp[0] + u' --- Finish: ' + str(int(string.split(times_exp[0], ':')[0]) + 9) + ':' + string.split(times_exp[0], ':')[1] + ' </span></b>')
else:
self.label.setText(u'<b><font color=blue size=3>No data...</span></b>')
def CreateApplet(parent):
return BWCBalancePlasmoid(parent)
And what I get is the following error:
# plasmoidviewer bwc-balance
plasmoidviewer(25255)/kdecore (services) KServiceFactory::findServiceByDesktopPath: "" not found
plasmoidviewer(25255)/libplasma Plasma::FrameSvg::resizeFrame: Invalid size QSizeF(0, 0)
plasmoidviewer(25255)/libplasma Plasma::FrameSvg::resizeFrame: Invalid size QSizeF(0, 0)
Traceback (most recent call last):
File "/home/grekhov/.kde/share/apps/plasma/plasmoids/bwc-balance/contents/code/main.py", line 116, in timerEvent
self.setLabelText()
File "/home/grekhov/.kde/share/apps/plasma/plasmoids/bwc-balance/contents/code/main.py", line 146, in setLabelText
out_exp = commands.getoutput(request)
File "/usr/lib/python2.7/commands.py", line 50, in getoutput
return getstatusoutput(cmd)[1]
File "/usr/lib/python2.7/commands.py", line 60, in getstatusoutput
text = pipe.read()
IOError: [Errno 4] Interrupted system call
As I understood after several hours of googling: reading from pipe is interrupted with some signal. But the only signal I have is timer. The only recommendation I have found is "get rid of the signal which interrupts your read". And it appears a bit strange and unrealistic for me: read data periodically without timer.
Am I missing something? Maybe there should be used some other mechanism for accessing web-resource and parsing its output? Or "Interrupted system call" is a normal situation and should be handled somehow?
Thanks in advance for help.

It appears that a signal is being delivered whilst the pipe is still reading.
So try stopping the timer before calling setLabelText(), and then restart it again afterwards.
EDIT
You should also try rewriting your code to use subprocess instead of the deprecated commands module. For example:
pipe = subprocess.Popen(['curl', '--ntlm', '-sn',
'http://some.local.resource'],
stdout=subprocess.PIPE)
output = pipe.communicate()[0]

Related

Problem to reload a loop with Qdial, signal & subprocess

I am trying to record and use data with a Qdial.
For the recording every thing seems to be fine, but in order to use this data in while loop I have to reload the while loop. When I do so with signal and a subprocess, the loop seems to use every data recorded, another problem is that the loop don’t stop at the end of the process.
I have no doubt I am using the wrong method.
Sorry for my bad English, and thank you very much for your help.
So I am using 3 scripts: Qdial.py, Data.py, Loop.py.
The Data.py is an accumulation of data, here I use just one:
A1 = 10
The Loop.py is in deed my main script, here I resume:
import time
from Data import *
def loop():
while True:
print('A1 = ', A1)
time.sleep(0.1)
loop()
[edit] Instead of this Loop.py I will not use from Data import *,
but this one witch don't raise errors (but there is always the problem of the continuous looping after the exit on Qdial.py):
import time
def loop():
with open('/address/Data.py', 'r') as file:
lines = file.readlines()
A1 = lines[0]
A1 = float(A1[4:-1])
print('A1 = ', A1)
while True:
loop()
time.sleep(0.09)
[/edit]
The Qdial.py is a common one, with the signal and a subprocess that raise false values:
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *
from Data import *
import sys, signal, subprocess, time
[edit] This line is no longer needed, and cause an endless loop.
I launch Loop.py with a subprocess:
proc = subprocess.Popen(['python3', '/address/Loop.py'])
[/edit]
Then it’s the writing for the Qdial:
class Window(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(300, 300, 150, 150)
vbox = QVBoxLayout()
self.a1 = QDial(self)
self.value = int(A1)
self.a1.setValue(self.value)
self.value = self.a1.value()
self.lab1 = QtWidgets.QLabel(self)
self.lab1.setText('A1 = ' + str(A1) + 's')
self.a1.valueChanged.connect(self.dial)
vbox.addWidget(self.a1)
vbox.addWidget(self.lab1)
self.setLayout(vbox)
self.show()
def dial(self):
val1 = self.a1.value()
self.lab1.setText('A1 = ' + str(val1) + 's')
with open('/address/data.py', 'r') as file:
lines = file.readlines()
lines[0] = 'A1 = ' + str(val1) + '\n'
with open('/address/data.py', 'w') as file:
for line in lines:
file.write(line)
file.close()
[edit] Thoses lines are no longer define or needed.
Here I use signal and subprocess in order to reload the data in Loop.py:
proc.send_signal(signal.SIGINT)
subprocess.Popen(['python3', '/address/Loop.py'])
[/edit]
And I finish:
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())
[edit] A solution to escape the endless loop was to run both scripts (Qdial.py & Loop.py) as subprocess from a main script and use Popen.wait(), Main.py:
import subprocess, signal
Qd = subprocess.Popen(['python3', '/address/Qdial.py'])
Lo = subprocess.Popen(['python3', '/address/Loop.py'])
if Qd.wait() != None:
Lo.send_signal(signal.SIGINT)
[/edit]
Of course I don’t understand what is wrong, sorry for the length of this post...Thanks again.

PyQt 5 QTableWidget.cellClicked Signal Not Working

I am trying to make a simple files app (or, file explorer app) and I am using the QTableWidget to Display the files and directories. When the user clicks an directory, I want the program to jump to that directory. I have used the QTableWidget.cellClicked signal, and it does not currently work.
The signal part:
self.filesTable.connect(print)#self.updateUiCellClick)
Added print instead of self.updateUiCellClick for debugging purposes.
Code (probably you do not need this):
#!/usr/bin/python3
print('i Import Modules')
print(' | Import sys')
import sys
print(' | Import PyQt5.QtCore')
from PyQt5.QtCore import *
print(' | Import PyQt5.QtGui')
from PyQt5.QtGui import *
print(' | Import PyQt5.QtWidgets')
from PyQt5.QtWidgets import * # PyQt5 Support
print(' | Import os')
import os
print(' | Import subprocess.Popen') # For backward-compatibility
from subprocess import Popen, PIPE
print(' | Done')
print('i Define class Form')
class root(QMainWindow):
def __init__(self, parent=None):
'''self.__init__ - Initializes QMainWindow'''
print(' self.__init__ - Initializes QMainWindow')
super(root, self).__init__(parent)
# Create Variables
self.currentPath = '/'
os.chdir(self.currentPath)
self.currentItems = os.listdir()
self.currentItemsLsProcess = Popen(['ls','-l'], stdout=PIPE, stderr=PIPE)
self.currentItemsLsProcessResult = self.currentItemsLsProcess.communicate()
if self.currentItemsLsProcessResult[1].decode('utf-8'):
QMessageBox.warning(self,'Files - ls -l Error','ls -l responded with non-blank stderr.Error is shown here:<br><code>{}</code><br><hr><br>Error LsStderr (e-lsstderr)<br><hr><br>If you want to support the team, go to the GitHub Repository.'.format(self.currentItemsLsProcessResult[1].decode('utf-8')))
self.currentItemsLs = self.currentItemsLsProcessResult[0].decode('utf-8').split('\n')[1:-1]
# Create Table Widget
self.filesTable = QTableWidget()
# Init Table Widget
self.filesTable.clear()
self.filesTable.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
self.filesTable.setRowCount(len(self.currentItems))
self.filesTable.setColumnCount(4)
self.filesTable.setHorizontalHeaderLabels(['Name','TimeStamp','Type','ls -l'])
# self.filesTable.setReadOnly(1)
# Create & Add Items
self.itemWidgets = [[],[],[],[]]
for i in range(len(self.currentItems)):
self.itemWidgets[0].append(QTableWidgetItem(self.currentItems[i]))
self.filesTable.setItem(i,0,self.itemWidgets[0][-1])
self.itemWidgets[3].append(QTableWidgetItem(self.currentItemsLs[i]))
self.filesTable.setItem(i,3,self.itemWidgets[3][-1])
# Init Widgets
# Align Widgets to root
self.setCentralWidget(self.filesTable)
# Signals-and-Slots
print('i Set self title')
self.setWindowTitle('{}'.format(self.currentPath))
def updateUi(self):
'''self.updateUi - None'''
os.chdir(self.currentPath)
self.currentItems = os.listdir()
self.currentItemsLsProcess = Popen(['ls','-l'], stdout=PIPE, stderr=PIPE)
self.currentItemsLsProcessResult = self.currentItemsLsProcess.communicate()
if self.currentItemsLsProcessResult[1].decode('utf-8'):
QMessageBox.warning(self,'Files - ls -l Error','ls -l responded with non-blank stderr.Error is shown here:<br><code>{}</code><br><hr><br>Error LsStderr (e-lsstderr)<br><hr><br>If you want to support the team, go to the GitHub Repository.'.format(self.currentItemsLsProcessResult[1].decode('utf-8')))
self.currentItemsLs = self.currentItemsLsProcessResult[0].decode('utf-8').split('\n')[1:-1]
self.filesTable.clear()
self.filesTable.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
self.filesTable.setRowCount(len(self.currentItems))
self.filesTable.setColumnCount(4)
self.filesTable.setHorizontalHeaderLabels(['Name','TimeStamp','Type','ls -l'])
self.itemWidgets = [[],[],[],[]]
for i in range(len(self.currentItems)):
self.itemWidgets[0].append(QTableWidgetItem(self.currentItems[i]))
self.filesTable.setItem(i,0,self.itemWidgets[0][-1])
self.filesTable..connect(print)#self.updateUiCellClick)
self.itemWidgets[3].append(QTableWidgetItem(self.currentItemsLs[i]))
self.filesTable.setItem(i,3,self.itemWidgets[3][-1])
self.filesTable.resizeColumnsToContents()
self.setWindowTitle('{}'.format(self.currentPath))
def updateUiCellClick(self, row, column):
'''self.updateUiCellClick - None'''
print('self.updateUiCellClick - None')
self.currentpath += self.itemWidgets[0][row].text+'/'
self.updateUi()
print(' | Done')
if __name__ == '__main__':
print('i Execute instance')
app = QApplication(sys.argv)
root = root()
root.show()
app.exec_()
print(' | Done')
The connection should be as follows
self.filesTable.cellClicked.connect(self.updateUiCellClick)
^^^^^^^^^^^
signal
In addition to this it is not necessary to create the connection every time you fill in the table, it is enough when you create it.
If you look at code you see that many parts are repeating, which is not necessary, I take the boldness to make improvements to your code as the verification of path and reduce the code.
import sys
import os
from subprocess import Popen, PIPE
from PyQt5.QtWidgets import *
class Root(QMainWindow):
def __init__(self, parent=None):
print(' self.__init__ - Initializes QMainWindow')
QMainWindow.__init__(self, parent)
# Create Table Widget
self.filesTable = QTableWidget()
self.filesTable.cellClicked.connect(self.updateUiCellClick)
# Init Table Widget
self.filesTable.clear()
self.filesTable.setSizeAdjustPolicy(QTableWidget.AdjustToContents)
self.filesTable.setColumnCount(4)
self.filesTable.setHorizontalHeaderLabels(['Name', 'TimeStamp', 'Type', 'ls -l'])
# Init Widgets
self.setCentralWidget(self.filesTable)
self.populate_table("/")
def populate_table(self, path):
# Verify that it is a directory.
if not os.path.isdir(path):
return
os.chdir(path)
current_items = os.listdir()
currentItemsLsProcess = Popen(['ls', '-l'], stdout=PIPE, stderr=PIPE)
currentItemsLsProcessResult = currentItemsLsProcess.communicate()
if currentItemsLsProcessResult[1].decode('utf-8'):
QMessageBox.warning(self, 'Files - ls -l Error',
'ls -l responded with non-blank stderr.Error is shown here:'
'<br><code>{}</code><br><hr><br>Error LsStderr (e-lsstderr)<br>'
'<hr><br>If you want to support the team, go to the '
'GitHub Repository.'.format(
currentItemsLsProcessResult[1].decode('utf-8')))
return
self.filesTable.clear()
currentItemsLs = currentItemsLsProcessResult[0].decode('utf-8').split('\n')[1:-1]
self.filesTable.setRowCount(len(current_items))
for i, values in enumerate(zip(current_items, currentItemsLs)):
name, ls = values
self.filesTable.setItem(i, 0, QTableWidgetItem(name))
self.filesTable.setItem(i, 3, QTableWidgetItem(ls))
self.setWindowTitle('{}'.format(path))
def updateUiCellClick(self, row, _):
path = os.path.join(os.getcwd(), self.filesTable.item(row, 0).text())
self.populate_table(path)
if __name__ == '__main__':
print('i Execute instance')
app = QApplication(sys.argv)
root = Root()
root.show()
status = app.exec_()
print(' | Done')
sys.exit(status)

Problems with running GTK3+ Python GUI for a web crawler

My program is a Python based Web crawler that pulls data through the terminal commands on Linux distributions(Ubuntu 14 used).
Now ever since I implemented a GT3+ GUI for it I got the following error:
/usr/bin/python3.4 /home/dipeshwar/Documents/WebsiteScanner/main.py Traceback (most recent call last): File
"/home/dipeshwar/Documents/WebsiteScanner/main.py", line 81, in
button_clicked
gather_info(self.name, self.url) File "/home/dipeshwar/Documents/WebsiteScanner/main.py", line 44, in
gather_info
domain = get_domain_name(url) File "/home/dipeshwar/Documents/WebsiteScanner/domain.py", line 16, in
get_domain_name
domain = get_tld(url) File "/usr/local/lib/python3.4/dist-packages/tld/utils.py", line 161, in
get_tld
url = url.lower() AttributeError: 'Entry' object has no attribute 'lower'
Here is my code. Could you tell me why its giving me that error?
In the get_doman_name function Im just using 'tld' module in Python to get the Top Level Domain.
Regards
from gi.repository import Gtk
# import statement for GUI
from general import *
from whois import *
from domain import *
from ipadd import *
from nmap import *
from robots_txt import *
# these import statements get all the variables from other modules of this program
ROOT_DIR = 'Output'
create_dir(ROOT_DIR)
# Checks if directory is created or not, if not, the program creates it
def create_report(name, url, domain, ipadd, nmap, robots_txt, whois): # or def create_report(name, full_url, domain, ipadd, nmap, robots_txt, whois): ?
project_dir = ROOT_DIR + '/' + name
create_dir(project_dir)
write_file(project_dir + '/full_url.txt', url) # or full_url ?
write_file(project_dir + '/domain.txt', domain)
write_file(project_dir + '/ipadd.txt', ipadd)
write_file(project_dir + '/nmap.txt', nmap)
write_file(project_dir + '/robots_txt.txt', robots_txt)
write_file(project_dir + '/whois.txt', whois)
def gather_info(name, url):
domain = get_domain_name(url)
ipadd = get_ip_address(url)
nmap = get_nmap(ipadd)
robots_txt = get_robots_txt(url)
whois = get_whois(url)
create_report(name, url, domain, ipadd, nmap, robots_txt, whois)
class MainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Reconnaissance Web-Scanner")
self.set_border_width(30)
self.set_size_request(300, 200)
# Layout of window
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
self.add(vbox)
# Inputs
self.name = Gtk.Entry()
self.name.set_text("Enter name of website here:")
vbox.pack_start(self.name, True, True, 0)
self.url = Gtk.Entry()
self.url.set_text("Enter URL of website here:")
vbox.pack_start(self.url, True, True, 0)
# Button
self.button = Gtk.Button(label="Scan Website")
self.button.connect("clicked", self.button_clicked)
vbox.pack_start(self.button, True, True, 0)
# when user clicks the button
def button_clicked(self, widget):
gather_info(self.name, self.url)
# put exec code here
self.name and self.url are both Gtk.Entrys and not strings. So you have to change your code to:
def button_clicked(self, widget):
gather_info(self.name.get_text(), self.url.get_text())

How can I register the code with different clsid?

I have modified the pywin package demo that creates a button to excel ribbon bar. Once the button is pressed the selected text is run in command line and the selected text is replaced by output.
There is a problem with registering the program with different clsid-filename combination. I cannot change the clsid and/or the filename of the code. If I create a copy of this file, modify and register it, The COM-object that is run is the original excelAddin.py not my new modified file. I am now missing a step in the COM registration process. What line should I add and where?
from win32com import universal
from win32com.server.exception import COMException
from win32com.client import gencache, DispatchWithEvents
import winerror
import pythoncom
from win32com.client import constants, Dispatch
import sys
import win32traceutil
# Support for COM objects we use.
gencache.EnsureModule('{00020813-0000-0000-C000-000000000046}', 0, 1, 3, bForDemand=True) # Excel 9
gencache.EnsureModule('{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}', 0, 2, 1, bForDemand=True) # Office 9
# The TLB defiining the interfaces we implement
universal.RegisterInterfaces('{AC0714F2-3D04-11D1-AE7D-00A0C90F26F4}', 0, 1, 0, ["_IDTExtensibility2"])
class ButtonEvent:
def OnClick(self, button, cancel):
import win32ui # Possible, but not necessary, to use a Pythonwin GUI
import win32con
import subprocess
app = Dispatch("Word.Application")
cmd = app.Selection
#subprocess.Popen(str(cmd))
cmdP = subprocess.Popen(str(cmd), shell = True, stdout = subprocess.PIPE)
txt = cmdP.stdout.readlines()
output = ''
for line in txt:
output += line
app.Selection.TypeText(output)
#win32ui.MessageBox(txt, "Python Test",win32con.MB_OKCANCEL)
return cancel
class ExcelAddin:
_com_interfaces_ = ['_IDTExtensibility2']
_public_methods_ = []
_reg_clsctx_ = pythoncom.CLSCTX_INPROC_SERVER
#_reg_clsid_ = ""
_reg_clsid_ = "{C5482ECA-F559-45A0-B078-B2036E6F011A}"
#_reg_clsid_ = "{E44EF798-7FDF-4015-AED6-00234CBBBA77}"
#_reg_clsid_ = "{91A89B71-56C0-4a76-BF1F-CF52E3671740}"
_reg_progid_ = 'Juha.test'#"Python.Test.ExcelAddin2"
_reg_policy_spec_ = "win32com.server.policy.EventHandlerPolicy"
def __init__(self):
self.appHostApp = None
def OnConnection(self, application, connectMode, addin, custom):
print "OnConnection", application, connectMode, addin, custom
try:
self.appHostApp = application
cbcMyBar = self.appHostApp.CommandBars.Add(Name="PythonBar",
Position=constants.msoBarTop,
MenuBar=constants.msoBarTypeNormal,
Temporary=True)
btnMyButton = cbcMyBar.Controls.Add(Type=constants.msoControlButton, Parameter="Greetings")
btnMyButton=self.toolbarButton = DispatchWithEvents(btnMyButton, ButtonEvent)
btnMyButton.Style = constants.msoButtonCaption
btnMyButton.BeginGroup = True
btnMyButton.Caption = "&Run..."
btnMyButton.TooltipText = "Runs the selected text in command line"
btnMyButton.Width = "34"
cbcMyBar.Visible = True
except pythoncom.com_error, (hr, msg, exc, arg):
print "The Excel call failed with code %d: %s" % (hr, msg)
if exc is None:
print "There is no extended error information"
else:
wcode, source, text, helpFile, helpId, scode = exc
print "The source of the error is", source
print "The error message is", text
print "More info can be found in %s (id=%d)" % (helpFile, helpId)
def OnDisconnection(self, mode, custom):
print "OnDisconnection"
self.appHostApp.CommandBars("PythonBar").Delete
self.appHostApp=None
def OnAddInsUpdate(self, custom):
print "OnAddInsUpdate", custom
def OnStartupComplete(self, custom):
print "OnStartupComplete", custom
def OnBeginShutdown(self, custom):
print "OnBeginShutdown", custom
def RegisterAddin(klass):
import _winreg
key = _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Office\\Word\\Addins")
#key = _winreg.CreateKey(_winreg.HKEY_LOCAL_MACHINE, "Software\\Classes\\CLSID")
subkey = _winreg.CreateKey(key, klass._reg_progid_)
_winreg.SetValueEx(subkey, "CommandLineSafe", 0, _winreg.REG_DWORD, 0)
_winreg.SetValueEx(subkey, "LoadBehavior", 0, _winreg.REG_DWORD, 3)
_winreg.SetValueEx(subkey, "Description", 0, _winreg.REG_SZ, "Runs selection in command line and pastes output")
_winreg.SetValueEx(subkey, "FriendlyName", 0, _winreg.REG_SZ, "cmdlineRunner")
def UnregisterAddin(klass):
import _winreg
try:
_winreg.DeleteKey(_winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Office\\Word\\Addins\\" + klass._reg_progid_)
except WindowsError:
pass
if __name__ == '__main__':
import win32com.server.register
win32com.server.register.UseCommandLine(ExcelAddin)
if "--unregister" in sys.argv:
UnregisterAddin(ExcelAddin)
else:
RegisterAddin(ExcelAddin)

Python & Tkinter -> About calling a long running function that freeze the program

Am a new in GUI programming and I am trying to make a GUI for one of my python parser.
I know that :
Tkinter is single threaded. Screen updates happen on each trip through the event loop. Any time you have a long running command you are preventing the event loop from completing an iteration, thus preventing the processing of events, thus preventing redraws.
My program call a big function that takes about 5 minutes to be ran entirely. So I guess the only solution is tu use thread for the long running command.
BUT, my long running command in already threaded so I don't really know how to proceed.
--> As soon as I click on BUT1 in the GUI, the program freeze until the function is entirely done. I'd like to run this function in the backgroung, so the program will not freeze.
--> I'm not looking for a complete solution but if someone can put me on a good track, it will be wonderful !
Main.py -> The GUI
Module_1.py -> The function that we call by clicking on the button BUT1
Thank you in advance !
Here is Main.py --> the GUI
#!/usr/bin/python
# -*- coding: utf-8 -*-
from Tkinter import *
import sys
import tkMessageBox
import tkFileDialog
import Module_1
import csv
from time import strftime, gmtime
DATE = strftime("_%d_%b_%Y")
class App:
def __init__(self, master):
self.frame = Frame(master, borderwidth=5, relief=RIDGE)
self.frame.grid()
class IORedirector(object):
def __init__(self,TEXT_INFO):
self.TEXT_INFO = TEXT_INFO
class StdoutRedirector(IORedirector):
def write(self,str):
self.TEXT_INFO.config(text=self.TEXT_INFO.cget('text') + str)
self.TEXT_HEADER = self.text_intro = Label(self.frame, bg="lightblue",text="THIS IS \n MY SUPER PROGRAM")
self.TEXT_HEADER.grid(row=0, column=0, columnspan=2, sticky=W+E+N+S)
self.MENU = Frame(self.frame, borderwidth=5, relief=RIDGE, height=12)
self.MENU.grid(row=1, column=0, sticky=N)
self.button = Button(self.MENU, text="QUIT", bg="red", command=self.frame.quit)
self.button.grid(row=4, column=0)
self.BUT1 = Button(self.MENU, text="BUT1", command=self.BUT1)
self.BUT1.grid(row=0, column=0,sticky=W+E)
self.TEXT_INFO = Label(self.frame, height=12, width=40, text="SOME TEXT", bg="grey",borderwidth=5, relief=RIDGE)
self.TEXT_INFO.grid(row=1, column=1, sticky = N+W)
sys.stdout = StdoutRedirector(self.TEXT_INFO)
def BUT1(self):
self.BUT1.config(text="RUNNING")
self.TEXT_INFO.config(text="BUT1 LAUNCHED")
Module_1.main("BUT1")
## HERE WE NEED TO RUN THE FUNCTION
## THE PROGRAMM FREEZE HERE UNTIL THE FUNCTION IS ENTIRELY RUN
self.TEXT_INFO.config(text="BUT1 FINISHED")
self.BUT1.config(text="DONE")
root = Tk()
app = App(root)
root.mainloop()
And here is Module_1.py --> contain the big function
#!/usr/bin/python
# -*- coding: utf-8 -*-
import Queue
import threading
import urllib2
import time
from bs4 import BeautifulSoup as soup
from urllib2 import urlopen
import re
import os
import random
import sys
import logging
import csv
from time import strftime, gmtime
import os
import random
import shutil
import sys
import re
import logging
from threading import RLock
from time import strftime, gmtime
import csv
import urllib
from urllib import urlretrieve
from grab.spider import Spider, Task
logging.basicConfig(level=logging.CRITICAL) # Loggin to DEBUG / INFO
log = logging.getLogger()
DATE = strftime("_%d_%b_%Y")
class SPIDER1(Spider):
initial_urls = ['URL_THAT_I_NEED_TO_PARSE']
def __init__(self):
super(SPIDER1, self).__init__(
thread_number=20,
network_try_limit=20,
task_try_limit=20
)
self.result = {}
def task_initial(self, grab, task):
for opt in grab.css_list("select[name='Template$TestCentreSearch1$SubRegionList'] option")[1:]:
grab.set_input('Template$TestCentreSearch1$SubRegionList', opt.attrib['value'])
grab.submit(extra_post={
'__EVENTTARGET': 'Template$TestCentreSearch1$SubRegionList'
}, make_request=False)
yield Task('parse', grab=grab, country=opt.text_content())
def task_parse(self, grab, task):
log.info('downloaded %s' % task.country)
city_gen = (x.text_content() for x in grab.css_list(".TestCentreSearchLabel+br+span"))
title_gen = (x.text_content() for x in grab.css_list(".TestCentreSearchTitle"))
id_gen = (x.attrib['href'][-36:] for x in grab.css_list(".TestCentreSearchLink"))
for x in zip(city_gen, title_gen, id_gen):
self.result[x[2]] = {
'country': task.country,
'city': x[0],
'name': x[1],
'id': x[2],
'price':'',
'currency':'',
'fee':''
}
yield Task('info', 'URL_URL=%s' % x[2], id=x[2])
def task_info(self, grab, task):
for label in grab.css_list(".TestCentreViewLabel"):
if label.text_content().strip()=="Test Fee:":
fees = label.getnext().text_content().strip()
self.result[task.id]['fee'] = fees
price = re.findall('\d[\d\., ]+\d',fees)
if price:
price = re.findall('\d[\d\., ]+\d',fees)[0]
self.result[task.id]['price'] = price.replace(' ','').replace(',','.')
currency = re.findall('[A-Z]{2,3}[$|€|£]?',fees)
if not currency:
currency = re.findall('[$|€|£]',fees)
if not currency:
currency = fees.replace(price,'').strip().replace(' ','')
if isinstance(currency,list):
currency = currency[0]
self.result[task.id]['currency'] = currency
#log.info(' %(price)s %(currency)s - %(fee)s ' % self.result[task.id])
break
def dump(self, path):
"""
Save result as csv into the path
"""
with open(path, 'w') as file:
file.write("ID;Country;State;City;Name;Price;Currency;Original Fee\n")
for test_center in sorted(self.result.values(), key=lambda x: "%(country)s%(city)s%(name)s" % x):
file.write(("%(id)s;%(country)s;;%(country)s;%(name)s;%(price)s;%(currency)s;%(fee)s\n" % test_center).encode('utf8'))
def main(choice):
parser, path, name = None, None, None
def run(name,parser,path):
log.info('Parsing %s...' % name)
parser.run()
parser.dump(path)
log.info('Parsing %s completed, data was dumped into %s' % (name, path))
log.info(parser.render_stats())
if choice == "NONE":
# DO NOTHING
# HERE I'D LIKE TO HAVE ANOTHER CALL TO ANOTHER THREADED FUNCTION
elif choice == "BUT1":
run('Function1',SPIDER1(),'C:\LOL\Output1'+DATE+'.csv')
So by clicking on BUT1, we run the main("BUT1") function contained in the Module_1.py file with argument BUT1 that launch -> run('Function1',SPIDER1(),'C:\LOL\Output1'+DATE+'.csv')
And then the program freeze until the parser has finished is work .. :)
The problem is simple: BUT1 won't return until the call to main returns. As long as main (and thus, BUT1) doesn't return, your GUI will be frozen.
For this to work you must put main in a separate thread. It's not sufficient that main spawns other threads if all it's doing is waiting for those threads.
If you call root.update() occasionally from the BUT1 function, that should prevent the GUI from freezing. You could also do that from a python thread with a fixed interval.
For example, updating every 0.1 seconds:
from threading import Thread
from time import sleep
self.updateGUIThread = Thread(target=self.updateGUI)
def updateGUI(self):
while self.updateNeeded
root.update()
sleep(0.1)
After the big function completes you can set self.updateNeeded to False.

Categories

Resources