SaltyCrane: decoratorshttps://www.saltycrane.com/blog/2010-11-06T22:00:52-07:00Fabric post-run processing Python decorator
2010-11-06T22:00:52-07:00https://www.saltycrane.com/blog/2010/11/fabric-post-run-processing-python-decorator/<pre class="python">import traceback
from functools import wraps
from fabric.api import env
# global variable for add_hooks()
parent_task_name = ''
def add_post_run_hook(hook, *args, **kwargs):
'''Run hook after Fabric tasks have completed on all hosts
Example usage:
@add_post_run_hook(postrunfunc, 'arg1', 'arg2')
def mytask():
# ...
'''
def true_decorator(f):
return add_hooks(post=hook, post_args=args, post_kwargs=kwargs)(f)
return true_decorator
def add_hooks(pre=None, pre_args=(), pre_kwargs={},
post=None, post_args=(), post_kwargs={}):
'''
Function decorator to be used with Fabric tasks. Adds pre-run
and/or post-run hooks to a Fabric task. Uses env.all_hosts to
determine when to run the post hook. Uses the global variable,
parent_task_name, to check if the task is a subtask (i.e. a
decorated task called by another decorated task). If it is a
subtask, do not perform pre or post processing.
pre: callable to be run before starting Fabric tasks
pre_args: a tuple of arguments to be passed to "pre"
pre_kwargs: a dict of keyword arguments to be passed to "pre"
post: callable to be run after Fabric tasks have completed on all hosts
post_args: a tuple of arguments to be passed to "post"
post_kwargs: a dict of keyword arguments to be passed to "post"
'''
# create a namespace to save state across hosts and tasks
class NS(object):
run_counter = 0
def true_decorator(f):
@wraps(f)
def f_wrapper(*args, **kwargs):
# set state variables
global parent_task_name
if not parent_task_name:
parent_task_name = f.__name__
NS.run_counter += 1
print 'parent_task_name: %s' % parent_task_name
print 'count/N_hosts: %d/%d' % (NS.run_counter, len(env.all_hosts))
# pre-run processing
if f.__name__ == parent_task_name and NS.run_counter == 1:
if pre:
print 'Pre-run processing...'
pre(*pre_args, **pre_kwargs)
# run the task
r = None
try:
r = f(*args, **kwargs)
except SystemExit:
pass
except:
print traceback.format_exc()
# post-run processing
if (f.__name__ == parent_task_name and
NS.run_counter >= len(env.all_hosts)):
if post:
print 'Post-run processing...'
post(*post_args, **post_kwargs)
return r
return f_wrapper
return true_decorator</pre>
Using a Python timeout decorator for uploading to S3
2010-04-27T15:55:58-07:00https://www.saltycrane.com/blog/2010/04/using-python-timeout-decorator-uploading-s3/<p>At work we are uploading many images to
<a href="http://aws.amazon.com/s3/">S3</a> using Python's
<a href="http://code.google.com/p/boto/">boto</a> library.
However we are experiencing a <em>RequestTimeTooSkewed</em>
error once every 100 uploads on average.
<a href="http://developer.amazonwebservices.com/connect/thread.jspa?messageID=144783">
We</a>
<a href="http://groups.google.com/group/boto-users/browse_thread/thread/467e0796052820ce/813e5b7db3867824?lnk=gst">
googled</a>, but did not find a solution. Our system time was in sync and our file
sizes were small (~50KB).
</p>
<p>Since we couldn't find the root cause, we added a
<a href="http://en.wikipedia.org/wiki/Watchdog_timer">watchdog timer</a>
as a bandaid solution.
We already use a
<a href="http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/">retry
decorator</a> to retry uploads to S3 when we get a
<em>500 Internal Server Error</em> response. To this we added a
timeout decorator which
cancels the S3 upload if it takes more than a couple minutes.
With this decorator, we don't have to wait the full 15 minutes
before S3 returns the <em>403 Forbidden</em> (RequestTimeTooSkewed error)
response.
</p>
<p>I found the timeout decorator at
<a href="http://code.activestate.com/recipes/307871-timing-out-function/">Activestate's
Python recipes</a>.
It makes use of Python's <a href="http://docs.python.org/library/signal.html">signal
library</a>.
Below is an example of how it's used.
</p>
<pre class="python">import signal
class TimeoutError(Exception):
def __init__(self, value = "Timed Out"):
self.value = value
def __str__(self):
return repr(self.value)
def timeout(seconds_before_timeout):
def decorate(f):
def handler(signum, frame):
raise TimeoutError()
def new_f(*args, **kwargs):
old = signal.signal(signal.SIGALRM, handler)
signal.alarm(seconds_before_timeout)
try:
result = f(*args, **kwargs)
finally:
# reinstall the old signal handler
signal.signal(signal.SIGALRM, old)
# cancel the alarm
# this line should be inside the "finally" block (per Sam Kortchmar)
signal.alarm(0)
return result
new_f.func_name = f.func_name
return new_f
return decorate</pre>
<p>Try it out:</p>
<pre class="python">import time
@timeout(5)
def mytest():
print "Start"
for i in range(1,10):
time.sleep(1)
print "%d seconds have passed" % i
if __name__ == '__main__':
mytest()</pre>
<p>Results:</p>
<pre>
Start
1 seconds have passed
2 seconds have passed
3 seconds have passed
4 seconds have passed
Traceback (most recent call last):
File "timeout_ex.py", line 47, in <module>
function_times_out()
File "timeout_ex.py", line 17, in new_f
result = f(*args, **kwargs)
File "timeout_ex.py", line 42, in function_times_out
time.sleep(1)
File "timeout_ex.py", line 12, in handler
raise TimeoutError()
__main__.TimeoutError: 'Timed Out'</pre>
<h4>Bug found by Sam Kortchmar <small><em>(added 2018-08-18)</em></small></h4>
<p>
The code on the
<a href="http://code.activestate.com/recipes/307871-timing-out-function/">
Activestate recipe</a> has <code>signal.alarm(0)</code> outside of the <code>finally</code>
block, but <a href="http://skortchmark.com/">Sam Kortchmar</a>
reported to me that it needs to be inside the <code>finally</code> block
so that the alarm will be cancelled even if there is an exception in the user's function
that is handled by the user. With <code>signal.alarm(0)</code> outside of the <code>finally</code>
block, the alarm still fires in that case.
</p>
<p>Here is the test case sent by Sam:</p>
<pre>import unittest2
import time
class TestTimeout(unittest2.TestCase):
def test_watchdog_doesnt_kill_interpreter(self):
"""If this test executes at all, it's working!
otherwise, the whole testing section will be killed
and print out "Alarm clock"
"""
@timeout(1)
def my_func():
raise Exception
try:
my_func()
except Exception:
pass
time.sleep(1.2)
assert True</pre>
<h4>The RequestTimeTooSkewed error</h4>
<pre>S3ResponseError: 403 Forbidden
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>RequestTimeTooSkewed</Code><Message>The difference between the request time and the current time is too large.</Message><MaxAllowedSkewMilliseconds>900000</MaxAllowedSkewMilliseconds><RequestId>7DDDS67HF8E37</RequestId><HostId>LKE893JFGDLASKJR9BJ-A9NASFPNAOPWEJORG-98DFGJA498JVJ-A04320JF0293JLKE</HostId><RequestTime>Tue, 27 Apr 2010 22:20:58 GMT</RequestTime><ServerTime>2010-04-27T22:55:24Z</ServerTime></Error></pre>
<h4>See also</h4>
<ul>
<li><a href="http://nick.vargish.org/clues/python-tricks.html">
http://nick.vargish.org/clues/python-tricks.html</a></li>
<li><a href="http://programming-guides.com/python/timeout-a-function">
http://programming-guides.com/python/timeout-a-function</a></li>
</ul>
Two of the simplest Python decorator examples
2010-03-09T00:00:00-08:00https://www.saltycrane.com/blog/2010/03/simple-python-decorator-examples/<p>After trying for about the fifth time, I think I am starting to understand Python
decorators due largely to
<a href="http://jackdied.blogspot.com/">Jack Diederich</a>'s PyCon 2009 talk,
<a href="https://www.youtube.com/watch?v=cAGliEJV9_o">
<em>Class Decorators: Radically Simple</em></a>.</p>
<p>Jack's practical definition of a decorator is:</p>
<ul>
<li>A function that takes one argument</li>
<li>Returns something <em>useful</em></li>
</ul>
<p>In many cases, a function decorator can be described more specifically:
<ul>
<li>A function that takes one argument (the function being decorated)</li>
<li>Returns the same function or a function with a similar signature</li>
</ul>
<p>As Jack states in his talk, a decorator is merely syntactic sugar. The same
functionality can be achieved without using the decorator syntax. This code
snippet:</p>
<pre class="python">@mydecorator
def myfunc():
pass</pre>
<p>is equivalent to:</p>
<pre class ="python">def myfunc():
pass
myfunc = mydecorator(myfunc)</pre>
<p>Here are two of the simplest examples from Jack's talk:</p>
<h4>Identity decorator</h4>
<p>This is the simplest decorator. It does nothing. It takes the decorated
function as an argument and returns the same function without doing anything.
</p>
<pre class="python">def identity(ob):
return ob
@identity
def myfunc():
print "my function"
myfunc()
print myfunc</pre>
<pre>my function
<function myfunc at 0xb76db17c></pre>
<h4>Hello world decorator</h4>
<p>I am dumb. This one doesn't do what it's supposed to.</p>
<del>
<p>This decorator prints "Hello world" before returning the decorated function.</p>
<pre class="python">def helloworld(ob):
print "Hello world"
return ob
@helloworld
def myfunc():
print "my function"
myfunc()
print myfunc</pre>
<pre>
Hello world
my function
<function myfunc at 0xb78360d4></pre>
</del>
<h4>A simple decorator that actually does something (and is not broken like the Hello world decorator above)</h4>
<p>This decorator is used to print some text before and after calling the decorated function.
Most of the time the decorated function is wrapped by a function which calls the decorated
function and returns what it returns. ?When is a wrapper not needed?
</p>
<pre class="python">from functools import wraps
def mydecorator(f):
@wraps(f)
def wrapped(*args, **kwargs):
print "Before decorated function"
r = f(*args, **kwargs)
print "After decorated function"
return r
return wrapped
@mydecorator
def myfunc(myarg):
print "my function", myarg
return "return value"
r = myfunc('asdf')
print r</pre>
<pre>
Before decorated function
my function asdf
After decorated function
return value</pre>
<h4>What if I want to pass arguments to the decorator itself (not the decorated function)?</h4>
<p>A decorator takes exactly one argument so you will need a factory to create the decorator.
Unlike the previous example, notice how the factory function is called with parentheses, <code>@mydecorator_not_actually(count=5)</code>,
to produce the real decorator.
</p>
<pre class="python">from functools import wraps
def mydecorator_not_actually(count):
def true_decorator(f):
@wraps(f)
def wrapped(*args, **kwargs):
for i in range(count):
print "Before decorated function"
r = f(*args, **kwargs)
for i in range(count):
print "After decorated function"
return r
return wrapped
return true_decorator
@mydecorator_not_actually(count=5)
def myfunc(myarg):
print "my function", myarg
return "return value"
r = myfunc('asdf')
print r</pre>
<pre>Before decorated function
Before decorated function
Before decorated function
Before decorated function
Before decorated function
my function asdf
After decorated function
After decorated function
After decorated function
After decorated function
After decorated function
return value</pre>
<h4>References / See also</h4>
<ul>
<li>The <a href="http://wiki.python.org/moin/PythonDecoratorLibrary">Decorator Library</a>
on the Python wiki</li>
<li><a href="http://stackoverflow.com/questions/739654/understanding-python-decorators">
Understanding Python decorators - Stack Overflow
</a>
</li>
</ul>
Trying out a Retry decorator in Python
2009-11-17T17:29:19-08:00https://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/<p>The Python wiki has a
<a href="http://wiki.python.org/moin/PythonDecoratorLibrary#Retry">
Retry decorator</a> example which retries calling a failure-prone function
using an <a href="http://en.wikipedia.org/wiki/Exponential_backoff">
exponential backoff</a> algorithm. I modified it slightly to check
for exceptions instead of a <code>False</code> return value to indicate
failure. Each time the decorated function throws an exception, the decorator
will wait a period of time and retry calling the function until the maximum
number of tries is used up. If the decorated function fails on the last try,
the exception will occur unhandled.
</p>
<pre class="python">import time
from functools import wraps
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
"""Retry calling the decorated function using an exponential backoff.
http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry
:param ExceptionToCheck: the exception to check. may be a tuple of
exceptions to check
:type ExceptionToCheck: Exception or tuple
:param tries: number of times to try (not retry) before giving up
:type tries: int
:param delay: initial delay between retries in seconds
:type delay: int
:param backoff: backoff multiplier e.g. value of 2 will double the delay
each retry
:type backoff: int
:param logger: logger to use. If None, print
:type logger: logging.Logger instance
"""
def deco_retry(f):
@wraps(f)
def f_retry(*args, **kwargs):
mtries, mdelay = tries, delay
while mtries > 1:
try:
return f(*args, **kwargs)
except ExceptionToCheck, e:
msg = "%s, Retrying in %d seconds..." % (str(e), mdelay)
if logger:
logger.warning(msg)
else:
print msg
time.sleep(mdelay)
mtries -= 1
mdelay *= backoff
return f(*args, **kwargs)
return f_retry # true decorator
return deco_retry</pre>
<h4 id="always-fail">Try an "always fail" case</h4>
<pre class="python">@retry(Exception, tries=4)
def test_fail(text):
raise Exception("Fail")
test_fail("it works!")</pre>
<p>Results:</p>
<pre>Fail, Retrying in 3 seconds...
Fail, Retrying in 6 seconds...
Fail, Retrying in 12 seconds...
Traceback (most recent call last):
File "retry_decorator.py", line 47, in <module>
test_fail("it works!")
File "retry_decorator.py", line 26, in f_retry
f(*args, **kwargs)
File "retry_decorator.py", line 33, in test_fail
raise Exception("Fail")
Exception: Fail</pre>
<h4 id="success">Try a "success" case</h4>
<pre class="python">@retry(Exception, tries=4)
def test_success(text):
print "Success: ", text
test_success("it works!")</pre>
<p>Results:</p>
<pre>Success: it works!</pre>
<h4 id="random-fail">Try a "random fail" case</h4>
<pre class="python">import random
@retry(Exception, tries=4)
def test_random(text):
x = random.random()
if x < 0.5:
raise Exception("Fail")
else:
print "Success: ", text
test_random("it works!")</pre>
<p>Results:</p>
<pre>Fail, Retrying in 3 seconds...
Success: it works!</pre>
<h4 id="multiple-exceptions">Try handling multiple exceptions</h4>
<p><em>Added 2010-04-27</em></p>
<pre class="python">import random
@retry((NameError, IOError), tries=20, delay=1, backoff=1)
def test_multiple_exceptions():
x = random.random()
if x < 0.40:
raise NameError("NameError")
elif x < 0.80:
raise IOError("IOError")
else:
raise KeyError("KeyError")
test_multiple_exceptions()</pre>
<p>Results:</p>
<pre>IOError, Retrying in 1 seconds...
NameError, Retrying in 1 seconds...
IOError, Retrying in 1 seconds...
IOError, Retrying in 1 seconds...
NameError, Retrying in 1 seconds...
IOError, Retrying in 1 seconds...
NameError, Retrying in 1 seconds...
NameError, Retrying in 1 seconds...
NameError, Retrying in 1 seconds...
IOError, Retrying in 1 seconds...
Traceback (most recent call last):
File "retry_decorator.py", line 61, in <module>
test_multiple_exceptions("hello")
File "retry_decorator.py", line 14, in f_retry
f(*args, **kwargs)
File "retry_decorator.py", line 56, in test_multiple_exceptions
raise KeyError("KeyError")
KeyError: 'KeyError'</pre>
<h4 id="tests">Unit tests</h4>
<p><em>Added 2013-01-22</em>. Note: Python 2.7 is required to run the tests.</p>
<pre class="python">import logging
import unittest
from decorators import retry
class RetryableError(Exception):
pass
class AnotherRetryableError(Exception):
pass
class UnexpectedError(Exception):
pass
class RetryTestCase(unittest.TestCase):
def test_no_retry_required(self):
self.counter = 0
@retry(RetryableError, tries=4, delay=0.1)
def succeeds():
self.counter += 1
return 'success'
r = succeeds()
self.assertEqual(r, 'success')
self.assertEqual(self.counter, 1)
def test_retries_once(self):
self.counter = 0
@retry(RetryableError, tries=4, delay=0.1)
def fails_once():
self.counter += 1
if self.counter < 2:
raise RetryableError('failed')
else:
return 'success'
r = fails_once()
self.assertEqual(r, 'success')
self.assertEqual(self.counter, 2)
def test_limit_is_reached(self):
self.counter = 0
@retry(RetryableError, tries=4, delay=0.1)
def always_fails():
self.counter += 1
raise RetryableError('failed')
with self.assertRaises(RetryableError):
always_fails()
self.assertEqual(self.counter, 4)
def test_multiple_exception_types(self):
self.counter = 0
@retry((RetryableError, AnotherRetryableError), tries=4, delay=0.1)
def raise_multiple_exceptions():
self.counter += 1
if self.counter == 1:
raise RetryableError('a retryable error')
elif self.counter == 2:
raise AnotherRetryableError('another retryable error')
else:
return 'success'
r = raise_multiple_exceptions()
self.assertEqual(r, 'success')
self.assertEqual(self.counter, 3)
def test_unexpected_exception_does_not_retry(self):
@retry(RetryableError, tries=4, delay=0.1)
def raise_unexpected_error():
raise UnexpectedError('unexpected error')
with self.assertRaises(UnexpectedError):
raise_unexpected_error()
def test_using_a_logger(self):
self.counter = 0
sh = logging.StreamHandler()
logger = logging.getLogger(__name__)
logger.addHandler(sh)
@retry(RetryableError, tries=4, delay=0.1, logger=logger)
def fails_once():
self.counter += 1
if self.counter < 2:
raise RetryableError('failed')
else:
return 'success'
fails_once()
if __name__ == '__main__':
unittest.main()</pre>
<h4 id="code-license">Code / License</h4>
<p>This code is also on github at: <a href="https://github.com/saltycrane/retry-decorator">https://github.com/saltycrane/retry-decorator</a>. It is <a href="https://github.com/saltycrane/retry-decorator/blob/master/LICENSE">BSD licensed</a>.
</p>