Skip to content

Commit

Permalink
Merge branch 'release/1.12.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
GrahamDumpleton committed Mar 9, 2020
2 parents 0148e39 + e7e7a14 commit 68316be
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 59 deletions.
12 changes: 12 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
Release Notes
=============

Version 1.12.1
--------------

**Bugs Fixed**

* Applying a function wrapper to a static method of a class using the
``wrap_function_wrapper()`` function, or wrapper for the same, wasn't
being done correctly when the static method was the immediate child of
the target object. It was working when the name path had multiple name
components. A failure would subsequently occur when the static method
was called via an instance of the class, rather than the class.

Version 1.12.0
--------------

Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
# The short X.Y version.
version = '1.12'
# The full version, including alpha/beta/rc tags.
release = '1.12.0'
release = '1.12.1'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def build_extension(self, ext):

setup_kwargs = dict(
name='wrapt',
version='1.12.0',
version='1.12.1',
description='Module for decorators, wrappers and monkey patching.',
long_description=open('README.rst').read(),
author='Graham Dumpleton',
Expand Down
2 changes: 1 addition & 1 deletion src/wrapt/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version_info__ = ('1', '12', '0')
__version_info__ = ('1', '12', '1')
__version__ = '.'.join(__version_info__)

from .wrappers import (ObjectProxy, CallableObjectProxy, FunctionWrapper,
Expand Down
18 changes: 9 additions & 9 deletions src/wrapt/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,8 @@
import sys

PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3

if PY3:
string_types = str,

import builtins
exec_ = getattr(builtins, "exec")
del builtins

else:
if PY2:
string_types = basestring,

def exec_(_code_, _globs_=None, _locs_=None):
Expand All @@ -30,6 +22,14 @@ def exec_(_code_, _globs_=None, _locs_=None):
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")

else:
string_types = str,

import builtins

exec_ = getattr(builtins, "exec")
del builtins

from functools import partial
from inspect import ismethod, isclass, formatargspec
from collections import namedtuple
Expand Down
34 changes: 17 additions & 17 deletions src/wrapt/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@
import threading

PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3

if PY3:
if PY2:
string_types = basestring,
else:
import importlib
string_types = str,
else:
string_types = basestring,

from .decorators import synchronized

Expand Down Expand Up @@ -188,7 +187,20 @@ def find_module(self, fullname, path=None):
# Now call back into the import system again.

try:
if PY3:
if PY2:
# For Python 2 we don't have much choice but to
# call back in to __import__(). This will
# actually cause the module to be imported. If no
# module could be found then ImportError will be
# raised. Otherwise we return a loader which
# returns the already loaded module and invokes
# the post import hooks.

__import__(fullname)

return _ImportHookLoader()

else:
# For Python 3 we need to use find_spec().loader
# from the importlib.util module. It doesn't actually
# import the target module and only finds the
Expand All @@ -204,18 +216,6 @@ def find_module(self, fullname, path=None):
if loader:
return _ImportHookChainedLoader(loader)

else:
# For Python 2 we don't have much choice but to
# call back in to __import__(). This will
# actually cause the module to be imported. If no
# module could be found then ImportError will be
# raised. Otherwise we return a loader which
# returns the already loaded module and invokes
# the post import hooks.

__import__(fullname)

return _ImportHookLoader()

finally:
del self.in_progress[fullname]
Expand Down
60 changes: 30 additions & 30 deletions src/wrapt/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
import inspect

PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3

if PY3:
string_types = str,
else:
if PY2:
string_types = basestring,
else:
string_types = str,

def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
Expand Down Expand Up @@ -117,7 +116,7 @@ def __dir__(self):
def __str__(self):
return str(self.__wrapped__)

if PY3:
if not PY2:
def __bytes__(self):
return bytes(self.__wrapped__)

Expand All @@ -130,7 +129,7 @@ def __repr__(self):
def __reversed__(self):
return reversed(self.__wrapped__)

if PY3:
if not PY2:
def __round__(self):
return round(self.__wrapped__)

Expand Down Expand Up @@ -740,33 +739,34 @@ def resolve_path(module, name):
path = name.split('.')
attribute = path[0]

original = getattr(parent, attribute)
for attribute in path[1:]:
parent = original

# We can't just always use getattr() because in doing
# that on a class it will cause binding to occur which
# will complicate things later and cause some things not
# to work. For the case of a class we therefore access
# the __dict__ directly. To cope though with the wrong
# class being given to us, or a method being moved into
# a base class, we need to walk the class hierarchy to
# work out exactly which __dict__ the method was defined
# in, as accessing it from __dict__ will fail if it was
# not actually on the class given. Fallback to using
# getattr() if we can't find it. If it truly doesn't
# exist, then that will fail.

if inspect.isclass(original):
for cls in inspect.getmro(original):
# We can't just always use getattr() because in doing
# that on a class it will cause binding to occur which
# will complicate things later and cause some things not
# to work. For the case of a class we therefore access
# the __dict__ directly. To cope though with the wrong
# class being given to us, or a method being moved into
# a base class, we need to walk the class hierarchy to
# work out exactly which __dict__ the method was defined
# in, as accessing it from __dict__ will fail if it was
# not actually on the class given. Fallback to using
# getattr() if we can't find it. If it truly doesn't
# exist, then that will fail.

def lookup_attribute(parent, attribute):
if inspect.isclass(parent):
for cls in inspect.getmro(parent):
if attribute in vars(cls):
original = vars(cls)[attribute]
break
return vars(cls)[attribute]
else:
original = getattr(original, attribute)

return getattr(parent, attribute)
else:
original = getattr(original, attribute)
return getattr(parent, attribute)

original = lookup_attribute(parent, attribute)

for attribute in path[1:]:
parent = original
original = lookup_attribute(parent, attribute)

return (parent, attribute, original)

Expand Down
50 changes: 50 additions & 0 deletions tests/test_inner_staticmethod.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,5 +230,55 @@ def _function(*args, **kwargs):

self.assertEqual(result, (_args, _kwargs))

def test_class_externally_applied_wrapper(self):
# Test calling staticmethod via class instance when
# the decorator has been applied from external to
# the class using wrapping function.

_args = (1, 2)
_kwargs = {'one': 1, 'two': 2}

def _decorator(wrapped, instance, args, kwargs):
self.assertEqual(instance, None)
self.assertEqual(args, _args)
self.assertEqual(kwargs, _kwargs)
return wrapped(*args, **kwargs)

class Class(object):
@staticmethod
def _function(*args, **kwargs):
return (args, kwargs)

wrapt.wrap_function_wrapper(Class, "_function", _decorator)

result = Class._function(*_args, **_kwargs)

self.assertEqual(result, (_args, _kwargs))

def test_instance_externally_applied_wrapper(self):
# Test calling staticmethod via class instance when
# the decorator has been applied from external to
# the class using wrapping function.

_args = (1, 2)
_kwargs = {'one': 1, 'two': 2}

def _decorator(wrapped, instance, args, kwargs):
self.assertEqual(instance, None)
self.assertEqual(args, _args)
self.assertEqual(kwargs, _kwargs)
return wrapped(*args, **kwargs)

class Class(object):
@staticmethod
def _function(*args, **kwargs):
return (args, kwargs)

wrapt.wrap_function_wrapper(Class, "_function", _decorator)

result = Class()._function(*_args, **_kwargs)

self.assertEqual(result, (_args, _kwargs))

if __name__ == '__main__':
unittest.main()

0 comments on commit 68316be

Please sign in to comment.