diff options
author | immerrr again <immerrr@gmail.com> | 2020-01-07 03:55:42 +0100 |
---|---|---|
committer | Benjamin Peterson <benjamin@python.org> | 2020-01-06 18:55:42 -0800 |
commit | 1988faf3f7a7f77c0eccbbc5192f365400d44deb (patch) | |
tree | cbb35c98e90da9dcd8f319b16c4ffe607d2c2a50 | |
parent | a4d9af96b122e98b7be3649b6545e0f6a04c8335 (diff) | |
download | six-git-1988faf3f7a7f77c0eccbbc5192f365400d44deb.tar.gz |
Fix wraps handing of missing attrs. (#251)
This is pretty-much a straight backport of Py3 implementations of update_wrapper and (privately) wraps.
Fixes #250
Fixes #165
Co-authored-by: Benjamin Peterson <benjamin@python.org>
-rw-r--r-- | documentation/index.rst | 7 | ||||
-rw-r--r-- | six.py | 31 | ||||
-rw-r--r-- | test_six.py | 27 |
3 files changed, 52 insertions, 13 deletions
diff --git a/documentation/index.rst b/documentation/index.rst index b7ec275..f4c90c9 100644 --- a/documentation/index.rst +++ b/documentation/index.rst @@ -257,9 +257,10 @@ functions and methods is the stdlib :mod:`py3:inspect` module. .. decorator:: wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, updated=functools.WRAPPER_UPDATES) - This is exactly the :func:`py3:functools.wraps` decorator, but it sets the - ``__wrapped__`` attribute on what it decorates as :func:`py3:functools.wraps` - does on Python versions after 3.2. + This is Python 3.2's :func:`py3:functools.wraps` decorator. It sets the + ``__wrapped__`` attribute on what it decorates. It doesn't raise an error if + any of the attributes mentioned in ``assigned`` and ``updated`` are missing + on ``wrapped`` object. Syntax compatibility @@ -808,13 +808,33 @@ if sys.version_info[:2] < (3, 3): _add_doc(reraise, """Reraise an exception.""") if sys.version_info[0:2] < (3, 4): + # This does exactly the same what the :func:`py3:functools.update_wrapper` + # function does on Python versions after 3.2. It sets the ``__wrapped__`` + # attribute on ``wrapper`` object and it doesn't raise an error if any of + # the attributes mentioned in ``assigned`` and ``updated`` are missing on + # ``wrapped`` object. + def _update_wrapper(wrapper, wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + continue + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr, {})) + wrapper.__wrapped__ = wrapped + return wrapper + _update_wrapper.__doc__ = functools.update_wrapper.__doc__ + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, updated=functools.WRAPPER_UPDATES): - def wrapper(f): - f = functools.wraps(wrapped, assigned, updated)(f) - f.__wrapped__ = wrapped - return f - return wrapper + return functools.partial(_update_wrapper, wrapped=wrapped, + assigned=assigned, updated=updated) + wraps.__doc__ = functools.wraps.__doc__ + else: wraps = functools.wraps @@ -919,7 +939,6 @@ def ensure_text(s, encoding='utf-8', errors='strict'): raise TypeError("not expecting type '%s'" % type(s)) - def python_2_unicode_compatible(klass): """ A class decorator that defines __unicode__ and __str__ methods under Python 2. diff --git a/test_six.py b/test_six.py index 8ada517..ab99ce9 100644 --- a/test_six.py +++ b/test_six.py @@ -832,14 +832,33 @@ def test_wraps(): def f(g, assign, update): def w(): return 42 - w.glue = {"foo" : "bar"} + w.glue = {"foo": "bar"} + w.xyzzy = {"qux": "quux"} return six.wraps(g, assign, update)(w) - k.glue = {"melon" : "egg"} + k.glue = {"melon": "egg"} k.turnip = 43 - k = f(k, ["turnip"], ["glue"]) + k = f(k, ["turnip", "baz"], ["glue", "xyzzy"]) assert k.__name__ == "w" assert k.turnip == 43 - assert k.glue == {"melon" : "egg", "foo" : "bar"} + assert not hasattr(k, "baz") + assert k.glue == {"melon": "egg", "foo": "bar"} + assert k.xyzzy == {"qux": "quux"} + + +def test_wraps_raises_on_missing_updated_field_on_wrapper(): + """Ensure six.wraps doesn't ignore missing attrs wrapper. + + Because that's what happens in Py3's functools.update_wrapper. + """ + def wrapped(): + pass + + def wrapper(): + pass + + with pytest.raises(AttributeError, match='has no attribute.*xyzzy'): + six.wraps(wrapped, [], ['xyzzy'])(wrapper) + def test_add_metaclass(): |