Есть у нас пара внешних HTTP сервисов, которые работают довольно нестабильно. Да, мы должны были об этом подумать на этапе разработки, но мы все время откладывали в длинный ящик, аккуратно писали logging.error
и двигались по другим направлениям.
Но, как обычно бывает, столкнулись с реальностью — сервисы работают действительно нестабильно. Иногда медленно, иногда возвращают 404 или 500, иногда вылетают с socket timeout. Сотни исключений сыпались на почту ежедневно.
С этим надо было что-то делать. Так родился safe_exec
декоратор. Декорируя функцию с его помощью, нужно указать ожидаемые исключения и поведение если исключение будет генерироваться. Например, повторить 3 раза через 1 секунду, если исключение все еще генерируется — вернуть значение по умолчанию.
Декоратор:
import logging
import time
__all__ = ("safe_exec",)
def safe_exec(exceptions, shakes=3, timeout=1, title="", default=None):
"""
Decorator to safely execute function or method
within `shakes` trying
"""
def wrap(func):
if not isinstance(exceptions, tuple):
raise TypeError(
"First argument of safe_exec should be tuple of exceptions"
)
def wrapped(*args, **kwargs):
name = func.__name__
result = None
for shake in range(shakes):
try:
result = func(*args, **kwargs)
break
except exceptions:
logging.warn("%s: Sorry, can't execute %s, shake #%d",
title,
name,
shake,
exc_info=True
)
time.sleep(timeout)
else:
logging.error(
"%s: Can't execute `%s` after %d shakes",
title,
name,
shakes
)
return default
return result
return wrapped
return wrap
Пример использования:
import urllib2
@safe_exec((urllib2.URLError, urllib2.HTTPError), shakes=2)
def download(url):
return urllib2.urlopen(url).read()
download("http://slow-resource.com/")
Автор: joymax