]> git.llucax.com Git - software/pymin.git/blob - pymin/eventloop.py
Add a small utility to send pymin commands.
[software/pymin.git] / pymin / eventloop.py
1 # vim: set encoding=utf-8 et sw=4 sts=4 :
2
3 r"""
4 A simple event loop.
5
6 Please see EventLoop class documentation for more info.
7 """
8
9 import select
10 import errno
11 import signal
12 from select import POLLIN, POLLPRI, POLLERR
13
14 __ALL__ = ('EventLoop', 'LoopInterruptedError')
15
16 class LoopInterruptedError(RuntimeError):
17     r"""
18     LoopInterruptedError(select_error) -> LoopInterruptedError instance.
19
20     This class is raised when the event loop is interrupted in an unexpected
21     way. It wraps a select error, which can be accessed using the 'select_error'
22     attribute.
23     """
24
25     def __init__(self, select_error):
26         r"""Initialize the object.
27
28         See the class documentation for more info.
29         """
30         self.select_error = select_error
31
32     def __repr__(self):
33         r"repr(obj) -> Object representation."
34         return 'LoopInterruptedError(select_error=%r)' % self.select_error
35
36     def __str__(self):
37         r"str(obj) -> String representation."
38         return 'Loop interrupted: %s' % self.select_error
39
40 # Flag to know if a timer was expired
41 timeout = False
42
43 # Alarm Signal handler
44 def alarm_handler(signum, stack_frame):
45     global timeout
46     timeout = True
47
48 class EventLoop:
49     r"""EventLoop(file[, timer[, handler[, timer_handler]]]) -> EventLoop.
50
51     This class implements a simple event loop based on select module.
52     It "listens" to activity a single 'file' object (a file, a pipe,
53     a socket, or even a simple file descriptor) and calls a 'handler'
54     function (or the handle() method if you prefer subclassing) every
55     time the file is ready for reading (or has an error).
56
57     If a 'timer' is supplied, then the timer_handler() function object
58     (or the handle_timer() method) is called every 'timer' seconds.
59
60     This is a really simple example of usage using a hanlder callable:
61
62     >>> import os
63     >>> def handle(event_loop):
64             data = os.read(event_loop.fileno, 100)
65             os.write(1, 'Received message: %r\n' % data)
66     >>> p = EventLoop(0, handle)
67     >>> p.loop(once=True)
68
69     In this example only one event is handled (see the 'once' argument
70     of loop).
71
72     A more complex example, making a subclass and explicitly stopping
73     the loop, looks something like this:
74
75     >>> class Test(EventLoop):
76     >>>     def handle(self):
77     >>>         data = os.read(self.fileno, 100)
78     >>>         if data == 'q\n':
79     >>>             self.stop()
80     >>>         else:
81     >>>             os.write(1, 'Received message: %r\n' % data)
82     >>>     def handle_timer(self):
83     >>>         print time.strftime('%c')
84     >>> p = Test(0, timer=5)
85     >>> p.loop()
86
87     This example loops until the user enters a single "q", when stop()
88     is called and the event loop is exited.
89     """
90
91     def __init__(self, file, handler=None, timer=None, timer_handler=None):
92         r"""Initialize the EventLoop object.
93
94         See EventLoop class documentation for more info.
95         """
96         self.poll = select.poll()
97         self._stop = False
98         self.__register(file)
99         self.timer = timer
100         self.handler = handler
101         self.timer_handler = timer_handler
102
103     def __register(self, file):
104         r"__register(file) -> None :: Register a new file for polling."
105         self._file = file
106         self.poll.register(self.fileno, POLLIN | POLLPRI | POLLERR)
107
108     def set_file(self, file):
109         r"""set_file(file) -> None :: New file object to be monitored
110
111         Unregister the previous file object being monitored and register
112         a new one.
113         """
114         self.poll.unregister(self.fileno)
115         self.__register(file)
116
117     def get_file(self):
118         r"get_file() -> file object/int :: Get the current file object/fd."
119         return self._file
120
121     file = property(get_file, set_file, doc='File object (or descriptor)')
122
123     def get_fileno(self):
124         r"get_fileno() -> int :: Get the current file descriptor"
125         if hasattr(self.file, 'fileno'):
126             return self.file.fileno()
127         return self.file
128
129     fileno = property(get_fileno, doc='File descriptor (never a file object)')
130
131     def stop(self):
132         r"""stop() -> None :: Stop the event loop.
133
134         The event loop will be interrupted as soon as the current handler
135         finishes.
136         """
137         self._stop = True
138
139     def loop(self, once=False):
140         r"""loop([once]) -> None :: Wait for events.
141
142         Wait for events and handle then when they arrive. If once is True,
143         then only 1 event is processed and then this method returns.
144         """
145         # Flag modified by the signal handler
146         global timeout
147         # If we use a timer, we set up the signal
148         if self.timer is not None:
149             signal.signal(signal.SIGALRM, alarm_handler)
150             self.handle_timer()
151             signal.alarm(self.timer)
152         while True:
153             try:
154                 res = self.poll.poll()
155             except select.error, e:
156                 # The error is not an interrupt caused by the alarm, then raise
157                 if e.args[0] != errno.EINTR or not timeout:
158                     raise LoopInterruptedError(e)
159             # There was a timeout, so execute the timer handler
160             if timeout:
161                 timeout = False
162                 self.handle_timer()
163                 signal.alarm(self.timer)
164             # Not a timeout, execute the regular handler
165             else:
166                 self.handle()
167             # Look if we have to stop
168             if self._stop or once:
169                 self._stop = False
170                 break
171
172     def handle(self):
173         r"handle() -> None :: Abstract method to be overriden to handle events."
174         self.handler(self)
175
176     def handle_timer(self):
177         r"handle() -> None :: Abstract method to be overriden to handle events."
178         self.timer_handler(self)
179
180 if __name__ == '__main__':
181
182     import os
183     import time
184
185     def handle(event_loop):
186         data = os.read(event_loop.fileno, 100)
187         os.write(1, 'Received message: %r\n' % data)
188
189     p = EventLoop(0, handle)
190
191     os.write(1, 'Say something once: ')
192     p.loop(once=True)
193     os.write(1, 'Great!\n')
194
195     class Test(EventLoop):
196         def handle(self):
197             data = os.read(self.fileno, 100)
198             if data == 'q\n':
199                 self.stop()
200             else:
201                 os.write(1, 'Received message: %r\n' % data)
202         def handle_timer(self):
203             print time.strftime('%c')
204
205     p = Test(0, timer=5)
206
207     os.write(1, 'Say a lot of things, then press write just "q" to stop: ')
208     p.loop()
209     os.write(1, 'Ok, bye!\n')
210