Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add update_wrapper and fix wraps handing of missing attrs #251

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions documentation/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 25 additions & 6 deletions six.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,13 +808,33 @@ def print_(*args, **kwargs):
_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

Expand Down Expand Up @@ -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.
Expand Down
27 changes: 23 additions & 4 deletions test_six.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,14 +832,33 @@ def k():
def f(g, assign, update):
def w():
return 42
w.glue = {"foo" : "bar"}
w.glue = {"foo": "bar"}
w.xyzzy = {"qux": "quux"}
immerrr marked this conversation as resolved.
Show resolved Hide resolved
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():
Expand Down