Skip to content

Commit

Permalink
Use mro lookup to avoid need for __wrapped__ in inheritance for Pytho…
Browse files Browse the repository at this point in the history
…n 3.7+.
  • Loading branch information
GrahamDumpleton committed Dec 2, 2019
1 parent cc6fbfc commit efa02c6
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 1 deletion.
10 changes: 10 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
Release Notes
=============

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

**Features Changed**

* Provided that you only want to support Python 3.7, when deriving from
a base class which has a decorator applied to it, you no longer need
to access the true type of the base class using ``__wrapped__`` in
the inherited class list of the derived class.

Version 1.11.2
--------------

Expand Down
4 changes: 4 additions & 0 deletions docs/issues.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,7 @@ decorator, the decorator would need to be applied to the derived class also.
In this case of trying to decorate a base class in a class hierarchy, it
may turn out to be more appropriate to use a meta class instead of trying
to decorate the base class.

Note that as of Python 3.7 and wrapt 1.12.0, accessing the true type of the
base class using ``__wrapped__`` is not required. Such code though will not
work for versions of Python older than Python 3.7.
17 changes: 17 additions & 0 deletions src/wrapt/_wrappers.c
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,19 @@ static PyObject *WraptObjectProxy_complex(

/* ------------------------------------------------------------------------- */

static PyObject *WraptObjectProxy_mro_entries(
WraptObjectProxyObject *self, PyObject *args, PyObject *kwds)
{
if (!self->wrapped) {
PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized");
return NULL;
}

return Py_BuildValue("(O)", self->wrapped);
}

/* ------------------------------------------------------------------------- */

static PyObject *WraptObjectProxy_get_name(
WraptObjectProxyObject *self)
{
Expand Down Expand Up @@ -1746,6 +1759,10 @@ static PyMethodDef WraptObjectProxy_methods[] = {
{ "__round__", (PyCFunction)WraptObjectProxy_round, METH_NOARGS, 0 },
#endif
{ "__complex__", (PyCFunction)WraptObjectProxy_complex, METH_NOARGS, 0 },
#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 7)
{ "__mro_entries__", (PyCFunction)WraptObjectProxy_mro_entries,
METH_VARARGS | METH_KEYWORDS, 0 },
#endif
{ NULL, NULL },
};

Expand Down
4 changes: 4 additions & 0 deletions src/wrapt/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ def __reversed__(self):
def __round__(self):
return round(self.__wrapped__)

if sys.hexversion >= 0x03070000:
def __mro_entries__(self, bases):
return (self.__wrapped__,)

def __lt__(self, other):
return self.__wrapped__ < other

Expand Down
2 changes: 2 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ def collect(self):
def pytest_pycollect_makemodule(path, parent):
if '_py33' in path.basename and version < (3, 3):
return DummyCollector(path, parent=parent)
if '_py37' in path.basename and version < (3, 7):
return DummyCollector(path, parent=parent)
if '_py3' in path.basename and version < (3, 0):
return DummyCollector(path, parent=parent)
if '_py2' in path.basename and version >= (3, 0):
Expand Down
36 changes: 35 additions & 1 deletion tests/test_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,42 @@ class class1(object):

class1d = decorators.passthru_decorator(class1)

class Testintrospection(unittest.TestCase):
class TestIntrospection(unittest.TestCase):

def test_getmembers(self):
class1o_members = inspect.getmembers(class1o)
class1d_members = inspect.getmembers(class1d)

class TestInheritance(unittest.TestCase):

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

@passthru
class BaseClass(object):
def __init__(self):
self.value = 1

class DerivedClass(BaseClass.__wrapped__):
def __init__(self):
super(DerivedClass, self).__init__()
self.value = 2

base = BaseClass()

self.assertEqual(type(base), BaseClass.__wrapped__)
self.assertTrue(isinstance(base, BaseClass.__wrapped__))
self.assertEqual(base.value, 1)

self.assertEqual(type(base).__mro__, (BaseClass.__wrapped__, object))

derived = DerivedClass()

self.assertEqual(type(derived), DerivedClass)
self.assertTrue(isinstance(derived, BaseClass.__wrapped__))
self.assertTrue(isinstance(derived, DerivedClass))
self.assertEqual(derived.value, 2)

self.assertEqual(type(derived).__mro__, (DerivedClass, BaseClass.__wrapped__, object))
102 changes: 102 additions & 0 deletions tests/test_class_py37.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from __future__ import print_function

import unittest
import inspect
import imp

import wrapt

from compat import PY2, PY3, exec_

class TestInheritance(unittest.TestCase):

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

@passthru
class BaseClass(object):
def __init__(self):
self.value = 1

class DerivedClass(BaseClass):
def __init__(self):
super(DerivedClass, self).__init__()
self.value = 2

base = BaseClass()

self.assertEqual(type(base), BaseClass.__wrapped__)
self.assertTrue(isinstance(base, BaseClass.__wrapped__))
self.assertEqual(base.value, 1)

self.assertEqual(type(base).__mro__, (BaseClass.__wrapped__, object))
self.assertEqual(BaseClass.__mro_entries__(()), (BaseClass.__wrapped__,))

derived = DerivedClass()

self.assertEqual(type(derived), DerivedClass)
self.assertTrue(isinstance(derived, BaseClass.__wrapped__))
self.assertTrue(isinstance(derived, DerivedClass))
self.assertEqual(derived.value, 2)

self.assertEqual(type(derived).__mro__, (DerivedClass, BaseClass.__wrapped__, object))

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

@passthru
class BaseClass1(object):
pass

@passthru
class BaseClass2(object):
pass

class DerivedClass(BaseClass1, BaseClass2):
pass

derived = DerivedClass()

self.assertEqual(type(derived), DerivedClass)
self.assertTrue(isinstance(derived, BaseClass1.__wrapped__))
self.assertTrue(isinstance(derived, BaseClass2.__wrapped__))
self.assertTrue(isinstance(derived, DerivedClass))

self.assertEqual(type(derived).__mro__, (DerivedClass,
BaseClass1.__wrapped__, BaseClass2.__wrapped__, object))

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

@passthru
class CommonClass(object):
pass

@passthru
class BaseClass1(CommonClass):
pass

@passthru
class BaseClass2(CommonClass):
pass

class DerivedClass(BaseClass1, BaseClass2):
pass

derived = DerivedClass()

self.assertEqual(type(derived), DerivedClass)
self.assertTrue(isinstance(derived, CommonClass.__wrapped__))
self.assertTrue(isinstance(derived, BaseClass1.__wrapped__))
self.assertTrue(isinstance(derived, BaseClass2.__wrapped__))
self.assertTrue(isinstance(derived, DerivedClass))

self.assertEqual(type(derived).__mro__, (DerivedClass,
BaseClass1.__wrapped__, BaseClass2.__wrapped__,
CommonClass.__wrapped__, object))

0 comments on commit efa02c6

Please sign in to comment.