From 395e152bce9d139fde8546789f96c66531e97625 Mon Sep 17 00:00:00 2001 From: "david.seb.fischer" Date: Wed, 14 Aug 2019 14:50:21 +0200 Subject: [PATCH] adapted conf for rtfd --- LICENSE | 2 +- NOTICE | 2 + diffxpy/__init__.py | 8 + docs/conf.py | 363 ++++++++++++-------------------------------- 4 files changed, 107 insertions(+), 268 deletions(-) create mode 100644 NOTICE diff --git a/LICENSE b/LICENSE index 0d8d25e..db8e2b4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2018, David S. Fischer, Florian R. Hölzlwimmer. +Copyright (c) 2018, David S. Fischer, Florian R. Hölzlwimmer, Theis Lab (theislab). All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..e3e6df7 --- /dev/null +++ b/NOTICE @@ -0,0 +1,2 @@ +The file ./docs/conf.py was adapted from ttps://github.com/theislab/scanpy/scanpy/conf.py and is licensed by the +scanpy project (F. Alexander Wolf, P. Angerer, Theis Lab) as described in the file. \ No newline at end of file diff --git a/diffxpy/__init__.py b/diffxpy/__init__.py index 2effa51..a2b32b8 100644 --- a/diffxpy/__init__.py +++ b/diffxpy/__init__.py @@ -4,3 +4,11 @@ del get_versions from .log_cfg import logger, unconfigure_logging, enable_logging + +__author__ = ', '.join([ + 'David Sebastian Fischer', + 'Florian Hölzlwimmer' +]) +__email__ = ', '.join([ + 'david.fischer@helmholtz-muenchen.de' +]) diff --git a/docs/conf.py b/docs/conf.py index 45cf027..09d9263 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,332 +1,161 @@ +# This code was adapted from https://github.com/theislab/scanpy/scanpy/conf.py +# This file is therefore licensed under the license of the scanpy project, +# available from https://github.com/theislab/scanpy and copied here at the time of accession. +# Note that multiple changes were made to this file to adapt it to the diffxpy project. + +# BSD 3-Clause License +# +# Copyright (c) 2017 F. Alexander Wolf, P. Angerer, Theis Lab +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + import sys -import inspect -import logging from pathlib import Path from datetime import datetime -from typing import Optional - -from sphinx.application import Sphinx -from sphinx.ext import autosummary - -# remove PyCharm’s old six module -if 'six' in sys.modules: - print(*sys.path, sep='\n') - for pypath in list(sys.path): - if any(p in pypath for p in ['PyCharm', 'pycharm']) and 'helpers' in pypath: - sys.path.remove(pypath) - del sys.modules['six'] - -import matplotlib # noqa -# Don’t use tkinter agg when importing scanpy → … → matplotlib +import matplotlib matplotlib.use('agg') HERE = Path(__file__).parent sys.path.insert(0, str(HERE.parent)) import diffxpy -logger = logging.getLogger("diffxpy") # -- General configuration ------------------------------------------------ -needs_sphinx = '1.7' # autosummary bugfix + +needs_sphinx = '1.7' + +# General information +project = 'diffxpy' +author = diffxpy.__author__ +copyright = f'{datetime.now():%Y}, {author}.' +version = diffxpy.__version__.replace('.dirty', '') +release = version + +# default settings +templates_path = ['_templates'] +source_suffix = '.rst' +master_doc = 'index' +default_role = 'literal' +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +pygments_style = 'sphinx' + extensions = [ 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', 'sphinx.ext.doctest', 'sphinx.ext.coverage', 'sphinx.ext.mathjax', - 'sphinx.ext.autosummary', - # 'plot_generator', - # 'plot_directive', 'sphinx.ext.napoleon', - 'sphinx_autodoc_typehints', - 'sphinx.ext.intersphinx', - # 'ipython_directive', - # 'ipython_console_highlighting', + 'sphinx.ext.autosummary' ] # Generate the API documentation when building autosummary_generate = True -# both of the following two lines don't work -# see falexwolf's issue for numpydoc -# autodoc_member_order = 'bysource' -# autodoc_default_flags = ['members'] +autodoc_member_order = 'bysource' napoleon_google_docstring = False napoleon_numpy_docstring = True napoleon_include_init_with_doc = False +napoleon_use_rtype = True +napoleon_use_param = True +napoleon_custom_sections = [('Params', 'Parameters')] +todo_include_todos = False intersphinx_mapping = dict( - python=('https://docs.python.org/3', None), + anndata=('https://anndata.readthedocs.io/en/latest/', None), + scanpy=('https://scanpy.readthedocs.io/en/latest/', None), numpy=('https://docs.scipy.org/doc/numpy/', None), - scipy=('https://docs.scipy.org/doc/scipy/reference/', None), pandas=('http://pandas.pydata.org/pandas-docs/stable/', None), - matplotlib=('https://matplotlib.org/', None), - # anndata=('https://anndata.readthedocs.io/en/latest/', None), + python=('https://docs.python.org/3', None), + scipy=('https://docs.scipy.org/doc/scipy/reference/', None) ) -templates_path = ['_templates'] - -project = 'diffxpy' -author = 'David S. Fischer, Florian R. Hölzlwimmer' - -source_suffix = '.rst' -master_doc = 'index' -copyright = f'{datetime.now():%Y}, {author}' - -version = diffxpy.__version__.replace('.dirty', '') -release = version -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] -pygments_style = 'sphinx' -todo_include_todos = False # -- Options for HTML output ---------------------------------------------- + html_theme = 'sphinx_rtd_theme' html_theme_options = dict( - navigation_depth=2, + navigation_depth=4, + logo_only=True, # Only show the logo ) html_context = dict( - display_github=True, # Integrate GitHub - github_user='theislab', # Username - github_repo='diffxpy', # Repo name + display_github=True, # Integrate GitHub + github_user='theislab', # Username + github_repo='diffxpy', # Repo name github_version='master', # Version - conf_py_path='/docs/', # Path in the checkout to the docs root + conf_py_path='/docs/', # Path in the checkout to the docs root ) html_static_path = ['_static'] +html_show_sphinx = False +gh_url = 'https://github.com/{github_user}/{github_repo}'.format_map(html_context) def setup(app): app.add_stylesheet('css/custom.css') + app.connect('autodoc-process-docstring', insert_function_images) + app.add_role('pr', autolink(f'{gh_url}/pull/{{}}', 'PR {}')) -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'diffxpydoc' +# -- Options for other output formats ------------------------------------------ -# -- Options for LaTeX output ------------------------------------------------ -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). +htmlhelp_basename = f'{project}doc' +doc_title = f'{project} Documentation' latex_documents = [ - (master_doc, 'diffxpy.tex', 'diffxpy Documentation', - 'David S. Fischer, Florian R. Hölzlwimmer', 'manual'), + (master_doc, f'{project}.tex', doc_title, author, 'manual'), ] - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'diffxpy', 'diffxpy Documentation', - [author], 1) + (master_doc, project, doc_title, [author], 1) ] - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'diffxpy', 'diffxpy Documentation', - author, 'diffxpy', 'One line description of project.', - 'Miscellaneous'), + (master_doc, project, doc_title, author, project, 'One line description of project.', 'Miscellaneous'), ] -# -- Extension configuration ------------------------------------------------- - - -# -- generate_options override ------------------------------------------ -# TODO: why? - - -def process_generate_options(app: Sphinx): - genfiles = app.config.autosummary_generate - - if genfiles and not hasattr(genfiles, '__len__'): - env = app.builder.env - genfiles = [ - env.doc2path(x, base=None) - for x in env.found_docs - if Path(env.doc2path(x)).is_file() - ] - if not genfiles: - return - - from sphinx.ext.autosummary.generate import generate_autosummary_docs +# -- Images for plot functions ------------------------------------------------- - ext = app.config.source_suffix - genfiles = [ - genfile + (not genfile.endswith(tuple(ext)) and ext[0] or '') - for genfile in genfiles - ] - suffix = autosummary.get_rst_suffix(app) - if suffix is None: - return +def insert_function_images(app, what, name, obj, options, lines): + path = Path(__file__).parent / 'api' / f'{name}.png' + if what != 'function' or not path.is_file(): return + lines[0:0] = [f'.. image:: {path.name}', ' :width: 200', ' :align: right', ''] - generate_autosummary_docs( - genfiles, builder=app.builder, - warn=logger.warning, info=logger.info, - suffix=suffix, base_path=app.srcdir, - imported_members=True, app=app, - ) +# -- GitHub links -------------------------------------------------------------- -autosummary.process_generate_options = process_generate_options +def autolink(url_template, title_template='{}'): + from docutils import nodes -# -- GitHub URLs for class and method pages ------------------------------------------ - - -def get_obj_module(qualname): - """Get a module/class/attribute and its original module by qualname""" - modname = qualname - classname = None - attrname = None - while modname not in sys.modules: - attrname = classname - modname, classname = modname.rsplit('.', 1) - - # retrieve object and find original module name - if classname: - cls = getattr(sys.modules[modname], classname) - modname = cls.__module__ - obj = getattr(cls, attrname) if attrname else cls - else: - obj = None - - return obj, sys.modules[modname] - - -def get_linenos(obj): - """Get an object’s line numbers""" - try: - lines, start = inspect.getsourcelines(obj) - except TypeError: - return None, None - else: - return start, start + len(lines) - 1 - - -project_dir = Path(__file__).parent.parent # project/docs/conf.py/../.. → project/ -github_url1 = 'https://github.com/{github_user}/{github_repo}/tree/{github_version}'.format_map(html_context) -github_url2 = 'https://github.com/theislab/diffxpy/tree/master' - - -def modurl(qualname: str) -> str: - """Get the full GitHub URL for some object’s qualname.""" - obj, module = get_obj_module(qualname) - github_url = github_url1 - try: - path = Path(module.__file__).relative_to(project_dir) - except ValueError: - # trying to document something from another package - github_url = github_url2 - path = '/'.join(module.__file__.split('/')[-2:]) - start, end = get_linenos(obj) - fragment = f'#L{start}-L{end}' if start and end else '' - return f'{github_url}/{path}{fragment}' - - -def api_image(qualname: str) -> Optional[str]: - # I’d like to make this a contextfilter, but the jinja context doesn’t contain the path, - # so no chance to not hardcode “api/” here. - path = Path(__file__).parent / 'api' / f'{qualname}.png' - print(path, path.is_file()) - return f'.. image:: {path.name}\n :width: 200\n :align: right' if path.is_file() else '' - - -# html_context doesn’t apply to autosummary templates ☹ -# and there’s no way to insert filters into those templates -# so we have to modify the default filters -from jinja2.defaults import DEFAULT_FILTERS - -DEFAULT_FILTERS.update(modurl=modurl, api_image=api_image) - -# -- Prettier Param docs -------------------------------------------- - - -from typing import Dict, List, Tuple - -from docutils import nodes -from sphinx import addnodes -from sphinx.domains.python import PyTypedField, PyObject -from sphinx.environment import BuildEnvironment - - -class PrettyTypedField(PyTypedField): - list_type = nodes.definition_list - - def make_field( - self, - types: Dict[str, List[nodes.Node]], - domain: str, - items: Tuple[str, List[nodes.inline]], - env: BuildEnvironment = None - ) -> nodes.field: - def makerefs(rolename, name, node): - return self.make_xrefs(rolename, domain, name, node, env=env) - - def handle_item(fieldarg: str, content: List[nodes.inline]) -> nodes.definition_list_item: - head = nodes.term() - head += makerefs(self.rolename, fieldarg, addnodes.literal_strong) - fieldtype = types.pop(fieldarg, None) - if fieldtype is not None: - head += nodes.Text(' : ') - if len(fieldtype) == 1 and isinstance(fieldtype[0], nodes.Text): - typename = ''.join(n.astext() for n in fieldtype) - head += makerefs(self.typerolename, typename, addnodes.literal_emphasis) - else: - head += fieldtype - - body_content = nodes.paragraph('', '', *content) - body = nodes.definition('', body_content) - - return nodes.definition_list_item('', head, body) - - fieldname = nodes.field_name('', self.label) - if len(items) == 1 and self.can_collapse: - fieldarg, content = items[0] - bodynode = handle_item(fieldarg, content) - else: - bodynode = self.list_type() - for fieldarg, content in items: - bodynode += handle_item(fieldarg, content) - fieldbody = nodes.field_body('', bodynode) - return nodes.field('', fieldname, fieldbody) - - -# replace matching field types with ours -PyObject.doc_field_types = [ - PrettyTypedField( - ft.name, - names=ft.names, - typenames=ft.typenames, - label=ft.label, - rolename=ft.rolename, - typerolename=ft.typerolename, - can_collapse=ft.can_collapse, - ) if isinstance(ft, PyTypedField) else ft - for ft in PyObject.doc_field_types -] + def role(name, rawtext, text, lineno, inliner, options={}, content=[]): + url = url_template.format(text) + title = title_template.format(text) + node = nodes.reference(rawtext, title, refuri=url, **options) + return [node], [] + return role