Source code for stoqlib.lib.decorators

# -*- Mode: Python; coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
# © 2011 Christopher Arndt, MIT License

import inspect
import time
import functools
import logging
import Queue
import sys
import threading
import traceback

log = logging.getLogger(__name__)

[docs]class cached_property(object): '''Decorator for read-only properties evaluated only once within TTL period. It can be used to created a cached property like this:: import random # the class containing the property must be a new-style class class MyClass(object): # create property whose value is cached for ten minutes @cached_property(ttl=600) def randint(self): # will only be evaluated every 10 min. at maximum. return random.randint(0, 100) The value is cached in the '_cache' attribute of the object instance that has the property getter method wrapped by this decorator. The '_cache' attribute value is a dictionary which has a key for every property of the object which is wrapped by this decorator. Each entry in the cache is created only when the property is accessed for the first time and is a two-element tuple with the last computed property value and the last time it was updated in seconds since the epoch. The default time-to-live (TTL) is 300 seconds (5 minutes). Set the TTL to zero for the cached value to never expire. To expire a cached property value manually just do:: del instance._cache[<property name>] ''' def __init__(self, ttl=300): self.ttl = ttl def __call__(self, fget, doc=None): self.fget = fget self.__doc__ = doc or fget.__doc__ self.__name__ = fget.__name__ self.__module__ = fget.__module__ return self def __get__(self, inst, owner): now = time.time() try: value, last_update = inst._cache[self.__name__] if self.ttl > 0 and now - last_update > self.ttl: raise AttributeError except (KeyError, AttributeError): value = self.fget(inst) try: cache = inst._cache except AttributeError: cache = inst._cache = {} cache[self.__name__] = (value, now) return value
[docs]class cached_function(object): """Like cached_property but for functions""" def __init__(self, ttl=300): self._cache = {} self.ttl = ttl def __call__(self, func): def wraps(*args, **kwargs): now = time.time() # Use inspect.getcallargs so that we have all positional arguments # with their respective names args_dict = inspect.getcallargs(func, *args, **kwargs) if len(args_dict) == len(args) + len(kwargs): # This means all args and kwargs were converted to the named # arguments, and we can use it as a key. key = tuple(sorted(args_dict.items())) else: # otherwise, just use them separately key = (args, tuple(sorted(kwargs.items()))) try: value, last_update = self._cache[key] if self.ttl > 0 and now - last_update > self.ttl: raise AttributeError return value except (KeyError, AttributeError): value = func(*args, **kwargs) self._cache[key] = (value, now) return value return wraps
[docs]class public: """ A decorator that is used to mark an API public. It can be used on classes:: @public(since="1.0") class SomeClass: pass Or a function/method:: @public(since="1.0") def foo(self, arg): pass There's an optional argument called *since* which takes a string and specifies the version in which this api was added. """ def __init__(self, since=None): self.since = since def __call__(self, func): return func
[docs]def threaded(original): """Threaded decorator This will make the decorated function run in a separate thread, and will keep the gui responsive by running pending main iterations. Note that the result will be syncronous. """ @functools.wraps(original) def _run_thread_task(*args, **kwargs): import gtk q = Queue.Queue() # Wrap the actual function inside a try/except so that we can return the # exception to the main thread, for it to be reraised def f(): try: retval = original(*args, **kwargs) except Exception as e: log.error(''.join(traceback.format_exception(*sys.exc_info()))) return e return retval # This is the new thread. t = threading.Thread(target=lambda q=q: q.put(f())) t.daemon = True t.start() # We we'll wait for the new thread to finish here, while keeping the # interface responsive (a nice progress dialog should be displayed) while t.is_alive(): if gtk.events_pending(): gtk.main_iteration(False) try: retval = q.get_nowait() except Queue.Empty: # pragma no cover (how do I test this?) return None if isinstance(retval, Exception): # reraise the exception just like if it happened in this thread. # This will help catching them by callsites so they can warn # the user raise retval else: return retval return _run_thread_task