Skip to content

Commit

Permalink
Merge pull request #26 from zopefoundation/issue25
Browse files Browse the repository at this point in the history
Avoid AttributeError splitting qualnames in getPythonPath.
  • Loading branch information
jamadden committed Aug 17, 2018
2 parents 84be629 + d7378ea commit 426115d
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 5 deletions.
7 changes: 6 additions & 1 deletion CHANGES.rst
Expand Up @@ -39,7 +39,12 @@
See `PR #15 <https://github.com/zopefoundation/zope.app.apidoc/pull/15/>`_.

- ``__main__.py`` files are no longer imported by the code documentation module.
See `issue #22 <https://github.com/zopefoundation/zope.app.apidoc/issues/22>`_.
See `issue #22
<https://github.com/zopefoundation/zope.app.apidoc/issues/22>`_.

- Cython functions registered as adapters on Python 2 no longer break
page generation with an ``AttributeError``. See `issue 25
<https://github.com/zopefoundation/zope.app.apidoc/issues/25>`_.

4.0.0 (2017-05-25)
==================
Expand Down
49 changes: 49 additions & 0 deletions src/zope/app/apidoc/tests.py
Expand Up @@ -244,6 +244,55 @@ def test_renderText_non_text(self):
text = renderText(self)
self.assertIn("Failed to render non-text", text)

def test__qualname__descriptor_path(self):
# When the __qualname__ is not a string but a descriptor object,
# getPythonPath does not raise an AttributeError
# https://github.com/zopefoundation/zope.app.apidoc/issues/25

from zope.app.apidoc.utilities import getPythonPath

class O(object):
"namespace object"

o = O()
o.__qualname__ = object()
o.__name__ = 'foo'

self.assertEqual('zope.app.apidoc.tests.foo', getPythonPath(o))

def test__qualname__descriptor_referencable(self):
# When the __qualname__ is not a string but a descriptor object,
# isReferencable does not raise an AttributeError
# https://github.com/zopefoundation/zope.app.apidoc/issues/25

from zope.app.apidoc import utilities

# Set up an object that will return itself when looked up
# as a module attribute via patching safe_import and when
# asked for its __class__ so we can control the __qualname__
# on all versions of Python.

class O(object):
"namespace object"

def __getattribute__(self, name):
if name in object.__getattribute__(self, '__dict__'):
return object.__getattribute__(self, '__dict__')[name]
return self

o = O()
o.__qualname__ = object()

def safe_import(path):
return o

old_safe_import = utilities.safe_import
utilities.safe_import = safe_import
try:
self.assertTrue(utilities.isReferencable('a.module.object'))
finally:
utilities.safe_import = old_safe_import


from zope.app.apidoc import static

Expand Down
10 changes: 6 additions & 4 deletions src/zope/app/apidoc/utilities.py
Expand Up @@ -130,10 +130,12 @@ def getPythonPath(obj):
# proxies for this check.
naked = removeSecurityProxy(obj)
qname = ''
if hasattr(naked, '__qualname__'):
# Python 3. This makes unbound functions inside classes
# do the same thing as they do an Python 2: return just their
# class name.
if isinstance(getattr(naked, '__qualname__', None), str):
# Python 3 (being sure to protect against non-str __qualname__
# that we could get on some versions of Cython. See
# https://github.com/zopefoundation/zope.app.apidoc/issues/25).
# This makes unbound functions inside classes do the same
# thing as they do an Python 2: return just their class name.
qname = naked.__qualname__
qname = qname.split('.')[0]
if hasattr(naked, 'im_class'):
Expand Down

0 comments on commit 426115d

Please sign in to comment.