How to handle console exit and object destruction - python

Given this code:
from time import sleep
class TemporaryFileCreator(object):
def __init__(self):
print 'create temporary file'
# create_temp_file('temp.txt')
def watch(self):
try:
print 'watching tempoary file'
while True:
# add_a_line_in_temp_file('temp.txt', 'new line')
sleep(4)
except (KeyboardInterrupt, SystemExit), e:
print 'deleting the temporary file..'
# delete_temporary_file('temp.txt')
sleep(3)
print str(e)
t = TemporaryFileCreator()
t.watch()
during the t.watch(), I want to close this application in the console..
I tried using CTRL+C and it works:
However, if I click the exit button:
it doesn't work.. I checked many related questions about this but it seems that I cannot find the right answer..
What I want to do:
The console can be exited while the program is still running.. to handle that, when the exit button is pressed, I want to make a cleanup of the objects (deleting of created temporary files), rollback of temporary changes, etc..
Question:
how can I handle console exit?
how can I integrate it on object destructors (__exit__())
Is it even possible? (how about py2exe?)
Note: code will be compiled on py2exe.. "hopes that the effect is the same"

You may want to have a look at signals. When a *nix terminal is closed with a running process, this process receives a couple signals. For instance this code waits for the SIGHUB hangup signal and writes a final message. This codes works under OSX and Linux. I know you are specifically asking for Windows but you might want to give it a shot or investigate what signals a Windows command prompt is emitting during shutdown.
import signal
import sys
def signal_handler(signal, frame):
with open('./log.log', 'w') as f:
f.write('event received!')
signal.signal(signal.SIGHUP, signal_handler)
print('Waiting for the final blow...')
#signal.pause() # does not work under windows
sleep(10) # so let us just wait here
Quote from the documentation:
On Windows, signal() can only be called with SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, or SIGTERM. A ValueError will be raised in any other case.
Update:
Actually, the closest thing in Windows is win32api.setConsoleCtrlHandler (doc). This was already discussed here:
When using win32api.setConsoleCtrlHandler(), I'm able to receive shutdown/logoff/etc events from Windows, and cleanly shut down my app.
And if Daniel's code still works, this might be a nice way to use both (signals and CtrlHandler) for cross-platform purposes:
import os, sys
def set_exit_handler(func):
if os.name == "nt":
try:
import win32api
win32api.SetConsoleCtrlHandler(func, True)
except ImportError:
version = β€œ.”.join(map(str, sys.version_info[:2]))
raise Exception(”pywin32 not installed for Python ” + version)
else:
import signal
signal.signal(signal.SIGTERM, func)
if __name__ == "__main__":
def on_exit(sig, func=None):
print "exit handler triggered"
import time
time.sleep(5)
set_exit_handler(on_exit)
print "Press to quit"
raw_input()
print "quit!"

If you use tempfile to create your temporary file, it will be automatically deleted when the Python process is killed.
Try it with:
>>> foo = tempfile.NamedTemporaryFile()
>>> foo.name
'c:\\users\\blah\\appdata\\local\\temp\\tmpxxxxxx'
Now check that the named file is there. You can write to and read from this file like any other.
Now kill the Python window and check that file is gone (it should be)
You can simply call foo.close() to delete it manually in your code.

Related

SIGTERM signal not received by python on shutdown command

I have been programming using python for the raspberryPi for several months now and I am trying to make my scripts "well behaved" and wrap up (close files and make sure no writes to SD are being perfomed) upon reception of SIGTERM.
Following advice on SO (1, 2) I am able to handle SIGTERM if I kill the process manually (i.e. kill {process number}) but if I send the shutdown command (i.e. shutdown -t 30 now) my handler never gets called.
I also tried registering for all signals and checking the signal being send on the shutdown event but I am not getting any.
Here's simple example code:
import time
import signal
import sys
def myHandler(signum, frame):
print "Signal #, ", signum
sys.exit()
for i in [x for x in dir(signal) if x.startswith("SIG")]:
try:
signum = getattr(signal, i)
signal.signal(signum, myHandler)
print "Handler added for {}".format(i)
except RuntimeError,m:
print "Skipping %s"%i
except ValueError:
break
while True:
print "goo"
time.sleep(1)
Any ideas will be greatly appreciated .. =)
this code works for me on the raspberry pi, i can see the correct output in the file output.log after the restart:
logging.basicConfig(level=WARNING,
filename='output.log',
format='%(message)s')
def quit():
#cleaning code here
logging.warning('exit')
sys.exit(0)
def handler(signum=None, frame=None):
quit()
for sig in [signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT, signal.SIGKILL]:
signal.signal(sig, handler)
def restart():
command = '/sbin/shutdown -r now'
process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
output = process.communicate()[0]
logging.warning('%s'%output)
restart()
maybe your terminal handles the signal before the python script does, so you can't actually see anything. Try to see the output in a file (with the logging module or the way as you like).

PySide app seems to ignore SIGTERM during shutdown when run at startup using ~/.config/autostart

My PySide app seems to ignore the TERM signal sent during shutdown on Linux when it is automatically started by putting an entry in ~/.config/autostart.
I came to the conclusion that the issue is with PySide/Qt through the following experiment.
First, I created a simple Python script that catches the TERM signal and prints a confirmation then exits. Here is the code.
import signal
import sys
import time
def on_quit(*args):
print 'here', args
sys.exit(0)
for i in [x for x in dir(signal) if x.startswith('SIG')]:
try:
signum = getattr(signal, i)
if i == 'SIGCHILD' or i == 'SIGCHLD':
continue
signal.signal(signum, on_quit)
except Exception, m:
print 'Skipping %s' % i
time.sleep(1000000)
The for loop just registers the on_quit method on all possible signals.
Calling this in a terminal and sending it the TERM signal by killing it, I can confirm that it is being caught and processed by the script.
I then tried to run this script at startup by including an entry in ~/.config/autostart. The entry is similar to the following:
[Desktop Entry]
Version=1.0
Type=Application
Name=Test
GenericName=Test
Comment=Test
Exec=strace -T -f -tt -o run_test.strace run_test
Terminal=false
StartupNotify=false
I used strace to trace the system calls of the script to confirm in another way that it is catching the TERM signal. The options to strace tells it to include the time spent on the system call, to follow forks, to print the time of day with microseconds, and to send the output to run_test.strace, in that order.
run_test is just a bash script that calls the Python script; it is included because that is how my original PySide app is being called. The code for it is:
#!/bin/bash
python test.py &>out.test
I proceeded to test it by adding the desktop entry to ~/.config/autostart, restarting the computer, then shutting it down. run_test.strace contained the line "--- SIGTERM (Terminated) # 0 (0) ---" which confirms that SIGTERM was sent and caught. out.test also contains the expected output which further confirms it.
I then tried to test a simple PySide application with signal handling. The code is as follows.
import signal
import sys
import time
from PySide.QtGui import QApplication
from PySide.QtCore import QTimer
app = QApplication([])
timer = QTimer()
timer.start(500)
timer.timeout.connect(lambda: None)
def on_quit(*args):
print 'here', args
sys.exit(0)
for i in [x for x in dir(signal) if x.startswith('SIG')]:
try:
signum = getattr(signal, i)
if i == 'SIGCHILD' or i == 'SIGCHLD':
continue
signal.signal(signum, on_quit)
except Exception, m:
print 'Skipping %s' % i
app.exec_()
The QTimer is essential to allow the signal handler to execute.
Running the above script in a terminal and sending it the TERM signal, the output still confirms that the TERM signal is being caught and processed. Running it through strace also shows the line "--- SIGTERM (Terminated) # 0 (0) ---".
When running it during startup, however, the output shows that the on_quit method is not called nor is there an indication in the strace output that it was sent the TERM signal.
Could anyone help? I can provide the strace outputs if needed. System is Ubuntu 12.04 32bit.
edit: Further experimentation shows that it happens with QApplication but not with QCoreApplication. It's something probably GUI related then?

Urwid: how to see errors?

I am building application with interactive console interface (line htop, atop utilities) using urwid library, so my trouble is: as interface takes all the space in console window - I could not see python's errors, I tried to do that:
import sys
f = open("test_err", "w")
original_stderr = sys.stderr
sys.stderr = f
print a #a is undefined
sys.stderr = original_stderr
f.close()
It works when I dont use urwid, but not when I use it...
you could try redirecting errors to a file. after each time you run the program, you will need to refresh the file. most editors let you easily do this by pushing f5
def main():
#your code here
print someError #raises an error
try: #run main function
main()
except BaseException as err: #catch all errors
with open('errors.txt','a') as errors: #open a file to write the errors to
errors.write(err.message+'\n')#write the error
change the 'a' to 'w' in the open function if you only want to see one error in the file at a time (instead of having multiple error over a long period of time in one file).
if you want to see the error right when it happens, you can make the error catcher open a window that has the error on it.
def main():
#your code here
print someErr
try: #run main function
main()
except BaseException as err: #catch all errors
import Tkinter as tk #imports the ui module
root = tk.Tk() #creates the root of the window
#creates the text and attaches it to the root
window = tk.Label(root, text=err.message)
window.pack()
#runs the window
root.mainloop()
if you want to build your own window to catch errors, you can learn about Tkinter here. (it is built into python, you don't have to install anything)
Here's what I came up with. I'm taking advantage of unicode-rxvt (urxvt) feature to be passed in a file descriptor. Of course this means you need to be developing this in an X environment, and not a console.
from __future__ import print_function
import os
from datetime import datetime
_debugfile = None
def _close_debug(fo):
fo.close()
def DEBUG(*obj):
"""Open a terminal emulator and write messages to it for debugging."""
global _debugfile
if _debugfile is None:
import atexit
masterfd, slavefd = os.openpty()
pid = os.fork()
if pid: # parent
os.close(masterfd)
_debugfile = os.fdopen(slavefd, "w+", 0)
atexit.register(_close_debug, _debugfile)
else: # child
os.close(slavefd)
os.execlp("urxvt", "urxvt", "-pty-fd", str(masterfd))
print(datetime.now(), ":", ", ".join(map(repr, obj)), file=_debugfile)
This will open a new terminal window automatically when you call DEBUG for the first time and close it at exit. Then any messages passed to it are shown in this new window. This is your "debug window". So your main app works normally, without cluttering it up with messages, but you can still see debug output in this new terminal.

Better handling of KeyboardInterrupt in cmd.Cmd command line interpreter

While using python's cmd.Cmd to create a custom CLI, how do I tell the handler to abort the current line and give me a new prompt?
Here is a minimal example:
# console_min.py
# run: 'python console_min.py'
import cmd, signal
class Console(cmd.Cmd):
def __init__(self):
cmd.Cmd.__init__(self)
self.prompt = "[test] "
signal.signal(signal.SIGINT, handler)
def do_exit(self, args):
return -1
def do_EOF(self, args):
return self.do_exit(args)
def preloop(self):
cmd.Cmd.preloop(self)
self._hist = []
self._locals = {}
self._globals = {}
def postloop(self):
cmd.Cmd.postloop(self)
print "Exiting ..."
def precmd(self, line):
self._hist += [ line.strip() ]
return line
def postcmd(self, stop, line):
return stop
def emptyline(self):
return cmd.Cmd.emptyline(self)
def handler(self, signum, frame):
# self.emptyline() does not work here
# return cmd.Cmd.emptyline(self) does not work here
print "caught ctrl+c, press return to continue"
if __name__ == '__main__':
console = Console()
console.cmdloop()
Further help is greatly appreciated.
Original Question and more details:
[Currently the suggestions below have been integrated into this question -- still searching for an answer. Updated to fix an error.]
I've since tried moving the handler to a function outside the loop to see if it were more flexible, but it does not appear to be.
I am using python's cmd.Cmd module to create my own command line interpreter to manage interaction with some software. I often press ctrl+c expecting popular shell-like behavior of returning a new prompt without acting on whatever command was typed. However, it just exits. I've tried to catch KeyboardInterrupt exceptions at various points in the code (preloop, etc.) to no avail. I've read a bit on sigints but don't quite know how that fits in here.
UPDATE: From suggestions below, I tried to implement signal and was able to do so such that ctrl+c now doesn't exit the CLI but does print the message. However, my new issue is that I cannot seem to tell the handler function (see below) to do much beside print. I would like ctrl+c to basically abort the current line and give me a new prompt.
I'm currently working on a creation of a Shell by using the Cmd module. I've been confronted with the same issue, and I found a solution.
Here is the code:
class Shell(Cmd, object)
...
def cmdloop(self, intro=None):
print(self.intro)
while True:
try:
super(Shell, self).cmdloop(intro="")
break
except KeyboardInterrupt:
print("^C")
...
Now you have a proper KeyboardInterrupt (aka CTRL-C) handler within the shell.
Instead of using signal handling you could just catch the KeyboardInterrupt that cmd.Cmd.cmdloop() raises. You can certainly use signal handling but it isn't required.
Run the call to cmdloop() in a while loop that restarts itself on an KeyboardInterrupt exception but terminates properly due to EOF.
import cmd
import sys
class Console(cmd.Cmd):
def do_EOF(self,line):
return True
def do_foo(self,line):
print "In foo"
def do_bar(self,line):
print "In bar"
def cmdloop_with_keyboard_interrupt(self):
doQuit = False
while doQuit != True:
try:
self.cmdloop()
doQuit = True
except KeyboardInterrupt:
sys.stdout.write('\n')
console = Console()
console.cmdloop_with_keyboard_interrupt()
print 'Done!'
Doing a CTRL-c just prints a new prompt on a new line.
(Cmd) help
Undocumented commands:
======================
EOF bar foo help
(Cmd) <----- ctrl-c pressed
(Cmd) <------ctrl-c pressed
(Cmd) ddasfjdfaslkdsafjkasdfjklsadfljk <---- ctrl-c pressed
(Cmd)
(Cmd) bar
In bar
(Cmd) ^DDone!
You can catch the CTRL-C signal with a signal handler. If you add the code below, the interpreter refuses to quit on CTRL-C:
import signal
def handler(signum, frame):
print 'Caught CTRL-C, press enter to continue'
signal.signal(signal.SIGINT, handler)
If you don't want to press ENTER after each CTRL-C, just let the handler do nothing, which will trap the signal without any effect:
def handler(signum, frame):
""" just do nothing """
In response to the following comment in this answer:
This appears to be converging on a solution, but I don't know how to integrate it into my own code (above). I have to figure out the 'super' line. I need to try and get this working at some point in the future.
super() would work in this answer if you have your class extend object in addition to cmd.Cmd. Like this:
class Console(cmd.Cmd, object):
I wanted to do the same, so I searched for it and created basic script for understanding purposes, my code can be found on my GitHub
So, In your code,
instead of this
if __name__ == '__main__':
console = Console()
console.cmdloop()
Use this,
if __name__ == '__main__':
console = Console()
try:
console.cmdloop()
except KeyboardInterrupt:
print ("print sth. ")
raise SystemExit
Hope this will help you out. well, it worked for me. πŸ˜€
You can check out the signal module: http://docs.python.org/library/signal.html
import signal
oldSignal = None
def handler(signum, frame):
global oldSignal
if signum == 2:
print "ctrl+c!!!!"
else:
oldSignal()
oldSignal = signal.signal(signal.SIGINT, handler)
Just add this inside the class Console(cmd.Cmd):
def cmdloop(self):
try:
cmd.Cmd.cmdloop(self)
except KeyboardInterrupt as e:
self.cmdloop()
Forget all the other stuff. This works. It doesn't make loops inside of loops. As it catches KeyboardInterrupt it calls do_EOF but only will execute the first line; since your first line in do_EOF is return do_exit this is fine.
do_exit calls postloop.
However, again, it only executes the first line after cmd.Cmd.postloop(self). In my program this is print "\n". Strangely, if you SPAM ctrl+C you will eventually see it print the 2nd line usually only printed on ACTUAL exit (ctrl+Z then enter, or typing in exit).
I prefer the signal method, but with just pass. Don't really care about prompting the user with anything in my shell environment.
import signal
def handler(signum, frame):
pass
signal.signal(signal.SIGINT, handler)

python on script unload event (destructor)

I am using file locking in python script (to control single instance of it execution).
http://code.google.com/p/pylockfile/
I release lock in finally code block.
But if script closed, for example, closing the terminal running it, the finally block doesn't execute and the file stays locked.
How to catch python script destructor event in any case?
See this blog post regarding this subject. It uses the win32api when under Windows, while under Linux the SIGTERM signal is caught. To verify its working, it might be helpful to write something to a file in the on_exit handler like as done below. As the snippet is quite brief, I'll just include it (full props to the blog author):
import os, sys
def set_exit_handler(func):
if os.name == "nt":
try:
import win32api
win32api.SetConsoleCtrlHandler(func, True)
except ImportError:
version = '.'.join(map(str, sys.version_info[:2]))
raise Exception('pywin32 not installed for Python ' + version)
else:
import signal
signal.signal(signal.SIGTERM, func)
if __name__ == '__main__':
def on_exit(sig, func=None):
f = open('log.txt', 'w')
f.write('shutdown...')
f.close()
sys.exit()
set_exit_handler(on_exit)
print 'Press to quit'
raw_input()
print 'quit!'
If you will close the terminal running that program, it will create a file to verify the callback functionality.

Categories