Created
January 23, 2013 12:35
-
-
Save uruz/4605065 to your computer and use it in GitHub Desktop.
Patch, which solves lazy() issues
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
+ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment