From 60cf3f2f842b6e56132b8880c70acc87bd753c2e Mon Sep 17 00:00:00 2001
From: Alex Gaynor <alex.gaynor@gmail.com>
Date: Wed, 1 Jun 2011 15:30:06 +0000
Subject: [PATCH] Allow SimpleLazyObjects to return None without constantly
 being reevaluated, also proxy ``__nonzero__``, and do some codecleanup as
 well.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16308 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
 django/utils/functional.py                    | 58 ++++++++-----------
 .../regressiontests/utils/simplelazyobject.py | 29 ++++++++--
 2 files changed, 49 insertions(+), 38 deletions(-)

diff --git a/django/utils/functional.py b/django/utils/functional.py
index 76f3639b58..c8f8ee33c7 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -1,3 +1,4 @@
+import operator
 from functools import wraps, update_wrapper
 
 
@@ -164,6 +165,14 @@ def allow_lazy(func, *resultclasses):
         return lazy(func, *resultclasses)(*args, **kwargs)
     return wrapper
 
+empty = object()
+def new_method_proxy(func):
+    def inner(self, *args):
+        if self._wrapped is empty:
+            self._setup()
+        return func(self._wrapped, *args)
+    return inner
+
 class LazyObject(object):
     """
     A wrapper for another class that can be used to delay instantiation of the
@@ -173,26 +182,23 @@ class LazyObject(object):
     instantiation. If you don't need to do that, use SimpleLazyObject.
     """
     def __init__(self):
-        self._wrapped = None
+        self._wrapped = empty
 
-    def __getattr__(self, name):
-        if self._wrapped is None:
-            self._setup()
-        return getattr(self._wrapped, name)
+    __getattr__ = new_method_proxy(getattr)
 
     def __setattr__(self, name, value):
         if name == "_wrapped":
             # Assign to __dict__ to avoid infinite __setattr__ loops.
             self.__dict__["_wrapped"] = value
         else:
-            if self._wrapped is None:
+            if self._wrapped is empty:
                 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:
+        if self._wrapped is empty:
             self._setup()
         delattr(self._wrapped, name)
 
@@ -204,11 +210,8 @@ class LazyObject(object):
 
     # introspection support:
     __members__ = property(lambda self: self.__dir__())
+    __dir__ = new_method_proxy(dir)
 
-    def __dir__(self):
-        if self._wrapped is None:
-            self._setup()
-        return dir(self._wrapped)
 
 class SimpleLazyObject(LazyObject):
     """
@@ -229,16 +232,14 @@ class SimpleLazyObject(LazyObject):
         self.__dict__['_setupfunc'] = func
         super(SimpleLazyObject, self).__init__()
 
-    def __str__(self):
-        if self._wrapped is None: self._setup()
-        return str(self._wrapped)
+    def _setup(self):
+        self._wrapped = self._setupfunc()
 
-    def __unicode__(self):
-        if self._wrapped is None: self._setup()
-        return unicode(self._wrapped)
+    __str__ = new_method_proxy(str)
+    __unicode__ = new_method_proxy(unicode)
 
     def __deepcopy__(self, memo):
-        if self._wrapped is None:
+        if self._wrapped is empty:
             # We have to use SimpleLazyObject, not self.__class__, because the
             # latter is proxied.
             result = SimpleLazyObject(self._setupfunc)
@@ -250,21 +251,10 @@ class SimpleLazyObject(LazyObject):
 
     # 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()
+    __class__ = property(new_method_proxy(operator.attrgetter("__class__")))
+    __eq__ = new_method_proxy(operator.eq)
+    __hash__ = new_method_proxy(hash)
+    __nonzero__ = new_method_proxy(bool)
 
 
 class lazy_property(property):
@@ -285,4 +275,4 @@ class lazy_property(property):
             @wraps(fdel)
             def fdel(instance, name=fdel.__name__):
                 return getattr(instance, name)()
-        return property(fget, fset, fdel, doc)
+        return property(fget, fset, fdel, doc)
\ No newline at end of file
diff --git a/tests/regressiontests/utils/simplelazyobject.py b/tests/regressiontests/utils/simplelazyobject.py
index e29729df60..e84752a5cc 100644
--- a/tests/regressiontests/utils/simplelazyobject.py
+++ b/tests/regressiontests/utils/simplelazyobject.py
@@ -1,7 +1,8 @@
 import copy
 import unittest
 
-from django.utils.functional import SimpleLazyObject
+from django.utils.functional import SimpleLazyObject, empty
+
 
 class _ComplexObject(object):
     def __init__(self, name):
@@ -65,13 +66,33 @@ class TestUtilsSimpleLazyObject(unittest.TestCase):
 
         # First, for an unevaluated SimpleLazyObject
         s = SimpleLazyObject(complex_object)
-        assert s._wrapped is None
+        self.assertIs(s._wrapped, empty)
         s2 = copy.deepcopy(s)
-        assert s._wrapped is None # something has gone wrong is s is evaluated
+        # something has gone wrong is s is evaluated
+        self.assertIs(s._wrapped, empty)
         self.assertEqual(s2, complex_object())
 
         # Second, for an evaluated SimpleLazyObject
         name = s.name # evaluate
-        assert s._wrapped is not None
+        self.assertIsNot(s._wrapped, empty)
         s3 = copy.deepcopy(s)
         self.assertEqual(s3, complex_object())
+
+
+    def test_none(self):
+        i = [0]
+        def f():
+            i[0] += 1
+            return None
+
+        x = SimpleLazyObject(f)
+        self.assertEqual(str(x), "None")
+        self.assertEqual(i, [1])
+        self.assertEqual(str(x), "None")
+        self.assertEqual(i, [1])
+
+    def test_bool(self):
+        x = SimpleLazyObject(lambda: 3)
+        self.assertTrue(x)
+        x = SimpleLazyObject(lambda: 0)
+        self.assertFalse(x)
\ No newline at end of file