From 670049c26212ed403714feed17e3ff6ea43c1c65 Mon Sep 17 00:00:00 2001 From: Leo Huckvale Date: Thu, 14 Jul 2016 14:04:33 +0100 Subject: [PATCH] inspect: return defargs[0] if obj.__dict__ raises exception The fallback implemented in #2731 cannot return `obj.__dict__[name]` if the `__dict__` method has been redefined in such a way that it raises an exception when trying to access it. This commit adds a try-except block to work around this. --- sphinx/util/inspect.py | 13 ++++++++- tests/test_util_inspect.py | 58 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 tests/test_util_inspect.py diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 1c6e4e151..91ae83d83 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -110,12 +110,23 @@ def safe_getattr(obj, name, *defargs): except Exception: # sometimes accessing a property raises an exception (e.g. # NotImplementedError), so let's try to read the attribute directly - if name in obj.__dict__: + + # We should also be aware that if `__dict__` has been overridden, + # this may also raise an exception. + + try: + obj_dict = obj.__dict__ + except Exception as exc: + obj_dict = {} + + if name in obj_dict: return obj.__dict__[name] + # this is a catch-all for all the weird things that some modules do # with attribute access if defargs: return defargs[0] + raise AttributeError(name) diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py new file mode 100644 index 000000000..0d3d8b7d5 --- /dev/null +++ b/tests/test_util_inspect.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +""" + test_util_inspect + ~~~~~~~~~~~~~~~ + + Tests util.inspect functions. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" +from unittest import TestCase + +from sphinx.util import inspect + + +class TestSafeGetAttr(TestCase): + def test_safe_getattr_with_default(self): + class Foo(object): + def __getattr__(self, item): + raise Exception + + obj = Foo() + + result = inspect.safe_getattr(obj, 'bar', 'baz') + + assert result == 'baz' + + def test_safe_getattr_with_exception(self): + class Foo(object): + def __getattr__(self, item): + raise Exception + + obj = Foo() + + with self.assertRaisesRegexp(AttributeError, 'bar'): + inspect.safe_getattr(obj, 'bar') + + def test_safe_getattr_with_property_exception(self): + class Foo(object): + @property + def bar(self): + raise Exception + + obj = Foo() + + with self.assertRaisesRegexp(AttributeError, 'bar'): + inspect.safe_getattr(obj, 'bar') + + def test_safe_getattr_with___dict___override(self): + class Foo(object): + @property + def __dict__(self): + raise Exception + + obj = Foo() + + with self.assertRaisesRegexp(AttributeError, 'bar'): + inspect.safe_getattr(obj, 'bar')