Contents
Main loop
The main loop facility within kaa is based on pyNotifier. A system-wide installation of pynotifier will be used if it exists, otherwise kaa will fallback to an internal version which supports integration with gtk and twisted main loops. kaa.base fully wraps and enhances stock pyNotifier API with process, thread and signal handling and a wide variety of callback classes.
If you already use some kind of main loop API, please read SourceDoc/MainLoop how to integrate kaa into your main loop.
To start the main loop, you need to import kaa and call kaa.main.run(). When the program stops because you press Ctrl-c in the terminal, send a SIG15 or call sys.exit, the application won't stop. It will just terminate the main loop (i.e. kaa.main.run() returns).
import kaa kaa.main.run()
This is a trivial example of course; your program should do something. The other classes available in kaa.base (described below) can be used to hook functionality into the main loop. As a general rule, one should try not to block the main loop for longer than 100 milliseconds. kaa.base provides several options to avoid blocking, such as coroutines or threads. See SourceDoc/Async for further discussion of asynchronous programming with Kaa.
The notion of signals is used heavily within all kaa modules. Here, signals are similar to gtk signals, in that they are hooks to allow you to connect callbacks when certain events occur. There is a dictionary signals called kaa.main.signals which contains several signals to which you can connect. In some cases, this dictionary is extended when modules are imported.
Step Signal
The step signal is emitted after every iteration of the main loop. The time between step emissions is non-deterministic. For example, if there's activity on a monitored socket and the socket callback executes quickly, the time between step signals could be very quick. If there are no notifier events (timer, socket activity, or threads waking up the mainloop), the time between step signals is 30 seconds. Therefore, the step signal is not an "idle" equivalent.
import kaa def handle_step(param): print param kaa.signals['step'].connect(handle_step, 1) kaa.main.run()
This small code will print '1' after each iteration of the main loop. (In this example, once every 30 seconds.)
Shutdown Signal
The callback will be called when the main loop terminates, which can happen because of a unix signal, SystemExit or KeyboardInterrupt exceptions, or kaa.main.stop() is called.
import kaa
def cleanup():
print 'Do some cleanup'
kaa.signals['shutdown'].connect(foo)
kaa.main.run()After the shutdown signal is emitted, all processes spawned using the Process class will be forcefully killed. If this is a problem, you should attach a callback to the shutdown signal to end the process gracefully.
Exception Signal
By default, any exception that bubbles up to the mainloop will cause the loop to terminate. These exceptions can be intercepted by a catch-all handler by connecting a callback to the exception signal. This is different from wrapping kaa.main.run() in a try/except clause because exception signal callbacks can prevent the termination of the mainloop by explicitly returning False. Callbacks connected to the exception signal must take three arguments: the exception type, the exception instance, and a traceback object. (Note that KeyboardInterrupt and SystemExit cannot be handled via the exception signal.)
import kaa def global_error_handler(exc_type, exc_value, tb): print "Uncaught exception:", exc_value return False kaa.signals['exception'].connect(global_error_handler) kaa.main.run()
Key Press Signal
If you want to handle key presses on stdin, you can connect to the 'stdin_key_press_event' signal, which is added to the kaa.signals dictionary when you import kaa.input.stdin. Any callback connected to this signal will get called for every individual key pressed in standard input. The argument passed to the callback is a string containing the name of the key, which can be an individual letter, e.g. 'q', or a word representing the key, like 'up' or 'enter'.
import kaa.input.stdin
def handle_key(key):
if key == "up":
print "You pressed the up arrow"
kaa.signals['stdin_key_press_event'].connect(handle_key)
kaa.main.run()
Callbacks
Often times you need to provide a callback function but want to pass certain arguments to the function when it is called. For this purpose, kaa.base provides a family of Callback objects. Callback objects can also be used to create partial functions. When constructed, it takes a callable and a list of arguments and keyword arguments. The resulting object is itself callable; if the object itself is called with parameters, the parameters will be appended to the list of parameters specified in the constructor. (This behavior can be reversed; see below.)
import kaa def foo(a, b = 1, c = 2): pass c = kaa.Callback(foo, 1, c=3) c()
When c is called, a is 1, b is the default 1 and c is 3.
Callback objects have two methods in order to adjust how arguments are handled. The first method is set_ignore_caller_args() which takes a boolean value. If True, any arguments passed when the callback is invoked will be ignored; only the arguments given when the Callback was constructed will get passed to the target function. The default is False: arguments passed when called get merged with the arguments passed to the constructor. The second method is set_user_args_first() which also takes a boolean. If True, arguments passed to the constructor will be listed before arguments passed when the callback is called. The default is False: arguments passed to the constructor appear after.
There is also a variant class WeakCallback that will use a weak reference to the function and won't call the internal callback when the object behind the weak reference is gone. WeakCallbacks do not retain explicit references either on the callable (specifically the instance, in the case of a method) or any arguments.
Timer
Timer objects inherit from Callback, so you add the function and the parameter when you create the object and can call the start(seconds) method to start the timer, and stop() to stop an currently-running Timer. When the callback function returns any value other than False, the timer remains active and the callback will be invoked again after the interval. (Note only an explicit return value of False will cause the timer to stop. Therefore if the callback has no explicit return, the timer continues to fire. This is probably what you want.)
If you start a timer that is already running, the object will stop itself and start itself again with the new interval. If you don't want that, you can call the method set_restart_when_active(False). The Timer object has a method active() to check if the timer is currently running or not.
You can safely start and stop timer from a thread. The callback itself will always be called from the main thread from within the main loop.
import kaa
def fire(param):
print param
kaa.Timer(fire, 42).start(1)This will print '42' every second. Sometimes you only want to run the callback once. You could always return False in the handler, but there is a corner case such that when you have a very short timer and you call kaa.main.step() inside the callback (or do so indirectly, for example with the InProgress.wait() method), the timer may fire again (recursively). To prevent that, there is a class called OneShotTimer. And similar to WeakCallback, there are WeakTimer and WeakOneShotTimer classes. If the reference to the callable or any of its arguments are gone, the weak timer will be automatically unregistered from the main loop.
IOMonitor
The IOMonitor is a callback that will be called when there is an action on a socket (or any file descriptor). It has a member function register() that takes the file descriptor (which can be an integer for the descriptor, or any file or socket object) and, optionally, the type of activity you want to handle (IO_READ, IO-WRITE or IO_EXCEPT). If you do not pass the second parameter, IO_READ is used. The unregister() method will stop the file descriptor from being monitored. And there is also a WeakIOMonitor class (seeing a pattern yet?) Like Timer objects, IOMonitor objects have a member function active() to check if the socket or file descriptor is currently being monitored.
A socket can be safely registered and unregistered from a thread, the callback itself will always be called from the main thread from within the main loop.
Signals
The next type of callbacks are Signals. These are not Unix signals, but are instead quite similar to signals that exist in widget sets like gtk. When they are called, each callback connected to the signal will be invoked with parameters which are dependent on the signal being emitted. Signal objects have the following member functions:
- connect: connect a callback to the signal that gets called when the signal is emitted
- connect_weak: same as connect(), but hold only weak references to the function and parameters
- connect_once: automatically disconnect the callback after it has been invoked once
- connect_weak_once: weak variant of connect_once
- connect_first: add at the beginning of the internal callback queue
- connect_weak_first: weak variant of connect_first
- disconnect: disconnects a given callback from the signal
- disconnect_all: removes all callbacks from the signal
- emit: invoke all callbacks connected to the signal
- emit_deferred: queues emission of the signal until the next callback is connected
- emit_when_handled: queues emission only if there are no handlers yet, otherwise emit immediately
For all Kaa classes that offer signals, the convention is for the instance to have a 'signals' attribute that is a dictionary of signals. For example, if you want to handle key presses from an X11Window in kaa.display, you will connect to mywindow.signals['key_press_event']
So first you create a signal and connect functions with optional parameters to it. After that, you can emit the signal and the callbacks will be called in the order they were connected (with the exception that callbacks connected with connect_first() get called first and in reverse order). If you add parameters to emit, they will also be passed to the connected callbacks.
import kaa
def signal_handler(fixed_param, param_from_emit)
print fixed_param, param_from_emit
s = kaa.Signal()
s.connect(signal_handler, 1)
s.emit(2)One special kind of Signal is the InProgress object often used together with the yield_execution decorator. See SourceDoc/Async for a detailed description.
Events
Events are similar to signals, except you don't need to connect to the event itself but instead to a global event handler. You connect to an event by name (events are compared based on a string). The callback itself is called from the main loop and there will be at most one event active at any given moment. So, when you post two events, nothing will happen until you return to the main loop. After that, the first event will be sent to all callbacks and, after all the connected callbacks return for that event, the second event will be sent. You register to a list of events or to all events by not specifying an event name. Unlike Signals, event handlers receive as a parameter the event object itself. And any parameters passed to the post() method will not be added to the parameter list of the handler, rather they are accessed via the 'arg' attribute of the event object.
You can safely post an event from a thread; the callbacks will get executed from the main thread from within the main loop, as described above.
import kaa
def foo(event):
if event == 'FOO_EVENT':
print 'got FOO'
elif event == 'BAR_EVENT':
print 'got BAR with %s' % event.arg
e = kaa.EventHandler(foo)
e.register(('FOO_EVENT', 'BAR_EVENT'))
Event('FOO_EVENT').post()
Event('BAR_EVENT').post(1)
Event('BAR_EVENT', 1).post()
Process
The Process class is available to invoke and handle interaction (stdin, stdout, stderr) with external processes. It takes a command line as a parameter to its constructor. The command line can either be a string, or a list of strings where the first item in the list is the path to the application and each subsequent string in the list is a parameter. A second optional argument is debugname: if set, the object will write the stdout of the process to a file called debugname-stdout.log and the stderr to the file debugname-sdterr.log.
The class provides three signals: 'stdout', which is emitted for every line the process writes to stdout and receives that line of text as an argument; 'stderr', which is emitted for every line the process writes to stderr and receives that line of text as an argument; and 'completed', which is emitted when the process exits, and receives as an argument the process's exit code.
The process can be started using the start() method. start() optionally takes a string or list of strings as with the constructor which can be used as additional parameters on the command line to begin the process. The class has also a member function write(string) to write something to the stdin of the process, a function is_alive() to check if the process is still running, and a function stop(cmd='') to stop the process. If cmd is given, the string will be sent to the stdin of the process to stop it. If the process does not exit after 3 seconds, it will be killed with SIGSTOP. (If no quit command is given, the process will be given a SIGSTOP immediately.) If this is also not working, the process will finally be sent a SIGKILL.
To get the stdout and stderr you need to connect to the 'stdout' and 'stderr' signals of the process object. These functions will receive the output line by line not including the end-of-line delimiters themselves. End-of-line delimiters are either '\r' or '\n'.
Starting and stopping a process is thread safe. The stdout, stderr and completed signals will be emitted from the main thread within the main loop. You should be cautious when using the write() method from within the a thread: if two threads write to the process at roughly the same time, the order these lines will be sent to the process is non-deterministic.
import kaa
def ls_stdout_cb(line):
print 'got line %s' % line
def ls_finished(exit_code):
print 'ls exited with code', exit_code
app = kaa.Process('ls -l')
app.signals['stdout'].connect(ls_stdout_cb)
app.signals['completed'].connect(ls_finished)
app.start('/home')You also get an InProgress object on the call of the start method. This is similar to a Signal and you can connect to it as usual. (See SourceDoc/Async for more information on InProgress.)
def done(exit_code):
pass
app.start('/home').connect(done)