Skip to content
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
3 changes: 2 additions & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
# W0108 => unnecessary lambda
# W0142 => Used * or ** magic
# R0401 => cyclic-import
disable=I, C0111, W0108, W0142, R0921, R0922, W0232, R0401, R0801
# R0204 => redefined-variable-type
disable=I, C0111, W0108, W0142, R0921, R0922, W0232, R0401, R0801, R0204


[REPORTS]
Expand Down
58 changes: 53 additions & 5 deletions boxsdk/util/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from datetime import timedelta

import six
from six.moves import map # pylint:disable=redefined-builtin


if not hasattr(timedelta, 'total_seconds'):
Expand Down Expand Up @@ -59,19 +60,66 @@ class Subclass(temporary_class):
``bases``, then errors might occur. For example, this was a problem when
used with ``enum.EnumMeta`` in Python 3.6. Here we make sure that
``__prepare__()`` is defined on the temporary metaclass, and pass ``bases``
to ``meta.__prepare__()``.
to ``meta.__prepare__()``. This is fixed in six>=1.11.0 by PR #178 [1].

Since ``temporary_class`` doesn't have the correct bases, in theory this
could cause other problems, besides the previous one, in certain edge
cases. To make sure that doesn't become a problem, we make sure that
``temporary_class`` has ``bases`` as its bases, just like the final class.

[1] <https://github.com/benjaminp/six/pull/178>
"""
temporary_class = six.with_metaclass(meta, *bases, **with_metaclass_kwargs)
temporary_metaclass = type(temporary_class)

class TemporaryMetaSubclass(temporary_metaclass):
@classmethod
def __prepare__(cls, name, this_bases, **kwds): # pylint:disable=unused-argument
return meta.__prepare__(name, bases, **kwds)
class TemporaryMetaSubclass(temporary_metaclass, _most_derived_metaclass(meta, bases)):

if '__prepare__' not in temporary_metaclass.__dict__:
# six<1.11.0, __prepare__ is not defined on the temporary metaclass.

@classmethod
def __prepare__(mcs, name, this_bases, **kwds): # pylint:disable=unused-argument,arguments-differ,bad-classmethod-argument
return meta.__prepare__(name, bases, **kwds)

return type.__new__(TemporaryMetaSubclass, str('temporary_class'), bases, {})


def raise_from(value, _): # pylint:disable=unused-argument
"""Fallback for six.raise_from(), when using six<1.9.0."""
raise value


raise_from = getattr(six, 'raise_from', raise_from) # pylint:disable=invalid-name


def _most_derived_metaclass(meta, bases):
"""Selects the most derived metaclass of all the given metaclasses.

This will be the same metaclass that is selected by

.. code-block:: python

class temporary_class(*bases, metaclass=meta): pass

or equivalently by

.. code-block:: python

types.prepare_class('temporary_class', bases, metaclass=meta)

"Most derived" means the item in {meta, type(bases[0]), type(bases[1]), ...}
which is a non-strict subclass of every item in that set.

If no such item exists, then :exc:`TypeError` is raised.

:type meta: `type`
:type bases: :class:`Iterable` of `type`
"""
most_derived_metaclass = meta
for base_type in map(type, bases):
if issubclass(base_type, most_derived_metaclass):
most_derived_metaclass = base_type
elif not issubclass(most_derived_metaclass, base_type):
# Raises TypeError('metaclass conflict: ...')
return type.__new__(meta, str('temporary_class'), bases, {})
return most_derived_metaclass
27 changes: 26 additions & 1 deletion test/unit/util/test_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import unicode_literals
from datetime import datetime, timedelta
import pytest
from boxsdk.util.compat import total_seconds, with_metaclass
from boxsdk.util.compat import raise_from, total_seconds, with_metaclass


@pytest.fixture(params=(
Expand Down Expand Up @@ -50,3 +50,28 @@ class Subclass(temporary_class):

assert type(Subclass) is Meta # pylint:disable=unidiomatic-typecheck
assert Subclass.__bases__ == bases


class MyError1(Exception):
pass


class MyError2(Exception):
pass


class MyError3(Exception):
pass


@pytest.mark.parametrize('custom_context', [None, False, True])
def test_raise_from(custom_context):
try:
raise MyError1
except MyError1 as context:
if custom_context is False:
custom_context = context
elif custom_context is True:
custom_context = MyError2()
with pytest.raises(MyError3):
raise_from(MyError3(), custom_context)