Skip to content
This repository has been archived by the owner on May 25, 2022. It is now read-only.

Add autodoc attrgetter workaround for classes #130

Merged
merged 2 commits into from
Jan 26, 2015
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: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ astropy-helpers Changelog
- Added some additional object reference link corrections for Python 3
when building docs. [#123]

- Added a workaround for documentation of properties in the rare case
where the class's metaclass has a property of the same name. [#130]


0.4.4 (2014-12-31)
------------------
Expand Down
1 change: 1 addition & 0 deletions astropy_helpers/sphinx/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def check_sphinx_version(expected_version):
'sphinx.ext.inheritance_diagram',
'astropy_helpers.sphinx.ext.numpydoc',
'astropy_helpers.sphinx.ext.astropyautosummary',
'astropy_helpers.sphinx.ext.autodoc_enhancements',
'astropy_helpers.sphinx.ext.automodsumm',
'astropy_helpers.sphinx.ext.automodapi',
'astropy_helpers.sphinx.ext.tocdepthfix',
Expand Down
56 changes: 56 additions & 0 deletions astropy_helpers/sphinx/ext/autodoc_enhancements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""
Miscellaneous enhancements to help autodoc along.
"""


# See
# https://github.com/astropy/astropy-helpers/issues/116#issuecomment-71254836
# for further background on this.
def type_object_attrgetter(obj, attr, *defargs):
"""
This implements an improved attrgetter for type objects (i.e. classes)
that can handle class attributes that are implemented as properties on
a metaclass.

Normally `getattr` on a class with a `property` (say, "foo"), would return
the `property` object itself. However, if the class has a metaclass which
*also* defines a `property` named "foo", ``getattr(cls, 'foo')`` will find
the "foo" property on the metaclass and resolve it. For the purposes of
autodoc we just want to document the "foo" property defined on the class,
not on the metaclass.

For example::

>>> class Meta(type):
... @property
... def foo(cls):
... return 'foo'
...
>>> class MyClass(metaclass=Meta):
... @property
... def foo(self):
... \"\"\"Docstring for MyClass.foo property.\"\"\"
... return 'myfoo'
...
>>> getattr(MyClass, 'foo')
'foo'
>>> type_object_attrgetter(MyClass, 'foo')
<property at 0x...>
>>> type_object_attrgetter(MyClass, 'foo').__doc__
'Docstring for MyClass.foo property.'

The last line of the example shows the desired behavior for the purposes
of autodoc.
"""

if attr in obj.__dict__ and isinstance(obj.__dict__[attr], property):
# Note, this should only be used for properties--for any other type of
# descriptor (classmethod, for example) this can mess up existing
# expectcations of what getattr(cls, ...) returns
return obj.__dict__[attr]
else:
return getattr(obj, attr, *defargs)


def setup(app):
app.add_autodoc_attrgetter(type, type_object_attrgetter)
56 changes: 56 additions & 0 deletions astropy_helpers/sphinx/ext/tests/test_autodoc_enhancements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import sys

from textwrap import dedent

import pytest

from ..autodoc_enhancements import type_object_attrgetter


# Define test classes outside the class; otherwise there is flakiness with the
# details of how exec works on different Python versions
class Meta(type):
@property
def foo(cls):
return 'foo'

if sys.version_info[0] < 3:
exec(dedent("""
class MyClass(object):
__metaclass__ = Meta
@property
def foo(self):
\"\"\"Docstring for MyClass.foo property.\"\"\"
return 'myfoo'
"""))
else:
exec(dedent("""
class MyClass(metaclass=Meta):
@property
def foo(self):
\"\"\"Docstring for MyClass.foo property.\"\"\"
return 'myfoo'
"""))


def test_type_attrgetter():
"""
This test essentially reproduces the docstring for
`type_object_attrgetter`.

Sphinx itself tests the custom attrgetter feature; see:
https://bitbucket.org/birkenfeld/sphinx/src/40bd03003ac6fe274ccf3c80d7727509e00a69ea/tests/test_autodoc.py?at=default#cl-502
so rather than a full end-to-end functional test it's simple enough to just
test that this function does what it needs to do.
"""

assert getattr(MyClass, 'foo') == 'foo'
obj = type_object_attrgetter(MyClass, 'foo')
assert isinstance(obj, property)
assert obj.__doc__ == 'Docstring for MyClass.foo property.'

with pytest.raises(AttributeError):
type_object_attrgetter(MyClass, 'susy')

assert type_object_attrgetter(MyClass, 'susy', 'default') == 'default'
assert type_object_attrgetter(MyClass, '__dict__') == MyClass.__dict__