diff -r b758351d191f -r cc9b7e14412b web/lib/django/utils/functional.py --- a/web/lib/django/utils/functional.py Wed May 19 17:43:59 2010 +0200 +++ b/web/lib/django/utils/functional.py Tue May 25 02:43:45 2010 +0200 @@ -60,9 +60,6 @@ # Summary of changes made to the Python 2.5 code below: # * swapped ``partial`` for ``curry`` to maintain backwards-compatibility # in Django. -# * Wrapped the ``setattr`` call in ``update_wrapper`` with a try-except -# block to make it compatible with Python 2.3, which doesn't allow -# assigning to ``__name__``. # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation. # All Rights Reserved. @@ -90,10 +87,7 @@ function (defaults to functools.WRAPPER_UPDATES) """ for attr in assigned: - try: - setattr(wrapper, attr, getattr(wrapped, attr)) - except TypeError: # Python 2.3 doesn't allow assigning to __name__. - pass + setattr(wrapper, attr, getattr(wrapped, attr)) for attr in updated: getattr(wrapper, attr).update(getattr(wrapped, attr)) # Return the wrapper so this can be used as a decorator via curry() @@ -147,6 +141,7 @@ the lazy evaluation code is triggered. Results are not memoized; the function is evaluated on every access. """ + class __proxy__(Promise): """ Encapsulate a function call and act as a proxy for methods that are @@ -162,14 +157,24 @@ if self.__dispatch is None: self.__prepare_class__() + def __reduce__(self): + return ( + _lazy_proxy_unpickle, + (self.__func, self.__args, self.__kw) + resultclasses + ) + def __prepare_class__(cls): cls.__dispatch = {} for resultclass in resultclasses: cls.__dispatch[resultclass] = {} for (k, v) in resultclass.__dict__.items(): + # All __promise__ return the same wrapper method, but they + # also do setup, inserting the method into the dispatch + # dict. + meth = cls.__promise__(resultclass, k, v) if hasattr(cls, k): continue - setattr(cls, k, cls.__promise__(resultclass, k, v)) + setattr(cls, k, meth) cls._delegate_str = str in resultclasses cls._delegate_unicode = unicode in resultclasses assert not (cls._delegate_str and cls._delegate_unicode), "Cannot call lazy() with both str and unicode return types." @@ -236,6 +241,9 @@ return wraps(func)(__wrapper__) +def _lazy_proxy_unpickle(func, args, kwargs, *resultclasses): + return lazy(func, *resultclasses)(*args, **kwargs) + def allow_lazy(func, *resultclasses): """ A decorator that allows a function to be called with one or more lazy @@ -257,9 +265,8 @@ A wrapper for another class that can be used to delay instantiation of the wrapped class. - This is useful, for example, if the wrapped class needs to use Django - settings at creation time: we want to permit it to be imported without - accessing settings. + By subclassing, you have the opportunity to intercept and alter the + instantiation. If you don't need to do that, use SimpleLazyObject. """ def __init__(self): self._wrapped = None @@ -267,9 +274,6 @@ def __getattr__(self, name): if self._wrapped is None: self._setup() - if name == "__members__": - # Used to implement dir(obj) - return self._wrapped.get_all_members() return getattr(self._wrapped, name) def __setattr__(self, name, value): @@ -281,9 +285,83 @@ self._setup() setattr(self._wrapped, name, value) + def __delattr__(self, name): + if name == "_wrapped": + raise TypeError("can't delete _wrapped.") + if self._wrapped is None: + self._setup() + delattr(self._wrapped, name) + def _setup(self): """ Must be implemented by subclasses to initialise the wrapped object. """ raise NotImplementedError + # introspection support: + __members__ = property(lambda self: self.__dir__()) + + def __dir__(self): + if self._wrapped is None: + self._setup() + return dir(self._wrapped) + +class SimpleLazyObject(LazyObject): + """ + A lazy object initialised from any function. + + Designed for compound objects of unknown type. For builtins or objects of + known type, use django.utils.functional.lazy. + """ + def __init__(self, func): + """ + Pass in a callable that returns the object to be wrapped. + + If copies are made of the resulting SimpleLazyObject, which can happen + in various circumstances within Django, then you must ensure that the + callable can be safely run more than once and will return the same + value. + """ + self.__dict__['_setupfunc'] = func + # For some reason, we have to inline LazyObject.__init__ here to avoid + # recursion + self._wrapped = None + + def __str__(self): + if self._wrapped is None: self._setup() + return str(self._wrapped) + + def __unicode__(self): + if self._wrapped is None: self._setup() + return unicode(self._wrapped) + + def __deepcopy__(self, memo): + if self._wrapped is None: + # We have to use SimpleLazyObject, not self.__class__, because the + # latter is proxied. + result = SimpleLazyObject(self._setupfunc) + memo[id(self)] = result + return result + else: + # Changed to use deepcopy from copycompat, instead of copy + # For Python 2.4. + from django.utils.copycompat import deepcopy + return deepcopy(self._wrapped, memo) + + # Need to pretend to be the wrapped class, for the sake of objects that care + # about this (especially in equality tests) + def __get_class(self): + if self._wrapped is None: self._setup() + return self._wrapped.__class__ + __class__ = property(__get_class) + + def __eq__(self, other): + if self._wrapped is None: self._setup() + return self._wrapped == other + + def __hash__(self): + if self._wrapped is None: self._setup() + return hash(self._wrapped) + + def _setup(self): + self._wrapped = self._setupfunc()