Skip to content

Commit

Permalink
Use internal python reference links in docs (#205)
Browse files Browse the repository at this point in the history
* Resolves sphinx linkchecker errors

Co-authored-by: Asif Tamuri <tamuri@gmail.com>
  • Loading branch information
mattagape and tamuri committed Nov 3, 2020
1 parent 8d7c470 commit 5cb0765
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 48 deletions.
85 changes: 48 additions & 37 deletions src/tlo/docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@


def get_package_name(dirpath):
# e.g. if dirpath = "./src/tlo/logging/sublog"
# we want "tlo.logging.sublog" returned
# and if dirpath = "./src/tlo", we want just "tlo" returned
'''
Given a file path to a TLO package, return the name in dot form.
:param dirpath: the path to the package,
e.g. (1) "./src/tlo/logging/sublog" or (2) "./src/tlo"
:return: string of package name in dot form,
e.g. (1) "tlo.logging.sublog" or (2) "tlo"
'''
TLO = "/tlo/"
if TLO not in dirpath:
raise ValueError(f"Sorry, {TLO} isn't in dirpath ({dirpath})")
Expand All @@ -17,21 +22,24 @@ def get_package_name(dirpath):
runt = runt.replace("/", ".") # e.g. logging.sublog
# print(f"now runt is {runt}")
if runt:
package_name = "tlo." + runt
package_name = f"tlo.{runt}"
else:
package_name = "tlo"
return package_name


def generate_module_dict(topdir):
# Given a root directory topdir, iterates recursively
# over top dir and the files and subdirectories within it.
# Returns a dictionary with each key = path to a directory
# (i.e. to topdir or one of its nested subdirectories), and
# value = list of Python .py files in that directory.
# :param topdir: root directory to traverse downwards from, iteratively.
# :returns: dict with key = a directory,
# value = list of Python files in dir `key`.
'''
Given a root directory topdir, iterates recursively
over top dir and the files and subdirectories within it.
Returns a dictionary with each key = path to a directory
(i.e. to topdir or one of its nested subdirectories), and
value = list of Python .py files in that directory.
:param topdir: root directory to traverse downwards from, iteratively.
:returns: dict with key = a directory,
value = list of Python files in dir `key`.
'''
data = {} # key = path to dir, value = list of .py files

for (dirpath, dirnames, filenames) in walk(topdir):
Expand Down Expand Up @@ -62,10 +70,10 @@ def get_classes_in_module(fqn, module_obj):
[class name, class object, line number]
:param fqn: Fully-qualified name of the module,
e.g. "tlo.methods.mockitis"
e.g. "tlo.methods.mockitis"
:param module_obj: an object representing this module
:param return: list of entries, each of the form:
[class name, class object, line number]
[class name, class object, line number]
'''
classes = []
module_info = inspect.getmembers(module_obj) # Gets everything
Expand Down Expand Up @@ -107,7 +115,7 @@ def get_fully_qualified_name(filename, context):
if context == "":
return parts[0]
else:
fqname = context + "." + parts[0]
fqname = f"{context}.{parts[0]}"
return fqname


Expand Down Expand Up @@ -247,12 +255,13 @@ def extract_bases(class_name, class_obj, spacer=""):
'''
Document which classes this class inherits from,
except for the object class or this class itself.
:param class_name: name of the class (e.g. Mockitis) for which we want the
bases
bases
:param class_obj: object with information about this class
:param spacer: string to use as whitespace padding.
:return: string of base(s) for this class (if any), with links to their
docs.
docs.
'''
# This next line gets the base classes including "object" class,
# and also the class_name class itself:
Expand All @@ -274,18 +283,12 @@ def extract_bases(class_name, class_obj, spacer=""):
parents.append(this_base_string)

if len(parents) > 0:
str = f"{spacer}**Base classes:**\n\n"
numbase = 1
str += f"{spacer}Base class #{numbase}: {parents[0]}\n\n"
for p in parents[1:]:
numbase += 1
str += f"{spacer}Base class #{numbase}: {p}\n\n"
out = f"{spacer}Bases: {', '.join(parents)}\n"
else:
str = ""
out = ""

# print(f"DEBUG: extract_bases: str = {str}")

return str
return out


def get_base_string(class_name, class_obj, base_obj):
Expand All @@ -295,13 +298,12 @@ def get_base_string(class_name, class_obj, base_obj):
Unless it is the root 'object' class, or the name of the class itself.
:param class_name: the name of the class (e.g. "Mockitis")
for which obj is a base,
for which obj is a base,
:param class_obj: object representing the class
:param base_obj: the object representation of the *base* class,
e.g. <class 'tlo.core.Module'> or <class 'object'>
or <class 'tlo.methods.mockitis.Mockitis'>
:return: string with hyperlink,
e.g. `tlo.core.Module <./tlo.core.html#tlo.core.Module>`_
e.g. <class 'tlo.core.Module'> or <class 'object'>
or <class 'tlo.methods.mockitis.Mockitis'>
:return: string as hyperlink
'''
# Extract fully-qualified name of base (e.g. "tlo.core.Module")
# from its object representation (e.g. "<class 'tlo.core.Module'>")
Expand All @@ -325,7 +327,7 @@ def get_base_string(class_name, class_obj, base_obj):
if name in [class_name, "object"]:
return ""

link = get_link(fqn, base_obj)
# link = get_link(fqn, base_obj)

# We want the final HTML to be like:
# Bases: <a class="reference internal" href="tlo.core.html#tlo.core.Module"
Expand All @@ -337,19 +339,26 @@ def get_base_string(class_name, class_obj, base_obj):
# the link to its documentation.
# e.g. the string might be:
# "tlo.core.Module <./tlo.core.html#tlo.core.Module>_"
mystr = "`" + fqn + " " + link + "`_"
#
# 3rd November 2020 - Asif suggests it should be more like:
# :class: `tlo.events.Event`
# or :py:class: `tlo.events.Event`
# to fix the "broken" link issue on Travis (#204)
# was: mystr = "`" + fqn + " " + link + "`_"
mystr = f":class:`{fqn}`"
# print(f"DEBUG: get_base_string(): {mystr}")
return mystr


def get_link(base_fqn, base_obj):
'''Given a name like Mockitis and a base_string like
tlo.core.Module, create a link to the latter's doc page.
:param base_fqn: fully-qualified name of the base class,
e.g. tlo.core.Module
e.g. tlo.core.Module
:param base_obj: the object representing the base class
:return: a string with the link to the base class's place in the generated
documentation.
documentation.
'''
# Example of module_defining_class: <module 'tlo.core' from '/Users/...
module_defining_class = str(inspect.getmodule(base_obj))
Expand All @@ -376,8 +385,10 @@ def get_link(base_fqn, base_obj):
def create_table(mydict):
'''
Dynamically create a table of arbitrary length
from PROPERTIES and PARAMETERS dictionaries.
`mydict` is the dictionary object.
from a PROPERTIES or PARAMETERS dictionary.
:param mydict: the dictionary object.
:return: A list of strings, used for output.
NB Do not change the positioning of items in the
f-strings below, or things will break!
Expand Down
14 changes: 3 additions & 11 deletions tests/test_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,8 @@ def test_extract_bases():
# [class name, class object, line number]
offspring = classes[-1]
name, obj = offspring[0:2]
expected = "**Base classes:**\n\n"
expected += ("Base class #1: `tests.test_docs_data.tlo.a.Father "
"<./tests.test_docs_data.tlo.a.html"
"#tests.test_docs_data.tlo.a.Father>`_\n\n")
expected += ("Base class #2: `tests.test_docs_data.tlo.a.Mother "
"<./tests.test_docs_data.tlo.a.html"
"#tests.test_docs_data.tlo.a.Mother>`_\n\n")
assert expected == extract_bases(name, obj)
expected = "Bases: :class:`tests.test_docs_data.tlo.a.Father`, :class:`tests.test_docs_data.tlo.a.Mother`"
assert expected == extract_bases(name, obj).strip()


def ignore_this_test_write_rst_file():
Expand Down Expand Up @@ -160,9 +154,7 @@ def test_get_base_string():
employee_name = employee_info[0]
employee_object = employee_info[1]
result = get_base_string(employee_name, employee_object, person_object)
expected = ("`tests.test_docs_data.tlo.a.Person "
"<./tests.test_docs_data.tlo.a.html#"
"tests.test_docs_data.tlo.a.Person>`_")
expected = ":class:`tests.test_docs_data.tlo.a.Person`"
assert result == expected


Expand Down

0 comments on commit 5cb0765

Please sign in to comment.