jenkins-bot merged this change.
Sphinx autodoc workaround for Family classproperty-s
Sphinx uses getattr() on the class in order to get the attribute.
This works for normal instance properties, since it gets the property
instance itself, but for classproperties, it gets whatever the
classproperty code returns. This not only loses all documentation
the classproperty code have, and can potentially cause side effects.
Other methods to workaround the issue are described in:
https://stackoverflow.com/q/22357961. This includes subclassing the
return value (unknown consequences to user), metaclass property
(not extensible), proxy object (error-prone and ugly),and indirect
get method (breaks backwards-compatibility entirely). This workaround
makes Sphinx uses a custom getattr we defined in docs/conf.py for
getting attributes of Family subclass, where it iterates through the
MRO looking for a definition. If it's defined as a classproperty,
it returns the classproperty instance instead; else it falls back
to sphinx's safe_getattr().
Other changes in this patch include:
* make all families defined in family.py abstract and
un-instantiatable.
* make classproperty fetch its docstring from its wrapped function
* make docs/conf.py have its `from script.` import after insert to
sys.path so that it is easier to run sphinx locally.
* make SubdomainFamily.codes no longer treat empty languages_by_size
as valid, because empty languages_by_size is always defined via
MRO and `hasattr(cls, 'languages_by_size')` is never False. This
also prevents SubdomainFamily.codes being accessed directly.
Change-Id: I790580b8d5d9e6349a26ab52c8859a7148441bff
---
M docs/conf.py
M pywikibot/family.py
M pywikibot/tools/__init__.py
3 files changed, 36 insertions(+), 10 deletions(-)
diff --git a/docs/conf.py b/docs/conf.py
index 14b44e8..dadc26c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -22,9 +22,6 @@
import re
import sys
-from scripts.cosmetic_changes import warning
-
-
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
@@ -262,9 +259,10 @@
os.environ['PYWIKIBOT2_NO_USER_CONFIG'] = '1'
-def pywikibot_script_docstring_fixups(
- app, what, name, obj, options, lines):
+def pywikibot_script_docstring_fixups(app, what, name, obj, options, lines):
"""Pywikibot specific conversions."""
+ from scripts.cosmetic_changes import warning
+
if what != "module":
return
@@ -326,10 +324,35 @@
return skip or name in exclusions
+def pywikibot_family_classproperty_getattr(obj, name, *defargs):
+ """Custom getattr() to get classproperty instances."""
+ from sphinx.util.inspect import safe_getattr
+
+ from pywikibot.family import Family
+ from pywikibot.tools import classproperty
+
+ if not isinstance(obj, type) or not issubclass(obj, Family):
+ return safe_getattr(obj, name, *defargs)
+
+ for base_class in obj.__mro__:
+ try:
+ prop = base_class.__dict__[name]
+ except KeyError:
+ continue
+
+ if not isinstance(prop, classproperty):
+ return safe_getattr(obj, name, *defargs)
+
+ return prop
+ else:
+ return safe_getattr(obj, name, *defargs)
+
+
def setup(app):
"""Implicit Sphinx extension hook."""
app.connect('autodoc-process-docstring', pywikibot_script_docstring_fixups)
app.connect('autodoc-skip-member', pywikibot_skip_members)
+ app.add_autodoc_attrgetter(type, pywikibot_family_classproperty_getattr)
pywikibot_env()
diff --git a/pywikibot/family.py b/pywikibot/family.py
index e5513c0..ae52a7b 100644
--- a/pywikibot/family.py
+++ b/pywikibot/family.py
@@ -46,9 +46,11 @@
def __new__(cls):
"""Allocator."""
- if cls is Family:
- raise TypeError('Base Family class cannot be instantiated; '
- 'subclass it instead')
+ # any Family class defined in this file are abstract
+ if cls in globals().values():
+ raise TypeError(
+ 'Abstract Family class {0} cannot be instantiated; '
+ 'subclass it instead'.format(cls.__name__))
# Override classproperty
cls.instance = super(Family, cls).__new__(cls)
@@ -1544,7 +1546,7 @@
@classproperty
def codes(cls):
"""Property listing family codes."""
- if hasattr(cls, 'languages_by_size'):
+ if cls.languages_by_size:
return cls.languages_by_size
raise NotImplementedError(
'Family %s needs property "languages_by_size" or "codes"'
diff --git a/pywikibot/tools/__init__.py b/pywikibot/tools/__init__.py
index 9a7f1ff..b0303d8 100644
--- a/pywikibot/tools/__init__.py
+++ b/pywikibot/tools/__init__.py
@@ -129,7 +129,7 @@
class classproperty(object): # noqa: N801
"""
- Metaclass to accesss a class method as a property.
+ Descriptor class to accesss a class method as a property.
This class may be used as a decorator::
@@ -147,6 +147,7 @@
def __init__(self, cls_method):
"""Hold the class method."""
self.method = cls_method
+ self.__doc__ = self.method.__doc__
def __get__(self, instance, owner):
"""Get the attribute of the owner class by its method."""
To view, visit change 435676. To unsubscribe, visit settings.