Flow ==== .. decorator:: silent Ignore all real exceptions (descendants of :exc:`~py3:exceptions.Exception`). Handy for cleaning data such as user input:: brand_id = silent(int)(request.GET['brand_id']) ids = keep(silent(int), request.GET.getlist('id')) And in data import/transform:: get_greeting = compose(silent(string.lower), re_finder(r'(\w+)!')) map(get_greeting, ['a!', ' B!', 'c.']) # -> ['a', 'b', None] .. note:: Avoid silencing non-primitive functions, use :func:`@ignore()` instead and even then be careful not to swallow exceptions unintentionally. .. decorator:: ignore(errors, default=None) Same as :func:`@silent`, but able to specify ``errors`` to catch and ``default`` to return in case of error caught. ``errors`` can either be exception class or a tuple of them. .. function:: suppress(*errors) A context manager which suppresses given exceptions under its scope:: with suppress(HttpError): # Assume this request can fail, and we are ok with it make_http_request() .. function:: nullcontext(enter_result=None) A noop context manager that returns ``enter_result`` from ``__enter__``:: ctx = nullcontext() if threads: ctx = op_thread_lock with ctx: # ... do stuff .. decorator:: once once_per_args once_per(*argnames) Call function only once, once for every combination of values of its arguments or once for every combination of given arguments. Thread safe. Handy for various initialization purposes:: # Global initialization @once def initialize_cache(): conn = some.Connection(...) # ... set up everything # Per argument initialization @once_per_args def initialize_language(lang): conf = load_language_conf(lang) # ... set up language # Setup each class once class SomeManager(Manager): @once_per('cls') def _initialize_class(self, cls): pre_save.connect(self._pre_save, sender=cls) # ... set up signals, no dups .. function:: raiser(exception_or_class=Exception, *args, **kwargs) Constructs function that raises given exception with given arguments on any invocation. You may pass a string instead of exception as a shortcut:: mocker.patch('mod.Class.propname', property(raiser("Shouldn't be called"))) This will raise an ``Exception`` with a corresponding message. .. decorator:: reraise(errors, into) Intercepts any error of ``errors`` classes and reraises it as ``into`` error. Can be used as decorator or a context manager:: with reraise(json.JSONDecodeError, SuspiciousOperation('Invalid JSON')): return json.loads(text) ``into`` can also be a callable to transform the error before reraising:: @reraise(requests.RequestsError, lambda e: MyAPIError(error_desc(e))) def api_call(...): # ... .. decorator:: retry(tries, errors=Exception, timeout=0, filter_errors=None) Every call of the decorated function is tried up to ``tries`` times. The first attempt counts as a try. Retries occur when any subclass of ``errors`` is raised, where``errors`` is an exception class or a list/tuple of exception classes. There will be a delay in ``timeout`` seconds between tries. A common use is to wrap some unreliable action:: @retry(3, errors=HttpError) def download_image(url): # ... make http request return image Errors to retry may addtionally be filtered with ``filter_errors`` when classes are not specific enough:: @retry(3, errors=HttpError, filter_errors=lambda e: e.status_code >= 500) def download_image(url): # ... You can pass a callable as ``timeout`` to achieve exponential delays or other complex behavior:: @retry(3, errors=HttpError, timeout=lambda a: 2 ** a) def download_image(url): # ... make http request return image .. function:: fallback(*approaches) Tries several approaches until one works. Each approach is either callable or a tuple ``(callable, errors)``, where errors is an exception class or a tuple of classes, which signal to fall back to next approach. If ``errors`` is not supplied then fall back is done for any :exc:`~py3:exceptions.Exception`:: fallback( (partial(send_mail, ADMIN_EMAIL, message), SMTPException), partial(log.error, message), # Handle any Exception (raiser(FeedbackError, "Failed"), ()) # Handle nothing ) .. function:: limit_error_rate(fails, timeout, exception=ErrorRateExceeded) If function fails to complete ``fails`` times in a row, calls to it will be intercepted for ``timeout`` with ``exception`` raised instead. A clean way to short-circuit function taking too long to fail:: @limit_error_rate(fails=5, timeout=60, exception=RequestError('Temporary unavailable')) def do_request(query): # ... make a http request return data Can be combined with :func:`ignore` to silently stop trying for a while:: @ignore(ErrorRateExceeded, default={'id': None, 'name': 'Unknown'}) @limit_error_rate(fails=5, timeout=60) def get_user(id): # ... make a http request return data .. function:: throttle(period) Only runs a decorated function once in a ``period``:: @throttle(60) def process_beat(pk, progress): Model.objects.filter(pk=pk).update(beat=timezone.now(), progress=progress) # Processing something, update progress info no more often then once a minute for i in ...: process_beat(pk, i / n) # ... do actual processing .. decorator:: collecting Transforms generator or other iterator returning function into list returning one. Handy to prevent quirky iterator-returning properties:: @property @collecting def path_up(self): node = self while node: yield node node = node.parent Also makes list constructing functions beautifully yielding. .. Or you could just write:: .. @property .. def path_up(self): .. going_up = iterate(attrgetter('parent'), self) .. return list(takewhile(bool, going_up)) .. decorator:: joining(sep) Wraps common python idiom "collect then join" into a decorator. Transforms generator or alike into function, returning string of joined results. Automatically converts all elements to separator type for convenience. Goes well with generators with some ad-hoc logic within:: @joining(', ') def car_desc(self): yield self.year_made if self.engine_volume: yield '%s cc' % self.engine_volume if self.transmission: yield self.get_transmission_display() if self.gear: yield self.get_gear_display() # ... Use ``bytes`` separator to get bytes result:: @joining(b' ') def car_desc(self): yield self.year_made # ... See also :func:`str_join`. .. decorator:: post_processing(func) Passes decorated function result through ``func``. This is the generalization of :func:`@collecting` and :func:`@joining()`. Could save you writing a decorator or serve as an extended comprehension: :: @post_processing(dict) def make_cond(request): if request.GET['new']: yield 'year__gt', 2000 for key, value in request.GET.items(): if value == '': continue # ... .. decorator:: wrap_with(ctx) Turns a context manager into a decorator:: @wrap_with(threading.Lock()) def protected_func(...): # ... .. raw:: html :file: descriptions.html