rss2email: split massive package into modules
[rss2email.git] / rss2email / util.py
1 # Copyright
2
3 """Odds and ends
4 """
5
6 import sys as _sys
7 import threading as _threading
8
9 from . import error as _error
10
11
12 class TimeLimitedFunction (_threading.Thread):
13     """Run `function` with a time limit of `timeout` seconds.
14
15     >>> import time
16     >>> def sleeping_return(sleep, x):
17     ...     time.sleep(sleep)
18     ...     return x
19     >>> TimeLimitedFunction(0.5, sleeping_return)(0.1, 'x')
20     'x'
21     >>> TimeLimitedFunction(0.5, sleeping_return)(10, 'y')
22     Traceback (most recent call last):
23       ...
24     rss2email.error.TimeoutError: 0.5 second timeout exceeded
25     >>> TimeLimitedFunction(0.5, time.sleep)('x')
26     Traceback (most recent call last):
27       ...
28     rss2email.error.TimeoutError: error while running time limited function: a float is required
29     """
30     def __init__(self, timeout, target, **kwargs):
31         super(TimeLimitedFunction, self).__init__(target=target, **kwargs)
32         self.setDaemon(True)  # daemon kwarg only added in Python 3.3.
33         self.timeout = timeout
34         self.result = None
35         self.error = None
36
37     def run(self):
38         """Based on Thread.run().
39
40         We add handling for self.result and self.error.
41         """
42         try:
43             if self._target:
44                 self.result = self._target(*self._args, **self._kwargs)
45         except:
46             self.error = _sys.exc_info()
47         finally:
48             # Avoid a refcycle if the thread is running a function with
49             # an argument that has a member that points to the thread.
50             del self._target, self._args, self._kwargs
51
52     def __call__(self, *args, **kwargs):
53         self._args = args
54         self._kwargs = kwargs
55         self.start()
56         self.join(self.timeout)
57         if self.error:
58             raise _error.TimeoutError(
59                 time_limited_function=self) from self.error[1]
60         elif self.isAlive():
61             raise _error.TimeoutError(time_limited_function=self)
62         return self.result