docs/keras_autodoc/autogen.py
import pathlib
import shutil
from inspect import getdoc
from inspect import isclass
from typing import Dict
from typing import List
from typing import Union
from typing import get_type_hints
from . import utils
from .docstring import process_docstring
from .examples import copy_examples
from .get_signatures import get_signature
class DocumentationGenerator:
"""Generates the documentation.
# Arguments
pages: A dictionary. The keys are the files' paths, the values
are lists of strings, functions /classes / methods names
with dotted access to the object. For example,
`pages = {'my_file.md': ['keras.layers.Dense']}` is valid.
project_url: The url pointing to the module directory of your project on
GitHub. This will be used to make a `[Sources]` link.
template_dir: Where to put the markdown files which will be copied and
filled in the destination directory. You should put files like
`index.md` inside. If you want a markdown file to be filled with
the docstring of a function, use the `{{autogenerated}}` tag inside,
and then add the markdown file to the `pages` dictionary.
example_dir: Where you store examples in your project. Usually
standalone files with a markdown docstring at the top. Will be
inserted in the docs.
extra_aliases: When displaying type hints, it's possible that the full
dotted path is displayed instead of alias. The aliases present in
`pages` are used, but it may happen if you're using a third-party
library. For example `tensorflow.python.ops.variables.Variable` is
displayed instead of `tensorflow.Variable`. Here you have two
solutions, either you provide the import keras-autodoc should
follow:
`extra_aliases=["tensorflow.Variable"]`, either you provide a
mapping to use
`extra_aliases={"tensorflow.python.ops.variables.Variable":
"tf.Variable"}`. The second option should be used if you want more
control and that you don't want to respect the alias corresponding
to the import (you can't do `import tf.Variable`). When giving a
list, keras-autodoc will try to import the object from the string to
understand what object you want to replace.
max_signature_line_length: When displaying class and function
signatures, keras-autodoc formats them using Black. This parameter
controls the maximum line length of these signatures, and is passed
directly through to Black.
titles_size: `"#"` signs to put before a title in the generated
markdown.
"""
def __init__(
self,
pages: Dict[str, list] = {},
project_url: Union[str, Dict[str, str]] = None,
template_dir=None,
examples_dir=None,
extra_aliases: Union[List[str], Dict[str, str]] = None,
max_signature_line_length: int = 110,
titles_size="###",
):
self.pages = pages
self.project_url = project_url
self.template_dir = template_dir
self.examples_dir = examples_dir
self.class_aliases = {}
self._fill_aliases(extra_aliases)
self.max_signature_line_length = max_signature_line_length
self.titles_size = titles_size
def generate(self, dest_dir):
"""Generate the docs.
# Arguments
dest_dir: Where to put the resulting markdown files.
"""
dest_dir = pathlib.Path(dest_dir)
print("Cleaning up existing sources directory.")
if dest_dir.exists():
shutil.rmtree(dest_dir)
print("Populating sources directory with templates.")
if self.template_dir:
shutil.copytree(self.template_dir, dest_dir)
for file_path, elements in self.pages.items():
markdown_text = ""
for element in elements:
markdown_text += self._render(element)
utils.insert_in_file(markdown_text, dest_dir / file_path)
if self.examples_dir is not None:
copy_examples(self.examples_dir, dest_dir / "examples")
def process_docstring(self, docstring, types: dict = None):
"""Can be overridden."""
processsed = process_docstring(docstring, types, self.class_aliases)
return processsed
def process_signature(self, signature):
"""Can be overridden."""
return signature
def _render(self, element):
if isinstance(element, str):
object_ = utils.import_object(element)
if utils.ismethod(object_):
# we remove the modules when displaying the methods
signature_override = ".".join(element.split(".")[-2:])
else:
signature_override = element
else:
signature_override = None
object_ = element
return self._render_from_object(object_, signature_override)
def _render_from_object(self, object_, signature_override: str):
subblocks = []
if self.project_url is not None:
subblocks.append(utils.make_source_link(object_, self.project_url))
signature = get_signature(
object_, signature_override, self.max_signature_line_length
)
signature = self.process_signature(signature)
subblocks.append(f"{self.titles_size} {object_.__name__}\n")
subblocks.append(utils.code_snippet(signature))
docstring = getdoc(object_)
if docstring:
if isclass(object_):
type_hints = get_type_hints(object_.__init__)
else:
type_hints = get_type_hints(object_)
docstring = self.process_docstring(docstring, type_hints)
subblocks.append(docstring)
return "\n\n".join(subblocks) + "\n\n----\n\n"
def _fill_aliases(self, extra_aliases):
for list_elements in self.pages.values():
for element_as_str in list_elements:
element = utils.import_object(element_as_str)
if not isclass(element):
continue
true_dotted_path = utils.get_dotted_path(element)
self.class_aliases[true_dotted_path] = element_as_str
if isinstance(extra_aliases, dict):
self.class_aliases.update(extra_aliases)
elif isinstance(extra_aliases, list):
for alias in extra_aliases:
full_dotted_path = utils.get_dotted_path(
utils.import_object(alias)
)
self.class_aliases[full_dotted_path] = alias