1 # Copyright (C) 2012-2013 W. Trevor King <wking@tremily.us>
3 # This file is part of rss2email.
5 # rss2email is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free Software
7 # Foundation, either version 2 of the License, or (at your option) version 3 of
10 # rss2email is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License along with
15 # rss2email. If not, see <http://www.gnu.org/licenses/>.
20 import importlib as _importlib
21 import pickle as _pickle
22 import pickletools as _pickletools
24 import threading as _threading
26 from . import error as _error
29 class TimeLimitedFunction (_threading.Thread):
30 """Run `function` with a time limit of `timeout` seconds.
33 >>> def sleeping_return(sleep, x):
36 >>> TimeLimitedFunction(0.5, sleeping_return)(0.1, 'x')
38 >>> TimeLimitedFunction(0.5, sleeping_return)(10, 'y')
39 Traceback (most recent call last):
41 rss2email.error.TimeoutError: 0.5 second timeout exceeded
42 >>> TimeLimitedFunction(0.5, time.sleep)('x')
43 Traceback (most recent call last):
45 rss2email.error.TimeoutError: error while running time limited function: a float is required
47 def __init__(self, timeout, target, **kwargs):
48 super(TimeLimitedFunction, self).__init__(target=target, **kwargs)
49 self.setDaemon(True) # daemon kwarg only added in Python 3.3.
50 self.timeout = timeout
55 """Based on Thread.run().
57 We add handling for self.result and self.error.
61 self.result = self._target(*self._args, **self._kwargs)
63 self.error = _sys.exc_info()
65 # Avoid a refcycle if the thread is running a function with
66 # an argument that has a member that points to the thread.
67 del self._target, self._args, self._kwargs
69 def __call__(self, *args, **kwargs):
73 self.join(self.timeout)
75 raise _error.TimeoutError(
76 time_limited_function=self) from self.error[1]
78 raise _error.TimeoutError(time_limited_function=self)
83 """Return the full import name for a Python object
85 Note that this does not always exist (e.g. for dynamically
86 generated functions). This function does it's best, using Pickle
87 for the heavy lifting. For example:
89 >>> import_name(import_name)
90 'rss2email.util import_name'
92 Note the space between the module (``rss2email.util``) and the
93 function within the module (``import_name``).
95 Some objects can't be pickled:
97 >>> import_name(lambda x: 'muahaha')
98 Traceback (most recent call last):
100 _pickle.PicklingError: Can't pickle <class 'function'>: attribute lookup builtins.function failed
102 Some objects don't have a global scope:
104 >>> import_name('abc')
105 Traceback (most recent call last):
109 pickle = _pickle.dumps(obj)
110 for opcode,arg,pos in _pickletools.genops(pickle):
111 if opcode.name == 'GLOBAL':
113 raise ValueError(obj)
115 def import_function(name):
116 """Import a function using the full import name
118 >>> import_function('rss2email.util import_function') # doctest: +ELLIPSIS
119 <function import_function at 0x...>
120 >>> import_function(import_name(import_function)) # doctest: +ELLIPSIS
121 <function import_function at 0x...>
123 >>> import_function('rss2email.util does_not_exist')
124 Traceback (most recent call last):
126 AttributeError: 'module' object has no attribute 'does_not_exist'
127 >>> import_function('rss2email.util has invalid syntax')
128 Traceback (most recent call last):
130 AttributeError: 'module' object has no attribute 'has invalid syntax'
131 >>> import_function('rss2email.util.no_space')
132 Traceback (most recent call last):
134 ValueError: rss2email.util.no_space
137 module_name,function_name = name.split(' ', 1)
138 except ValueError as e:
139 raise ValueError(name) from e
140 module = _importlib.import_module(module_name)
141 return getattr(module, function_name)