Skip to content

Commit

Permalink
Fix issues when using keyword argument named self with function wrapp…
Browse files Browse the repository at this point in the history
…ers and decorators.
  • Loading branch information
GrahamDumpleton committed Jan 12, 2023
1 parent ee6bab6 commit 4e09f7a
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 6 deletions.
18 changes: 14 additions & 4 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,27 @@ Version 1.15.0
thread and code executed in the context of the new thread itself tried to
register a post import hook, or imported a new module.

* When using `CallableObjectProxy` as a wrapper for a type or function and
* When using ``CallableObjectProxy`` as a wrapper for a type or function and
calling the wrapped object, it was not possible to pass a keyword argument
named ``self``. This only occurred when using the pure Python version of
wrapt and did not occur when using the C extension based implementation.
named ``self``. This only occurred when using the pure Python version of wrapt
and did not occur when using the C extension based implementation.

* When using `PartialCallableObjectProxy` as a wrapper for a type or function,
* When using ``PartialCallableObjectProxy`` as a wrapper for a type or function,
when constructing the partial object and when calling the partial object, it
was not possible to pass a keyword argument named ``self``. This only occurred
when using the pure Python version of wrapt and did not occur when using the C
extension based implementation.

* When using ``FunctionWrapper`` as a wrapper for a type or function and calling
the wrapped object, it was not possible to pass a keyword argument named
``self``. Because ``FunctionWrapper`` is also used by decorators, this also
affected decorators on functions and class types. A similar issue also arose
when these were applied to class and instance methods where binding occurred
when the method was accessed. In that case it was in ``BoundFunctionWrapper``
that the problem could arise. These all only occurred when using the pure
Python version of wrapt and did not occur when using the C extension based
implementation.

Version 1.14.1
--------------

Expand Down
14 changes: 12 additions & 2 deletions src/wrapt/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,12 @@ def __get__(self, instance, owner):

return self

def __call__(self, *args, **kwargs):
def __call__(*args, **kwargs):
def _unpack_self(self, *args):
return self, args

self, args = _unpack_self(*args)

# If enabled has been specified, then evaluate it at this point
# and if the wrapper is not to be executed, then simply return
# the bound function rather than a bound wrapper for the bound
Expand Down Expand Up @@ -622,7 +627,12 @@ def __subclasscheck__(self, subclass):

class BoundFunctionWrapper(_FunctionWrapperBase):

def __call__(self, *args, **kwargs):
def __call__(*args, **kwargs):
def _unpack_self(self, *args):
return self, args

self, args = _unpack_self(*args)

# If enabled has been specified, then evaluate it at this point
# and if the wrapper is not to be executed, then simply return
# the bound function rather than a bound wrapper for the bound
Expand Down
218 changes: 218 additions & 0 deletions tests/test_object_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2199,5 +2199,223 @@ def __init__(_self, self, *args, **kwargs):

self.assertNotEqual(re.match(".*got multiple values for (keyword )?argument '_self'.*", str(e.exception)), None)

class TestArgumentUnpackingWrapperBase(unittest.TestCase):

def test_self_keyword_argument_on_dict(self):
# A dict when given self as keyword argument uses it to create item in
# the dict and no attempt is made to use a positional argument.

def wrapper(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)

d = wrapt.wrappers.FunctionWrapper(dict, wrapper)(self='self')

self.assertEqual(d, dict(self='self'))

def test_self_positional_argument_on_class_init(self):
def wrapper(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)

class Object:
def __init__(self, *args, **kwargs):
self._args = args
self._kwargs = kwargs

o = Object('arg1')

self.assertEqual(o._args, ('arg1',))
self.assertEqual(o._kwargs, {})

o = wrapt.wrappers.FunctionWrapper(Object, wrapper)('arg1')

self.assertEqual(o._args, ('arg1',))
self.assertEqual(o._kwargs, {})

def test_self_keyword_argument_on_class_init_1(self):
def wrapper(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)

class Object:
def __init__(self, *args, **kwargs):
self._args = args
self._kwargs = kwargs

with self.assertRaises(TypeError) as e:
Object(self='self')

self.assertNotEqual(re.match(".*got multiple values for (keyword )?argument 'self'.*", str(e.exception)), None)

with self.assertRaises(TypeError) as e:
wrapt.wrappers.FunctionWrapper(Object, wrapper)(self='self')

self.assertNotEqual(re.match(".*got multiple values for (keyword )?argument 'self'.*", str(e.exception)), None)

def test_self_keyword_argument_on_class_init_2(self):
def wrapper(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)

class Object:
def __init__(self, *args, **kwargs):
self._args = args
self._kwargs = kwargs

with self.assertRaises(TypeError) as e:
Object(arg1='arg1', self='self')

self.assertNotEqual(re.match(".*got multiple values for (keyword )?argument 'self'.*", str(e.exception)), None)

with self.assertRaises(TypeError) as e:
wrapt.wrappers.FunctionWrapper(Object, wrapper)(arg1='arg1', self='self')

self.assertNotEqual(re.match(".*got multiple values for (keyword )?argument 'self'.*", str(e.exception)), None)

def test_self_keyword_argument_on_class_init_renamed(self):
def wrapper(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)

class Object:
def __init__(_self, *args, **kwargs):
_self._args = args
_self._kwargs = kwargs

o = Object(self='self')

self.assertEqual(o._args, ())
self.assertEqual(o._kwargs, dict(self="self"))

o = wrapt.wrappers.FunctionWrapper(Object, wrapper)(self='self')

self.assertEqual(o._args, ())
self.assertEqual(o._kwargs, dict(self="self"))

def test_self_keyword_argument_on_class_init_overloaded_1(self):
def wrapper(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)

class Object:
def __init__(_self, self, *args, **kwargs):
_self._self = self
_self._args = args
_self._kwargs = kwargs

o = Object(self='self')

self.assertEqual(o._args, ())
self.assertEqual(o._kwargs, {})
self.assertEqual(o._self, 'self')

o = wrapt.wrappers.FunctionWrapper(Object, wrapper)(self='self')

self.assertEqual(o._args, ())
self.assertEqual(o._kwargs, {})
self.assertEqual(o._self, 'self')

def test_self_keyword_argument_on_class_init_overloaded_2(self):
def wrapper(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)

class Object:
def __init__(_self, self, *args, **kwargs):
_self._self = self
_self._args = args
_self._kwargs = kwargs

with self.assertRaises(TypeError) as e:
Object(_self='self')

self.assertNotEqual(re.match(".*got multiple values for (keyword )?argument '_self'.*", str(e.exception)), None)

with self.assertRaises(TypeError) as e:
wrapt.wrappers.FunctionWrapper(Object, wrapper)(_self='self')

self.assertNotEqual(re.match(".*got multiple values for (keyword )?argument '_self'.*", str(e.exception)), None)

class TestArgumentUnpackingBoundFunctionWrapper(unittest.TestCase):

def test_self_keyword_argument_on_classmethod(self):
def wrapper(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)

class Object:
@classmethod
def function(cls, self, *args, **kwargs):
return self, args, kwargs

function = wrapt.wrappers.FunctionWrapper(function, wrapper)

result = Object().function(self='self')

self.assertEqual(result, ('self', (), {}))

def test_self_keyword_argument_on_instancemethod(self):
def wrapper(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)

class Object:
def function(_self, self, *args, **kwargs):
return self, args, kwargs

function = wrapt.wrappers.FunctionWrapper(function, wrapper)

result = Object().function(self='self')

self.assertEqual(result, ('self', (), {}))

class TestArgumentUnpackingDecorator(unittest.TestCase):

def test_self_keyword_argument_on_function(self):
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)

@wrapper
def function(self, *args, **kwargs):
return self, args, kwargs

result = function(self='self')

self.assertEqual(result, ('self', (), {}))

result = function('self')

self.assertEqual(result, ('self', (), {}))

def test_self_keyword_argument_on_classmethod(self):
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)

class Object:
@wrapper
@classmethod
def function(cls, self, *args, **kwargs):
return self, args, kwargs

result = Object().function(self='self')

self.assertEqual(result, ('self', (), {}))

result = Object().function('self', arg1='arg1')

self.assertEqual(result, ('self', (), dict(arg1='arg1')))

def test_self_keyword_argument_on_instancemethod(self):
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)

class Object:
@wrapper
def function(_self, self, *args, **kwargs):
return self, args, kwargs

result = Object().function(self='self', arg1='arg1')

self.assertEqual(result, ('self', (), dict(arg1='arg1')))

result = Object().function('self', arg1='arg1')

self.assertEqual(result, ('self', (), dict(arg1='arg1')))

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

0 comments on commit 4e09f7a

Please sign in to comment.