public
Created

Patch, which solves lazy() issues

  • Download Gist
gistfile1.diff
Diff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
diff --git a/django/utils/functional.py b/django/utils/functional.py
index 661518e..9dcd325 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -1,9 +1,10 @@
+from django.utils import six
+from django.utils.decorators import available_attrs
+from functools import wraps, update_wrapper
import copy
import operator
-from functools import wraps, update_wrapper
import sys
-from django.utils import six
# You can't trivially replace this with `functools.partial` because this binds
# to classes and returns bound instances, whereas functools.partial (on
@@ -82,6 +83,8 @@ def lazy(func, *resultclasses):
def __prepare_class__(cls):
cls.__dispatch = {}
+ builtin_method_names = ['__hash__', '__str__']
+ builtin_methods = dict((methname, getattr(cls, methname)) for methname in builtin_method_names)
for resultclass in resultclasses:
cls.__dispatch[resultclass] = {}
for type_ in reversed(resultclass.mro()):
@@ -90,9 +93,14 @@ def lazy(func, *resultclasses):
# also do setup, inserting the method into the dispatch
# dict.
meth = cls.__promise__(resultclass, k, v)
- if hasattr(cls, k):
+ if hasattr(cls, k) and k not in builtin_methods or (k in builtin_methods and builtin_methods[k] == v):
continue
- setattr(cls, k, meth)
+ else:
+ meth = wraps(v)(meth, assigned=available_attrs(v))
+ if callable(v):
+ setattr(cls, k, meth)
+ else:
+ setattr(cls, k, v)
cls._delegate_bytes = bytes in resultclasses
cls._delegate_text = six.text_type in resultclasses
assert not (cls._delegate_bytes and cls._delegate_text), "Cannot call lazy() with both bytes and text return types."
@@ -157,8 +165,8 @@ def lazy(func, *resultclasses):
return bytes(self) % rhs
elif self._delegate_text:
return six.text_type(self) % rhs
- else:
- raise AssertionError('__mod__ not supported for non-string types')
+ return self.__cast() % rhs
+ #raise AssertionError('__mod__ not supported for non-string types')
def __deepcopy__(self, memo):
# Instances of this class are effectively immutable. It's just a
diff --git a/tests/regressiontests/utils/functional.py b/tests/regressiontests/utils/functional.py
index 90a6f08..a27aef3 100644
--- a/tests/regressiontests/utils/functional.py
+++ b/tests/regressiontests/utils/functional.py
@@ -1,5 +1,6 @@
from django.utils import unittest
from django.utils.functional import lazy, lazy_property
+from django.utils import six
class FunctionalTestCase(unittest.TestCase):
@@ -37,3 +38,53 @@ class FunctionalTestCase(unittest.TestCase):
self.assertRaises(NotImplementedError, lambda: A().do)
self.assertEqual(B().do, 'DO IT')
+
+ def test_lazy_magicmethods_mod(self):
+ class A(six.text_type):
+ def __mod__(self, rhs):
+ return 'Result: ' + super(A, self).__mod__(rhs)
+
+ t = lazy(lambda: A('Hello %(word)s'), A)()
+ self.assertEqual(t % {'word': 'world'}, 'Result: Hello world')
+
+ def test_lazy_magicmethods_hash(self):
+ class NotAHashable(object):
+ __hash__ = None
+
+ notahash = NotAHashable()
+ notahash_lazy = lazy(lambda: NotAHashable(), NotAHashable)()
+ self.assertRaises(TypeError, lambda: {notahash: 1})
+ self.assertRaises(TypeError, lambda: {notahash_lazy: 2})
+
+ def test_lazy_magicmethods_str(self):
+ class SomeObjectWithString(object):
+ def __str__(self):
+ return b'Byte string'
+ lazy_str_obj = lazy(lambda: SomeObjectWithString(), SomeObjectWithString)()
+ self.assertEqual(str(lazy_str_obj), b'Byte string')
+
+ def test_lazy_losing_docstring(self):
+ class A(object):
+ def hello(self):
+ '''Docstring'''
+ return 'Hello world'
+ lazy_a = lazy(lambda: A(), A)()
+ self.assertEqual(lazy_a.hello.__doc__, 'Docstring')
+
+ def test_lazy_attributes(self):
+ class A(object):
+ counter = 15
+
+ lazy_a = lazy(lambda: A(), A)()
+ self.assertEqual(lazy_a.counter, 15)
+ lazy_a.counter = 16
+ self.assertEqual(lazy_a.counter, 16)
+
+ class B(object):
+ @property
+ def counter(self):
+ return 15
+
+ lazy_b = lazy(lambda: B(), B)()
+ self.assertEqual(lazy_b.counter, 15)
+

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.