Revised edition to support autosummary, markdown, and other goodies
Add the following to your development requirements file:
- sphinx
- sphinx_rtd_theme
- pyenchant
- sphinxcontrib-spelling
- recommonmark
docs/conf.py
import sys
import os
import importlib
import inspect
from recommonmark.parser import CommonMarkParser
from django.conf import settings
from django.db.models.fields.files import FileDescriptor
from django.db.models.manager import ManagerDescriptor
from django.db.models.query import QuerySet
from django.utils.translation import ugettext, get_language, activate, deactivate
try:
import enchant # NoQA
except ImportError:
enchant = None
# 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.
sys.path.insert(0, os.path.abspath('.'))
sys.path.insert(0, os.path.abspath('..'))
sys.path.append(os.path.abspath('../../lib/python3.5/site-packages/'))
# Import Django settings
settings.configure()
import django
django.setup()
# Fix Django's FileFields
FileDescriptor.__get__ = lambda self, *args, **kwargs: self
ManagerDescriptor.__get__ = lambda self, *args, **kwargs: self.manager
# Stop Django from executing DB queries
QuerySet.__repr__ = lambda self: self.__class__.__name__
GITHUB_USER = '' # Name of your Github user or organisation
GITHUB_PROJECT = '' # Name of your Github repository
def process_django_models(app, what, name, obj, options, lines):
"""Append params from fields to model documentation."""
from django.utils.encoding import force_text
from django.utils.html import strip_tags
from django.db import models
spelling_white_list = ['', '.. spelling::']
if inspect.isclass(obj) and issubclass(obj, models.Model):
for field in obj._meta.fields:
help_text = strip_tags(force_text(field.help_text))
verbose_name = force_text(field.verbose_name).capitalize()
if help_text:
lines.append(':param %s: %s - %s' % (field.attname, verbose_name, help_text))
else:
lines.append(':param %s: %s' % (field.attname, verbose_name))
if enchant is not None:
from enchant.tokenize import basic_tokenize
words = verbose_name.replace('-', '.').replace('_', '.').split('.')
words = [s for s in words if s != '']
for word in words:
spelling_white_list += [" %s" % ''.join(i for i in word if not i.isdigit())]
spelling_white_list += [" %s" % w[0] for w in basic_tokenize(word)]
field_type = type(field)
module = field_type.__module__
if 'django.db.models' in module:
# scope with django.db.models * imports
module = 'django.db.models'
lines.append(':type %s: %s.%s' % (field.attname, module, field_type.__name__))
if enchant is not None:
lines += spelling_white_list
return lines
def process_modules(app, what, name, obj, options, lines):
"""Add module names to spelling white list."""
if what != 'module':
return lines
from enchant.tokenize import basic_tokenize
spelling_white_list = ['', '.. spelling::']
words = name.replace('-', '.').replace('_', '.').split('.')
words = [s for s in words if s != '']
for word in words:
spelling_white_list += [" %s" % ''.join(i for i in word if not i.isdigit())]
spelling_white_list += [" %s" % w[0] for w in basic_tokenize(word)]
lines += spelling_white_list
return lines
def skip_queryset(app, what, name, obj, skip, options):
"""Skip queryset subclasses to avoid database queries."""
from django.db import models
if isinstance(obj, (models.QuerySet, models.manager.BaseManager)) or name.endswith('objects'):
return True
return skip
def setup(app):
# Register the docstring processor with sphinx
app.connect('autodoc-process-docstring', process_django_models)
app.connect('autodoc-skip-member', skip_queryset)
if enchant is not None:
app.connect('autodoc-process-docstring', process_modules)
intersphinx_mapping = {
'python': ('https://docs.python.org/2.7', None),
'sphinx': ('http://sphinx.pocoo.org/', None),
'django': ('https://docs.djangoproject.com/en/1.9/', 'https://docs.djangoproject.com/en/1.9/_objects/'),
'djangoextensions': ('https://django-extensions.readthedocs.org/en/latest/', None),
# 'geoposition': ('https://django-geoposition.readthedocs.org/en/latest/', None),
'braces': ('https://django-braces.readthedocs.org/en/latest/', None),
# 'select2': ('https://django-select2.readthedocs.org/en/latest/', None),
# 'celery': ('https://celery.readthedocs.org/en/latest/', None),
}
def linkcode_resolve(domain, info):
"""Link source code to GitHub."""
project = GITHUB_PROJECT
github_user = GITHUB_USER
head = 'master'
if domain != 'py' or not info['module']:
return None
filename = info['module'].replace('.', '/')
mod = importlib.import_module(info['module'])
basename = os.path.splitext(mod.__file__)[0]
if basename.endswith('__init__'):
filename += '/__init__'
item = mod
lineno = ''
for piece in info['fullname'].split('.'):
item = getattr(item, piece)
try:
lineno = '#L%d' % inspect.getsourcelines(item)[1]
except (TypeError, IOError):
pass
return ("https://github.com/%s/%s/blob/%s/%s.py%s" %
(github_user, project, head, filename, lineno))
autodoc_member_order = 'bysource'
autodoc_default_flags = ['members', 'undoc-members']
autosummary_generate = True
# spell checking
spelling_lang = 'en_GB'
spelling_word_list_filename = 'spelling_wordlist.txt'
spelling_show_suggestions = True
spelling_ignore_pypi_package_names = True
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx.ext.graphviz',
'sphinx.ext.napoleon',
'sphinx.ext.linkcode',
'sphinx.ext.inheritance_diagram',
'sphinx.ext.doctest',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.imgmath',
'sphinx.ext.ifconfig',
'sphinx.ext.viewcode',
'sphinx.ext.intersphinx'
]
if enchant is not None:
extensions.append('sphinxcontrib.spelling')
# Here goes all your other config.
In lib/pythonX.Y/site-packages/sphinx/ext/autosummary/__init__.py
, comment out lines 561-562 and add the two lines below. They attempt to add a file extension onto files that shouldn't exist with a file extension on. Without this patch, when source_suffix = ['.rst', '.md', '.txt']
in conf.py, this method is broken because it tries to add .rst onto .md and other files and thus generates a list of files that don't exist
# genfiles = [genfile + (not genfile.endswith(ext) and ext or '')
# for genfile in genfiles]
# Filter files for ReST files only
genfiles = [genfile for genfile in genfiles if genfile.endswith('.rst')]
Docstring reference: https://pythonhosted.org/an_example_pypi_project/sphinx.html
Also look at implementing the Napoleon extension for better docstrings: https://sphinxcontrib-napoleon.readthedocs.org/en/latest/
Handy primer slideshow at: http://www.slideshare.net/shimizukawa/sphinx-autodoc-automated-api-documentation-pyconapac2015