Generate Python documentation in HTML.

__main__ special

pydoc_fork A fork of pydoc that is optimized for generating html documentation in a CI context

Usage: pydoc_fork … [options] pydoc_fork (-h | –help) pydoc_fork –version

Options: -h –help Show this screen. -v –version Show version. –quiet No printing or logging. –verbose Crank up the logging. –config pyproject.toml or other toml config. –document_internals respect underscore or all private –prefer_docs_python_org link to python.org or generate own stdlib docs -o –output where to write files

main()

Get the args object from command parameters

Source code in pydoc_fork\__main__.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def main() -> int:
    """Get the args object from command parameters"""
    arguments = docopt.docopt(__doc__, version=f"pydoc_fork {__version__}")
    config_path = arguments.get("<config>")
    if config_path:
        load_config(config_path)

    LOGGER.debug(f"Invoking with docopts: {str(arguments)}")
    output_folder = arguments["--output"]

    # TODO: add lists of packages
    package = arguments["<package>"] or []
    # quiet = bool(arguments.get("--quiet", False))
    if arguments.get("--document_internals"):
        settings.DOCUMENT_INTERNALS = arguments["--document_internals"]
    if arguments.get("--prefer_docs_python_org"):
        settings.PREFER_DOCS_PYTHON_ORG = arguments["--prefer_docs_python_org"]

    if arguments.get("--verbose"):
        # root logger, all modules
        for root in ("pydoc_fork", "__main__"):
            logger = logging.getLogger(root)
            logger.setLevel(logging.DEBUG)
            handler = logging.StreamHandler()
            handler.setLevel(logging.DEBUG)
            log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
            formatter = logging.Formatter(log_format)
            handler.setFormatter(formatter)
            logger.addHandler(handler)
            LOGGERS.append(logger)

    commands.process_path_or_dot_name(
        package,
        output_folder=output_folder,
    )
    # # TODO
    #     print("Don't recognize that command.")
    #     return -1
    return 0

commands

Process commands as pure python functions.

All the CLI logic should be handled in main.

calculate_file_name(name, output_folder)

Returns name. If this was written, what would its name be

Source code in pydoc_fork\commands.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def calculate_file_name(name: str, output_folder: str) -> str:
    """Returns name. If this was written, what would its name be"""
    name = (
        name.replace("<", "")
            .replace(">", "")
            .replace(":", "")
            .replace(",", "_")
            .replace(" ", "_")
            .replace("(", "")
            .replace(")", "")
    )
    full_path = output_folder + os.sep + name + ".html"

    return full_path

document_directory(source_directory, output_folder, for_only='')

Write out HTML documentation for all modules in a directory tree.

Source code in pydoc_fork\commands.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def document_directory(
        source_directory: str,
        output_folder: str,
        for_only: str = "",
) -> List[str]:
    """Write out HTML documentation for all modules in a directory tree."""
    package_path = ""
    # walk packages is why pydoc drags along with it tests folders
    LOGGER.debug(f"document_directory: Walking packages for {source_directory}")

    full_paths: List[str] = []
    for _, modname, _ in pkgutil.walk_packages([source_directory], package_path):
        if not str(modname).startswith(for_only):
            continue
        LOGGER.debug(f"document_directory: current module: {modname})")
        full_path = document_one(modname, output_folder)
        if full_path:
            full_paths.append(full_path)
    return full_paths

document_one(thing, output_folder, force_load=False)

Write HTML documentation to a file in the current directory.

Source code in pydoc_fork\commands.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def document_one(
        thing: Union[TypeLike, str],
        output_folder: str,
        force_load: bool = False,
) -> Optional[str]:
    """Write HTML documentation to a file in the current directory."""
    try:
        the_object, name = resolve(thing, force_load)
    except (ImportError, ImportTimeError):
        LOGGER.warning(f"document_one failed for {str(thing)} with folder {output_folder}")
        return None

    # MR
    # should go in constructor, but what? no constructor
    settings.OUTPUT_FOLDER = output_folder
    page_out = render(describe(the_object), the_object, name)
    # MR output_folder + os.sep
    full_path = calculate_file_name(name, output_folder)

    with open(full_path, "w", encoding="utf-8") as file:
        file.write(page_out)
    print("wrote", name + ".html")
    return full_path
    # except (ImportError, ErrorDuringImport) as value:
    #     print(value)
    # return ""

modules_in_current()

Convert . shortcut into list of modules

Source code in pydoc_fork\commands.py
67
68
69
70
71
72
73
74
75
76
77
def modules_in_current() -> List[str]:
    """Convert . shortcut into list of modules"""
    current = os.getcwd()
    files = glob.glob(os.path.join(os.path.dirname(current), "*.py"))
    py_files = [os.path.basename(f)[:-3] for f in files if os.path.isdir(f)]
    folders = glob.glob(os.path.join(os.path.dirname(current), "*.py"))

    py_folders = [os.path.basename(f) for f in folders if os.path.isdir(f)]
    found = py_files + py_folders
    LOGGER.debug(f"Adding these modules from current folder to document {found}")
    return found

process_path_or_dot_name(files, output_folder, overwrite_existing=False)

Generate html documentation for all modules found at paths or dot notation module names.

Parameters:

files: List[str]

output_folder: str

overwrite_existing: bool

Returns:

List[str] – List of successfully documented modules

Source code in pydoc_fork\commands.py
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
def process_path_or_dot_name(
        files: List[str],
        output_folder: str,
        overwrite_existing: bool = False,
) -> List[str]:
    """
    Generate html documentation for all modules found at paths or
    dot notation module names.

    Args:
        files:
        output_folder:
        overwrite_existing:

    Returns:
        List of successfully documented modules
    """
    LOGGER.debug(f"process_path_or_dot_name for {files} and writing to {output_folder}")

    if not os.path.exists(output_folder):
        os.makedirs(output_folder)
    copy2(locate_file("templates/style.css", __file__), output_folder)

    _adjust_cli_sys_path()

    return write_docs_per_module(
        files, output_folder, skip_if_written=not overwrite_existing
    )

write_docs_live_module(output_folder, total_third_party=0, skip_if_written=False)

Write out HTML documentation for all modules in a directory tree.

Source code in pydoc_fork\commands.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def write_docs_live_module(
        output_folder: str,
        total_third_party: int = 0,
        skip_if_written: bool = False,
) -> List[str]:
    """Write out HTML documentation for all modules in a directory tree."""

    # This is going to handle filesystem paths, e.g. ./module/submodule.py
    # There will be ANOTHER method to handle MODULE paths, e.g. module.submodule"
    # Attempting to mix these two types is a bad idea.
    written: List[str] = []
    while settings.MENTIONED_MODULES and total_third_party <= 100:
        module = settings.MENTIONED_MODULES.pop()
        thing, name = module  # destructure it
        # should only be live modules or dot notation modules, not paths.
        full_path = calculate_file_name(name, output_folder)
        if os.path.exists(full_path) and skip_if_written:
            settings.MENTIONED_MODULES.discard(module)
        else:
            actual_full_path = document_one(thing, output_folder)
            total_third_party += 1
            if actual_full_path:
                written.append(actual_full_path)
            settings.MENTIONED_MODULES.discard(module)

    # TODO: make this a param
    return written

write_docs_per_module(modules, output_folder, skip_if_written=False)

Write out HTML documentation for all modules in a directory tree.

Source code in pydoc_fork\commands.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
def write_docs_per_module(
        modules: List[str],
        output_folder: str,
        skip_if_written: bool = False,
) -> List[str]:
    """Write out HTML documentation for all modules in a directory tree."""

    if "." in modules:
        modules.extend(modules_in_current())
    # This is going to handle filesystem paths, e.g. ./module/submodule.py
    # There will be ANOTHER method to handle MODULE paths, e.g. module.submodule"
    # Attempting to mix these two types is a bad idea.
    written: List[str] = []
    for module in modules:
        # file
        if module.lower().endswith(".py"):
            full_path = document_one(module[:-3], output_folder)
            if full_path:
                written.append(full_path)
        else:
            full_path = document_one(module, output_folder)
            if full_path:
                written.append(full_path)
            # "." needs to mean pwd... does it?
            full_paths = document_directory(".", output_folder, for_only=module)
            written.extend(full_paths)
    # One pass, not ready to walk entire tree.

    third_party_written = write_docs_live_module(output_folder, 0, skip_if_written)
    written.extend(third_party_written)
    return written

inspector special

This submodule has all the logic related to type-lookup and type-discovery. It has nothing to do with UI output other than it is creating a view model for the reporter submodule.

custom_types

Custom Types so mypy can check the code

TypeLike (Protocol)

This is a union of all sort of types

module_utils

Module Manipulation

ImportTimeError (Exception)

Errors that occurred while trying to import something to document it.

__init__(self, filename, exc_info) special

Set up

Source code in pydoc_fork\inspector\module_utils.py
26
27
28
29
30
def __init__(self, filename: Optional[str], exc_info: Tuple[Any, Any, Any]) -> None:
    """Set up"""
    self.filename = filename
    # pylint: disable=invalid-name
    self.exc, self.value, self.tb = exc_info
__str__(self) special

For display

Source code in pydoc_fork\inspector\module_utils.py
32
33
34
35
def __str__(self) -> str:
    """For display"""
    exc = self.exc.__name__
    return f"Problem in {self.filename} - {exc}: {self.value}"

importfile(path)

Import a Python source file or compiled file given its path.

Source code in pydoc_fork\inspector\module_utils.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def importfile(path: str) -> TypeLike:
    """Import a Python source file or compiled file given its path."""
    magic = importlib.util.MAGIC_NUMBER
    with open(path, "rb") as file:
        is_bytecode = magic == file.read(len(magic))
    filename = os.path.basename(path)
    name, _ = os.path.splitext(filename)
    if is_bytecode:
        loader = importlib._bootstrap_external.SourcelessFileLoader(name, path)
    else:
        loader = importlib._bootstrap_external.SourceFileLoader(name, path)
    # XXX We probably don't need to pass in the loader here.
    spec = importlib.util.spec_from_file_location(name, path, loader=loader)
    try:
        return cast(TypeLike, importlib._bootstrap._load(spec))
    # pylint: disable=broad-except
    except BaseException as import_error:
        LOGGER.warning(f"Skipping importfile for {name} at {path}, got a {import_error}")
        raise ImportTimeError(path, sys.exc_info()) from import_error

ispackage(path)

Guess whether a path refers to a package directory.

Source code in pydoc_fork\inspector\module_utils.py
124
125
126
127
128
129
130
def ispackage(path: str) -> bool:
    """Guess whether a path refers to a package directory."""
    if os.path.isdir(path):
        for ext in (".py", ".pyc"):
            if os.path.isfile(os.path.join(path, "__init__" + ext)):
                return True
    return False

locate(path, force_load=False)

Locate an object by name or dotted path, importing as necessary.

Source code in pydoc_fork\inspector\module_utils.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def locate(path: str, force_load: bool = False) -> Any:
    """Locate an object by name or dotted path, importing as necessary."""
    if "-" in path:
        # Not sure about this
        path = path.replace("-", "_")

    LOGGER.debug(f"locate(): locating {path}")
    parts = [part for part in path.split(".") if part]

    module, index = None, 0
    while index < len(parts):
        next_module = safe_import(".".join(parts[: index + 1]), force_load)
        if next_module:
            module, index = next_module, index + 1
        else:
            break
    if module:
        the_object = module
        # this errors?!
        # LOGGER.debug(f"putative module {str(the_object)}")
    else:
        the_object = builtins

    for part in parts[index:]:
        try:
            the_object = getattr(the_object, part)
        except AttributeError:
            LOGGER.debug(f"locate(): Don't think this is a module {the_object}")
            return None
    return the_object

safe_import(path, force_load=False, cache={})

Import a module; handle errors; return None if the module isn’t found.

If the module is found but an exception occurs, it’s wrapped in an ErrorDuringImport exception and reraised. Unlike import, if a package path is specified, the module at the end of the path is returned, not the package at the beginning. If the optional ‘force_load’ argument is True, we reload the module from disk (unless it’s a dynamic extension).

Source code in pydoc_fork\inspector\module_utils.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def safe_import(
    path: str,
    force_load: bool = False,
    cache: Dict[str, Any] = {},  # noqa - this is mutable on purpose!
) -> Any:
    """
    Import a module; handle errors; return None if the module isn't found.

    If the module *is* found but an exception occurs, it's wrapped in an
    ErrorDuringImport exception and reraised.  Unlike __import__, if a
    package path is specified, the module at the end of the path is returned,
    not the package at the beginning.  If the optional 'force_load' argument
    is True, we reload the module from disk (unless it's a dynamic extension).
    """
    try:
        # If force_load is True and the module has been previously loaded from
        # disk, we always have to reload the module.  Checking the file's
        # mtime isn't good enough (e.g. the module could contain a class
        # that inherits from another module that has changed).
        if force_load and path in sys.modules:
            if path not in sys.builtin_module_names:
                # Remove the module from sys.modules and re-import to try
                # and avoid problems with partially loaded modules.
                # Also remove any submodules because they won't appear
                # in the newly loaded module's namespace if they're already
                # in sys.modules.
                subs = [m for m in sys.modules if m.startswith(path + ".")]
                for key in [path] + subs:
                    # Prevent garbage collection.
                    cache[key] = sys.modules[key]
                    del sys.modules[key]
        module = __import__(path)
    # pylint: disable=broad-except
    except BaseException as import_error:
        # Did the error occur before or after the module was found?
        (exc, value, _) = info = sys.exc_info()
        if path in sys.modules:
            # An error occurred while executing the imported module.
            LOGGER.warning(f"Skipping safe_import for {path}, got a {import_error}")
            raise ImportTimeError(sys.modules[path].__file__, info) from import_error
        if exc is SyntaxError:
            # A SyntaxError occurred before we could execute the module.
            # MR : this isn't null safe.
            LOGGER.warning(f"Skipping safe_import for {path}, got a {str(exc)}")
            raise ImportTimeError(
                cast(SyntaxError, value).filename, info
            ) from import_error
        if issubclass(exc, ImportError) and cast(ImportError, value).name == path:
            LOGGER.warning(f"Skipping safe_import for {path}, got a {import_error}")
            LOGGER.warning(f"Cannot import this path: {path}")
            # No such module in the path.
            return None
        LOGGER.warning(f"Skipping safe_import for {path}, got a {import_error}")
        # Some other error occurred during the importing process.
        raise ImportTimeError(path, sys.exc_info()) from import_error
    for part in path.split(".")[1:]:
        try:
            module = getattr(module, part)
        except AttributeError:
            LOGGER.warning(f"While safe_import - {str(module)} does not have {part} from dot path {path}")
            return None
    return module

path_utils

Path Manipulation

locate_file(file_name, executing_file)

Find file relative to a source file, e.g. locate_file(“foo/bar.txt”, file)

Succeeds regardless to context of execution

Source code in pydoc_fork\inspector\path_utils.py
58
59
60
61
62
63
64
65
66
67
68
def locate_file(file_name: str, executing_file: str) -> str:
    """
    Find file relative to a source file, e.g.
    locate_file("foo/bar.txt", __file__)

    Succeeds regardless to context of execution
    """
    file_path = os.path.join(
        os.path.dirname(os.path.abspath(executing_file)), file_name
    )
    return file_path

utils

Unclassified utils

classify_class_attrs(the_object)

Wrap inspect.classify_class_attrs, with fixup for data descriptors.

Source code in pydoc_fork\inspector\utils.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
def classify_class_attrs(the_object: TypeLike) -> List[Tuple[str, str, type, object]]:
    """Wrap inspect.classify_class_attrs, with fixup for data descriptors."""
    results = []
    try:
        for (name, kind, cls, value) in inspect.classify_class_attrs(
            cast(type, the_object)
        ):
            if inspect.isdatadescriptor(value):
                kind = "data descriptor"
                if isinstance(value, property) and value.fset is None:
                    kind = "readonly property"
            results.append((name, kind, cls, value))
    except ValueError:
        LOGGER.warning(f"Skipping classify_class_attrs for {str(the_object)} got ValueError, maybe this is a Namespace")
        # py._xmlgen.Namespace
        # ValueError: Namespace class is abstract
    return results

classname(the_object, modname)

Get a class name and qualify it with a module name if necessary.

Source code in pydoc_fork\inspector\utils.py
172
173
174
175
176
177
def classname(the_object: TypeLike, modname: str) -> str:
    """Get a class name and qualify it with a module name if necessary."""
    name = the_object.__name__
    if the_object.__module__ != modname:
        name = the_object.__module__ + "." + name
    return name

describe(thing)

Produce a short description of the given thing.

Source code in pydoc_fork\inspector\utils.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def describe(thing: TypeLike) -> str:
    """Produce a short description of the given thing."""
    if inspect.ismodule(thing):
        if thing.__name__ in sys.builtin_module_names:
            return "built-in module " + thing.__name__
        if hasattr(thing, "__path__"):
            return "package " + thing.__name__
        return "module " + thing.__name__
    if inspect.isbuiltin(thing):
        return "built-in function " + thing.__name__
    if inspect.isgetsetdescriptor(thing):
        return f"getset descriptor {thing.__objclass__.__module__}.{thing.__objclass__.__name__}.{thing.__name__}"
    if inspect.ismemberdescriptor(thing):
        return f"member descriptor {thing.__objclass__.__module__}.{thing.__objclass__.__name__}.{thing.__name__}"
    if inspect.isclass(thing):
        return "class " + thing.__name__
    if inspect.isfunction(thing):
        return "function " + thing.__name__
    if inspect.ismethod(thing):
        return "method " + thing.__name__
    return type(thing).__name__

getdoc(the_object)

Get the doc string or comments for an object.

Source code in pydoc_fork\inspector\utils.py
166
167
168
169
def getdoc(the_object: TypeLike) -> str:
    """Get the doc string or comments for an object."""
    result = _getdoc(the_object) or inspect.getcomments(the_object)
    return result and re.sub("^ *\n", "", result.rstrip()) or ""

isdata(the_object)

Check if an object is of a type that probably means it’s data.

Source code in pydoc_fork\inspector\utils.py
180
181
182
183
184
185
186
187
188
189
def isdata(the_object: Any) -> bool:
    """Check if an object is of a type that probably means it's data."""
    return not (
        inspect.ismodule(the_object)
        or inspect.isclass(the_object)
        or inspect.isroutine(the_object)
        or inspect.isframe(the_object)
        or inspect.istraceback(the_object)
        or inspect.iscode(the_object)
    )

resolve(thing, force_load=False)

Given an object or a path to an object, get the object and its name.

Source code in pydoc_fork\inspector\utils.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def resolve(thing: Union[str, Any], force_load: bool = False) -> Tuple[Any, Any]:
    """Given an object or a path to an object, get the object and its name."""
    if isinstance(thing, str):
        the_object = locate(thing, force_load)
        if the_object is None:

            raise ImportError(
                """\
No Python documentation found for %r."""
                % thing
            )
        return the_object, thing

    name = getattr(thing, "__name__", None)
    if isinstance(name, str):
        return thing, name
    return thing, str(thing)  # HACK

sort_attributes(attrs, the_object)

Sort the attrs list in-place by _fields and then alphabetically by name

Source code in pydoc_fork\inspector\utils.py
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
def sort_attributes(attrs: List[Any], the_object: Union[TypeLike, type]) -> None:
    """Sort the attrs list in-place by _fields and then alphabetically by name"""
    # This allows data descriptors to be ordered according
    # to a _fields attribute if present.
    fields = getattr(the_object, "_fields", [])
    try:
        field_order = {name: i - len(fields) for (i, name) in enumerate(fields)}
    except TypeError:
        field_order = {}

    def key_function(attr: List[Any]) -> Tuple[Any, Any]:
        """Sorting function"""
        return field_order.get(attr[0], 0), attr[0]

    attrs.sort(key=key_function)

visiblename(name, all_things=None, obj=None)

Decide whether to show documentation on a variable.

Source code in pydoc_fork\inspector\utils.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
def visiblename(
    name: str, all_things: Optional[List[str]] = None, obj: Optional[Any] = None
) -> bool:
    """Decide whether to show documentation on a variable."""
    # Certain special names are redundant or internal.
    # XXX Remove __initializing__?
    if name in {
        # people use these for all sorts of things
        # "__author__",
        # "__credits__",
        # "__date__",
        # "__version__",
        #
        # These are internal implementation details USUALLY
        "__builtins__",
        "__cached__",
        "__doc__",
        "__file__",
        "__spec__",
        "__loader__",
        "__module__",
        "__name__",
        "__package__",
        "__path__",
        "__qualname__",
        "__slots__",
        "__dict__",
        "__weakref__",
    }:
        return False
    # Private names are hidden, but special names are displayed.
    if name.startswith("__") and name.endswith("__"):
        return True
    # Namedtuples have public fields and methods with a single leading underscore
    if name.startswith("_") and hasattr(obj, "_fields"):
        return True
    if all_things is not None:
        # only document that which the programmer exported in __all__
        return name in all_things
    return not name.startswith("_")

reporter special

This module has everything that turns type info into html.

It also has a lot of type-lookup and type-discovery logic woven into into it, what we’d call a bad separation of concerns.

format_class

Roughly a UI component for classes

Make a link for a class.

Source code in pydoc_fork\reporter\format_class.py
26
27
28
29
30
31
def classlink(the_object: Union[TypeLike, type], modname: str) -> str:
    """Make a link for a class."""
    name, module = the_object.__name__, sys.modules.get(the_object.__module__)
    if hasattr(module, name) and getattr(module, name) is the_object:
        return f'<a href="{module.__name__}.html#{name}">{classname(cast(TypeLike, the_object), modname)}</a>'
    return classname(the_object, modname)

docclass(the_object, name='', mod='', funcs=None, classes=None)

Produce HTML documentation for a class object.

Source code in pydoc_fork\reporter\format_class.py
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
def docclass(
    the_object: TypeLike,
    name: str = "",
    mod: str = "",
    funcs: Optional[Dict[str, str]] = None,
    classes: Optional[Dict[str, str]] = None,
) -> str:
    """Produce HTML documentation for a class object."""
    funcs = funcs or {}
    classes = classes or {}

    real_name = the_object.__name__
    name = name or real_name
    bases = the_object.__bases__

    contents: List[str] = []
    push = contents.append

    class HorizontalRule:
        """Cute little class to pump out a horizontal rule between sections."""

        def __init__(self) -> None:
            self.need_one = 0

        def maybe(self) -> None:
            """Skip"""
            if self.need_one:
                push("<hr>\n")
            self.need_one = 1

    # pylint:disable=invalid-name
    hr = HorizontalRule()

    # List the mro, if non-trivial.
    mro = deque(inspect.getmro(cast(type, the_object)))
    if len(mro) > 2:
        hr.maybe()
        push("<dl><dt>Method resolution order:</dt>\n")
        for base in mro:
            push(f"<dd>{classlink(base, the_object.__module__)}</dd>\n")
        push("</dl>\n")

    def spill(
        msg: str, attrs_in: List[Any], predicate: Callable[[Any], Any]
    ) -> List[Any]:
        """Not sure"""
        ok, attrs = _split_list(attrs_in, predicate)
        if ok:
            hr.maybe()
            push(msg)
            for name, _, _, value in ok:
                # noinspection PyBroadException
                try:
                    value = getattr(the_object, name)
                except Exception:  # nosec
                    # Some descriptors may meet a failure in their __get__.
                    # (bug #1785)
                    push(
                        document_data(
                            value,
                            name,
                            # mod, unused
                        )
                    )
                else:
                    # circular ref
                    # pylint: disable=import-outside-toplevel
                    from pydoc_fork.reporter.format_page import document

                    push(
                        document(
                            value, name, mod, funcs, classes, module_dict, the_object
                        )
                    )
                push("\n")
        return attrs

    def spilldescriptors(
        msg: str,
        attrs_in: List[Any],  # Tuple[str, str, type, "object"]
        predicate: Callable[[Any], bool],
    ) -> List[Any]:
        """Not sure"""
        ok, attrs = _split_list(attrs_in, predicate)
        if ok:
            hr.maybe()
            push(msg)
            for name, _, _, value in ok:
                push(
                    document_data(
                        value,
                        name,
                        # mod, ignored
                    )
                )
        return attrs

    def spilldata(
        msg: str, attrs_in: List[Any], predicate: Callable[[Any], bool]
    ) -> List[Any]:
        """Not sure"""
        ok, attrs = _split_list(attrs_in, predicate)
        if ok:
            hr.maybe()
            push(msg)
            for name, _, __, value in ok:
                base = docother(
                    getattr(the_object, name),
                    name,
                    # mod ignored
                )
                found_doc = getdoc(value)
                if not found_doc:
                    push(f"<dl><dt>{base}</dl>\n")
                else:
                    found_doc = markup(getdoc(value), funcs, classes, module_dict)
                    found_doc = f"<dd><tt>{found_doc}</tt>"
                    push(f"<dl><dt>{base}{found_doc}</dl>\n")
                push("\n")
        return attrs

    attrs = [
        (name, kind, cls, value)
        for name, kind, cls, value in classify_class_attrs(the_object)
        if visiblename(name, obj=the_object)
    ]

    module_dict = {}
    for key, _, _, value in attrs:
        module_dict[key] = anchor = "#" + name + "-" + key
        try:
            value = getattr(the_object, name)
        except Exception:  # nosec
            # Some descriptors may meet a failure in their __get__.
            # (bug #1785)
            pass  # nosec
        try:
            # The value may not be hashable (e.g., a data attr with
            # a dict or list value).
            module_dict[value] = anchor
        except TypeError:
            pass  # nosec

    while attrs:
        if mro:
            this_class = mro.popleft()
        else:
            this_class = attrs[0][2]

        is_this_class: Callable[[Any], Any] = lambda t: t[2] is this_class
        attrs, inherited = _split_list(attrs, is_this_class)

        if the_object is not builtins.object and this_class is builtins.object:
            attrs = inherited
            continue
        if this_class is the_object:
            tag = "defined here"
        else:
            tag = f"inherited from {classlink(this_class, the_object.__module__)}"
        tag += ":<br>\n"

        sort_attributes(attrs, the_object)

        # feature to remove typing annotations cruft.
        for kind in attrs.copy():
            module_name = inspect.getmodule(kind)
            if module_name and module_name.__name__ in settings.SKIP_MODULES:
                attrs.remove(kind)

        # Pump out the attrs, segregated by kind.
        is_method: Callable[[Any], Any] = lambda t: t[1] == "method"
        attrs = spill(f"Methods {tag}", attrs, is_method)
        is_class: Callable[[Any], Any] = lambda t: t[1] == "class method"
        attrs = spill(f"Class methods {tag}", attrs, is_class)
        is_static: Callable[[Any], Any] = lambda t: t[1] == "static method"
        attrs = spill(f"Static methods {tag}", attrs, is_static)
        is_read_only: Callable[[Any], Any] = lambda t: t[1] == "readonly property"
        attrs = spilldescriptors(
            f"Readonly properties {tag}",
            attrs,
            is_read_only,
        )
        is_data_descriptor: Callable[[Any], Any] = lambda t: t[1] == "data descriptor"
        attrs = spilldescriptors(f"Data descriptors {tag}", attrs, is_data_descriptor)
        is_data: Callable[[Any], Any] = lambda t: t[1] == "data"
        attrs = spilldata(f"Data and other attributes {tag}", attrs, is_data)
        assert not attrs  # nosec
        attrs = inherited

    contents_as_string = "".join(contents)  # type got redefined

    if name == real_name:
        title = f'<a name="{name}">class <strong>{real_name}</strong></a>'
    else:
        title = f'<strong>{name}</strong> = <a name="{name}">class {real_name}</a>'
    if bases:
        parents = []
        for base in bases:
            parents.append(classlink(base, the_object.__module__))
        title = title + f"({', '.join(parents)})"

    decl = ""
    try:
        signature = inspect.signature(the_object)
    except (ValueError, TypeError):
        signature = None
    if signature:
        argument_specification = str(signature)
        if argument_specification and argument_specification != "()":
            # this will cause double escape on ->
            # escape(argument_specification)
            decl = name + argument_specification + "\n\n"

    doc = getdoc(the_object)
    if decl:
        doc = decl + (doc or "")
    doc = markup(doc, funcs, classes, module_dict)
    doc = doc and f"<tt>{doc}<br>&nbsp;</tt>"

    return section(title, "#000000", "#ffc8d8", contents_as_string, 3, doc)

format_tree(tree, modname, parent=None)

Creates a representation of class inheritance.

Source code in pydoc_fork\reporter\format_class.py
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
def format_tree(tree: List[Any], modname: str, parent: Optional[Any] = None) -> str:
    """
    Creates a representation of class inheritance.
    """

    # """Produce HTML for a class tree as given by inspect.getclasstree()."""
    result = ""
    for entry in tree:
        class_object = entry
        # pylint: disable=unidiomatic-typecheck
        if type(entry) is type(()):  # noqa - not sure of switching to isinstance
            class_object, bases = entry
            result = (
                result + f'<dt><span style="font-family:{inline_styles.SAN_SERIF}">'
            )
            result = result + classlink(class_object, modname)
            if bases and bases != (parent,):
                parents = []
                for base in bases:
                    parents.append(classlink(base, modname))
                result = result + "(" + ", ".join(parents) + ")"
            result = result + "\n</span></dt>"

        elif type(entry) is type([]):  # noqa - not sure of switching to isinstance
            tree = format_tree(entry, modname, class_object)
            result = result + f"<dd>\n{tree}</dd>\n"
    return f"<dl>\n{result}</dl>\n"

format_data

Roughly a UI component for variables and their values

document_data(the_object, name='')

Produce html documentation for a data descriptor.

Source code in pydoc_fork\reporter\format_data.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def document_data(
    the_object: TypeLike,
    name: str = "",
) -> str:
    """Produce html documentation for a data descriptor."""

    results: List[str] = []

    if name:
        results.append(f"<dl><dt><strong>{name}</strong></dt>\n")
    doc = markup(getdoc(the_object))
    if doc:
        results.append(f"<dd><tt>{doc}</tt></dd>\n")
    results.append("</dl>\n")

    return "".join(results)

format_module

Roughly a UI component for modules

docmodule(the_object)

Produce HTML documentation for a module object.

Source code in pydoc_fork\reporter\format_module.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
def docmodule(
    the_object: TypeLike,
) -> str:
    """Produce HTML documentation for a module object."""
    # circular ref
    from pydoc_fork.reporter.format_page import document

    name = the_object.__name__

    try:
        all_things = None if settings.DOCUMENT_INTERNALS else the_object.__all__
    except AttributeError:
        all_things = None
    parts = name.split(".")
    links = []
    for i in range(len(parts) - 1):
        link_url = ".".join(parts[: i + 1])
        link_text = parts[i]
        links.append(
            f'<a href="{link_url}.html"><span style="color:{inline_styles.MODULE_LINK}">{link_text}</span></a>'
        )
    linked_name = ".".join(links + parts[-1:])
    head = f"<big><big><strong>{linked_name}</strong></big></big>"
    try:
        path = inspect.getabsfile(cast(type, the_object))
        # MR : Make relative
        output_folder_path = os.path.normcase(os.path.abspath(settings.OUTPUT_FOLDER))
        path = os.path.relpath(path, output_folder_path).replace("\\", "/")
        # end MR
        # uh, oh, forgot why I wrote this
        # url = urllib.parse.quote(path)
        # MR
        file_link_text = file_link(path, path)
    except TypeError:
        file_link_text = "(built-in)"
    info = []
    # TODO: Include the rest of the meta data
    if hasattr(the_object, "__version__"):
        version = str(the_object.__version__)
        if version[:11] == "$" + "Revision: " and version[-1:] == "$":
            version = version[11:-1].strip()
        info.append(f"version {escape(version)}")
    if hasattr(the_object, "__date__"):
        info.append(escape(str(the_object.__date__)))
    if info:
        head = head + f" ({', '.join(info)})"
    document_location = getdocloc(the_object)
    if document_location is not None:
        # Was this just a bug? document_location/locals?
        # document_location = '<br><a href="%(docloc)s">Module Reference</a>' % locals()
        document_location = f'<br><a href="{document_location}">Module Reference</a>'
    else:
        document_location = ""
    result = heading(
        head,
        "#ffffff",
        "#7799ee",
        '<a href=".">index</a><br>' + file_link_text + document_location,
    )

    # this will get `import foo` but ignore `from foo import bar`
    # And bar gets no doc string love either!
    modules = inspect.getmembers(the_object, inspect.ismodule)

    for to_remove in settings.SKIP_MODULES:
        for module_info in modules:
            candidate_module, _ = module_info
            if candidate_module == to_remove:
                try:
                    modules.remove(module_info)
                except ValueError:
                    pass
    modules_by_import_from = set()
    classes, class_dict = [], {}
    for key, value in inspect.getmembers(the_object, inspect.isclass):
        _class_module = inspect.getmodule(value)
        if _class_module and _class_module is not the_object:
            if _class_module.__name__ not in settings.SKIP_MODULES:
                modules_by_import_from.add((None, _class_module))
                settings.MENTIONED_MODULES.add((_class_module, _class_module.__name__))
        # if __all__ exists, believe it.  Otherwise use old heuristic.
        if (
            # TODO put doc internals switch here
            # all_things is not None or
            (inspect.getmodule(value) or the_object)
            is the_object
        ):
            if visiblename(key, all_things, the_object):
                classes.append((key, value))
                class_dict[key] = class_dict[value] = "#" + key
    for key, value in classes:
        for base in value.__bases__:
            key, modname = base.__name__, base.__module__
            module = sys.modules.get(modname)
            if (
                modname != name
                and module
                and hasattr(module, key)
                and getattr(module, key) is base
                and key not in class_dict
            ):
                class_dict[key] = class_dict[base] = modname + ".html#" + key
    funcs, function_dict = [], {}
    for key, value in inspect.getmembers(the_object, inspect.isroutine):
        # if __all__ exists, believe it.  Otherwise use old heuristic.
        _func_module = inspect.getmodule(value)
        # why does this sometimes return no module?
        if _func_module and _func_module is not the_object:
            if _func_module.__name__ not in settings.SKIP_MODULES:
                modules_by_import_from.add((None, _func_module))
                settings.MENTIONED_MODULES.add((_func_module, _func_module.__name__))
        if (
            True
            # TODO put doc internals switch here
            # all_things is not None or # __all__ as scope limiter
            # inspect.isbuiltin(value)  # thing w/o module
            # or inspect.getmodule(value) is the_object # from foo import bar
        ) and visiblename(key, all_things, the_object):
            funcs.append((key, value))
            function_dict[key] = "#-" + key
            if inspect.isfunction(value):
                function_dict[value] = function_dict[key]
    data = []
    for key, value in inspect.getmembers(the_object, isdata):
        if inspect.getmodule(type(value)).__name__ in settings.SKIP_MODULES:
            continue
        if visiblename(key, all_things, the_object):
            data.append((key, value))

    doc = markup(getdoc(the_object), function_dict, class_dict)
    doc = doc and f"<tt>{doc}</tt>"
    result = result + f"<p>{doc}</p>\n"

    if hasattr(the_object, "__path__"):
        module_packages = []
        for _, modname, is_package in pkgutil.iter_modules(the_object.__path__):
            if modname not in settings.SKIP_MODULES:
                module_packages.append((modname, name, is_package, 0))
        module_packages.sort()
        contents_string = multicolumn(module_packages, module_package_link)
        result = result + bigsection(
            "Package Contents", "#ffffff", "#aa55cc", contents_string
        )
    elif modules:
        contents_string = multicolumn(modules, lambda t: modulelink(t[1]))
        result = result + bigsection("Modules", "#ffffff", "#aa55cc", contents_string)

    if modules_by_import_from:
        contents_string = multicolumn(
            list(modules_by_import_from), lambda t: modulelink(list(t)[1])
        )
        result = result + bigsection(
            "`from` Modules", "#ffffff", "#aa55cc", contents_string
        )

    if classes:
        class_list = [value for (key, value) in classes]
        # MR: boolean type safety
        contents_list = [format_tree(inspect.getclasstree(class_list, True), name)]
        for key, value in classes:
            contents_list.append(document(value, key, name, function_dict, class_dict))
        result = result + bigsection(
            "Classes", "#ffffff", "#ee77aa", " ".join(contents_list)
        )
    if funcs:
        contents_list = []
        for key, value in funcs:
            contents_list.append(document(value, key, name, function_dict, class_dict))
        result = result + bigsection(
            "Functions", "#ffffff", "#eeaa77", " ".join(contents_list)
        )
    if data:
        contents_list = []
        for key, value in data:
            contents_list.append(document(value, key))
        result = result + bigsection(
            "Data", "#ffffff", "#55aa55", "<br>\n".join(contents_list)
        )
    if hasattr(the_object, "__author__"):
        contents = markup(str(the_object.__author__))
        result = result + bigsection("Author", "#ffffff", "#7799ee", contents)
    if hasattr(the_object, "__credits__"):
        contents = markup(str(the_object.__credits__))
        result = result + bigsection("Credits", "#ffffff", "#7799ee", contents)

    return result

getdocloc(the_object, basedir='C:\\Users\\matth\\AppData\\Local\\Programs\\Python\\Python39\\Lib')

Return the location of module docs or None

Source code in pydoc_fork\reporter\format_module.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def getdocloc(the_object: TypeLike, basedir: str = STDLIB_BASEDIR) -> Optional[str]:
    """Return the location of module docs or None"""
    try:
        file = inspect.getabsfile(cast(type, the_object))
    except TypeError:
        file = "(built-in)"

    basedir = os.path.normcase(basedir)
    is_known_stdlib = the_object.__name__ in (
        "errno",
        "exceptions",
        "gc",
        "imp",
        "marshal",
        "posix",
        "signal",
        "sys",
        "_thread",
        "zipimport",
    )
    is_module = isinstance(the_object, type(os))
    is_in_pythons_folder = file.startswith(basedir) and not file.startswith(
        os.path.join(basedir, "site-packages")
    )
    # # This is nasty special case coding, how many more special cases are there?
    # is_exception =the_object.__name__ in ("xml.etree", "test.pydoc_mod")
    # # special case for etree
    # "https://docs.python.org/3/library/xml.etree.elementtree.html"

    if (
        is_module
        and (is_known_stdlib or is_in_pythons_folder)
        and settings.PREFER_DOCS_PYTHON_ORG
    ):
        if settings.PYTHONDOCS.startswith(("http://", "https://")):
            doc_loc = (
                f"{settings.PYTHONDOCS.rstrip('/')}/{the_object.__name__.lower()}.html"
            )
        else:
            doc_loc = os.path.join(
                settings.PYTHONDOCS, the_object.__name__.lower() + ".html"
            )
    else:
        doc_loc = None
    return doc_loc

Make a link for a module.

Source code in pydoc_fork\reporter\format_module.py
74
75
76
77
78
79
80
81
82
83
84
def modulelink(the_object: TypeLike) -> str:
    """Make a link for a module."""
    url = f"{the_object.__name__}.html"
    internet_link = getdocloc(the_object)

    if internet_link and settings.PREFER_DOCS_PYTHON_ORG:
        url = internet_link
    # BUG: doesn't take into consideration an alternate base
    if not internet_link:
        settings.MENTIONED_MODULES.add((the_object, the_object.__name__))
    return f'<a href="{url}">{the_object.__name__}</a>'

format_other

Fallback docs

docother(the_object, name='')

Produce HTML documentation for a data object.

Source code in pydoc_fork\reporter\format_other.py
 8
 9
10
11
12
13
14
def docother(
    the_object: TypeLike,
    name: str = "",
) -> str:
    """Produce HTML documentation for a data object."""
    lhs = name and f"<strong>{name}</strong> = " or ""
    return lhs + html_repr(the_object)

format_page

Roughly page and top level containers

document(the_object, name='', *args)

Generate documentation for an object. This also part of the public API of class

Types of : Module, class, routine, data descriptor, “other” are supported

Modules ignore 1st name.

Public API doesn’t call with *args Args are: name, fdict, cdict (name twice?) mod, funcs, classes, mdict, the_object

Source code in pydoc_fork\reporter\format_page.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def document(the_object: TypeLike, name: str = "", *args: Any) -> str:  # Null safety
    """Generate documentation for an object.
    This also part of the public API of class

    Types of : Module, class, routine, data descriptor, "other" are supported

    Modules ignore 1st name.

    Public API doesn't call with *args
    Args are:
    name, fdict, cdict (name twice?)
    mod, funcs, classes, mdict, the_object
    """
    args = (the_object, name) + args

    # 'try' clause is to attempt to handle the possibility that inspect
    # identifies something in a way that pydoc itself has issues handling;
    # think 'super' and how it is a descriptor (which raises the exception
    # by lacking a __name__ attribute) and an instance.
    # try:
    if inspect.ismodule(the_object):
        return docmodule(the_object)
    if inspect.isclass(the_object):
        return docclass(*args)
    if inspect.isroutine(the_object):
        return docroutine(*args)
    # except AttributeError:
    #     pass  # nosec
    if inspect.isdatadescriptor(the_object):
        return document_data(the_object, name)
    return docother(the_object, name)

index(directory, shadowed=None)

Generate an HTML index for a directory of modules.

Source code in pydoc_fork\reporter\format_page.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def index(directory: str, shadowed: Optional[Dict[str, Any]] = None) -> str:
    """Generate an HTML index for a directory of modules."""
    module_packages = []
    if shadowed is None:
        shadowed = {}
    for _, name, is_package in pkgutil.iter_modules([directory]):
        if any((0xD800 <= ord(ch) <= 0xDFFF) for ch in name):
            # ignore a module if its name contains a surrogate character
            continue
        module_packages.append((name, "", is_package, name in shadowed))
        shadowed[name] = 1

    module_packages.sort()
    contents = multicolumn(module_packages, module_package_link)
    return bigsection(directory, "#ffffff", "#ee77aa", contents)

page(title, contents)

Format an HTML page.

This is part of the public API

Source code in pydoc_fork\reporter\format_page.py
27
28
29
30
31
32
33
34
def page(title: str, contents: str) -> str:
    """Format an HTML page.

    This is part of the public API
    """
    template = JINJA_ENV.get_template("page.jinja2")
    result = template.render(title=title, contents=contents)
    return result

render(title, the_object, name)

Compose two functions

Source code in pydoc_fork\reporter\format_page.py
22
23
24
def render(title: str, the_object: TypeLike, name: str) -> str:
    """Compose two functions"""
    return page(title, document(the_object, name))

format_routine

Roughly a UI component for routines

docroutine(the_object, name='', mod='', funcs=None, classes=None, methods=None, class_object=None)

Produce HTML documentation for a function or method object.

Source code in pydoc_fork\reporter\format_routine.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def docroutine(
    the_object: TypeLike,
    name: str = "",
    mod: str = "",
    funcs: Optional[Dict[str, Any]] = None,  # noqa - clean up later
    classes: Optional[Dict[str, Any]] = None,  # noqa - clean up later
    methods: Optional[Dict[str, Any]] = None,  # noqa - clean up later
    class_object: Optional[TypeLike] = None,
) -> str:
    """Produce HTML documentation for a function or method object."""
    if not funcs:
        funcs = {}
    if not classes:
        classes = {}
    if not methods:
        methods = {}
    # AttributeError: 'cached_property' object has no attribute '__name__'
    try:
        real_name = the_object.__name__
    except AttributeError:
        real_name = None
    name = name or real_name
    anchor = (class_object and class_object.__name__ or "") + "-" + name
    note = ""
    skip_docs = 0
    if _is_bound_method(the_object):
        imported_class = the_object.__self__.__class__
        if class_object:
            if imported_class is not class_object:
                note = " from " + classlink(imported_class, mod)
        else:
            if the_object.__self__ is not None:
                link = classlink(the_object.__self__.__class__, mod)
                note = f" method of {link} instance"
            else:
                link = classlink(imported_class, mod)
                note = f" unbound {link} method"

    if inspect.iscoroutinefunction(the_object) or inspect.isasyncgenfunction(
        the_object
    ):
        async_qualifier = "async "
    else:
        async_qualifier = ""

    if name == real_name:
        title = f'<a name="{anchor}"><strong>{real_name}</strong></a>'
    else:
        if class_object and inspect.getattr_static(class_object, real_name, []) is the_object:
            real_link = f'<a href="#{class_object.__name__ + "-" + real_name}">{real_name}</a>'
            skip_docs = 1
        else:
            real_link = real_name
        title = f'<a name="{anchor}"><strong>{name}</strong></a> = {real_link}'
    argument_specification = None
    if inspect.isroutine(the_object):
        try:
            signature: Optional[inspect.Signature] = inspect.signature(the_object)
        except (ValueError, TypeError):
            signature = None
        if signature:
            argument_specification = str(signature)
            if real_name == "<lambda>":
                title = f"<strong>{name}</strong> <em>lambda</em> "
                # XXX lambda's won't usually have func_annotations['return']
                # since the syntax doesn't support but it is possible.
                # So removing parentheses isn't truly safe.
                argument_specification = argument_specification[
                    1:-1
                ]  # remove parentheses
    if not argument_specification:
        argument_specification = "(...)"

    decl = (
        async_qualifier
        + title
        + escape(argument_specification)
        + (
            note
            and disabled_text(
                f'<span style="font-family:{inline_styles.SAN_SERIF}">{note}</span>'
            )
        )
    )

    if skip_docs:
        return f"<dl><dt>{decl}</dt></dl>\n"

    doc = markup(getdoc(the_object), funcs, classes, methods)
    doc = doc and f"<dd><tt>{doc}</tt></dd>"
    return f"<dl><dt>{decl}</dt>{doc}</dl>\n"

formatter_html

Roughly components

STDLIB_BASEDIR

Formatter class for HTML documentation.

MarkupSyntax (Enum)

Syntax type we assume comments are using

bigsection(title, fgcol, bgcol, contents, width=6, prelude='', marginalia='', gap=' ')

Format a section with a big heading.

Source code in pydoc_fork\reporter\formatter_html.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def bigsection(
    title: str,
    fgcol: str,
    bgcol: str,
    contents: str,
    width: int = 6,  # used by marginalia?
    prelude: str = "",
    marginalia: str = "",  # not used
    gap: str = "&nbsp;",  # not used
) -> str:
    """Format a section with a big heading."""
    title = f"<big><strong>{title}</strong></big>"
    # prefer explicit interfaces over secret hidden ones
    return section(title, fgcol, bgcol, contents, width, prelude, marginalia, gap)

disabled_text(text)

Wrap in grey

Source code in pydoc_fork\reporter\formatter_html.py
115
116
117
def disabled_text(text: str) -> str:
    """Wrap in grey"""
    return f'<span style="color:{inline_styles.DISABLED_TEXT}">{text}</span>'

escape(value)

HTML safe repr and escape

Source code in pydoc_fork\reporter\formatter_html.py
34
35
36
37
38
39
40
def escape(value: Any) -> str:
    """HTML safe repr and escape"""
    _repr_instance = HTMLRepr()
    result = _repr_instance.escape(value)
    if "&amp;gt;" in result:
        print("possible double escape")
    return result

Make a link to source file.

Source code in pydoc_fork\reporter\formatter_html.py
149
150
151
def file_link(url: str, path: str) -> str:
    """Make a link to source file."""
    return f'<a href="file:{url}">{path}</a>'

heading(title, fgcol, bgcol, extras='')

Format a page heading.

Source code in pydoc_fork\reporter\formatter_html.py
43
44
45
46
def heading(title: str, fgcol: str, bgcol: str, extras: str = "") -> str:
    """Format a page heading."""
    template = JINJA_ENV.get_template("heading.jinja2")
    return template.render(title=title, fgcol=fgcol, bgcol=bgcol, extras=extras)

html_repr(value)

Turn method into function

Source code in pydoc_fork\reporter\formatter_html.py
28
29
30
31
def html_repr(value: Any) -> str:  # noqa - unhiding could break code?
    """Turn method into function"""
    _repr_instance = HTMLRepr()
    return _repr_instance.repr(value)

markup(text, funcs=None, classes=None, methods=None)

Replace all linkable things with links of appropriate syntax.

Handle either an adhoc markup language, or RST or Markdown. funcs, classes, methods are name/symbol to URL maps.

Source code in pydoc_fork\reporter\formatter_html.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
def markup(
    text: str,
    funcs: Optional[Dict[str, str]] = None,
    classes: Optional[Dict[str, str]] = None,
    methods: Optional[Dict[str, str]] = None,
) -> str:
    """
    Replace all linkable things with links of appropriate syntax.

    Handle either an adhoc markup language, or RST or Markdown.
    funcs, classes, methods are name/symbol to URL maps.
    """
    funcs = funcs or {}
    classes = classes or {}
    methods = methods or {}

    syntax = MarkupSyntax.NOTHING

    def nothing(_: str) -> str:
        """Does nothing"""
        return _

    if syntax == MarkupSyntax.NOTHING:
        _preformat = preformat
        markup_to_html = nothing
    elif syntax == MarkupSyntax.RST:
        _preformat = nothing
        markup_to_html = rst_to_html
        # make_rst_link =
    elif syntax == MarkupSyntax.MARKDOWN:
        _preformat = nothing
        markup_to_html = markdown.markdown
        # make_markdown_link =
    else:
        raise NotImplementedError()

    results = []
    here = 0
    pattern = re.compile(
        r"\b((http|https|ftp)://\S+[\w/]|"
        r"RFC[- ]?(\d+)|"
        r"PEP[- ]?(\d+)|"
        r"(self\.)?(\w+))"
    )
    while True:
        match = pattern.search(text, here)
        if not match:
            break
        start, end = match.span()
        results.append(_preformat(text[here:start]))

        the_all, scheme, rfc, pep, self_dot, name = match.groups()
        if scheme:
            # Hyperlink actual urls
            url = _preformat(the_all).replace('"', "&quot;")
            results.append(f'<a href="{url}">{url}</a>')
        elif rfc:
            url = "https://www.rfc-editor.org/rfc/rfc%d.txt" % int(rfc)
            results.append(f'<a href="{url}">{_preformat(the_all)}</a>')
        elif pep:
            url = "https://www.python.org/dev/peps/pep-%04d/" % int(pep)
            results.append(f'<a href="{url}">{_preformat(the_all)}</a>')
        elif self_dot:
            # Create a link for methods like 'self.method(...)'
            # and use <strong> for attributes like 'self.attr'
            if text[end : end + 1] == "(":
                results.append("self." + namelink(name, methods))
            else:
                results.append(f"self.<strong>{name}</strong>")
        elif text[end : end + 1] == "(":
            results.append(namelink(name, methods, funcs, classes))
        else:
            # This assumes everything else is a class!!
            results.append(namelink(name, classes))
        here = end
    # plain text with links to HTML
    results.append(_preformat(text[here:]))
    rejoined_semi_html = "".join(results)
    return markup_to_html(rejoined_semi_html)

Make a link for a module or package to display in an index.

Source code in pydoc_fork\reporter\formatter_html.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
def module_package_link(module_package_info: Tuple[str, str, str, str]) -> str:
    """Make a link for a module or package to display in an index."""
    name, path, ispackage, shadowed = module_package_info
    try:
        settings.MENTIONED_MODULES.add((resolve(path + "." + name)[0], name))
    except (ImportTimeError, ImportError):
        print(f"Can't import {name}, won't doc")
    if shadowed:
        return disabled_text(name)
    if path:
        url = f"{path}.{name}.html"
    else:
        url = f"{name}.html"
    if ispackage:
        text = f"<strong>{name}</strong>&nbsp;(package)"
    else:
        text = name
    return f'<a href="{url}">{text}</a>'

multicolumn(the_list, the_format, cols=4)

Format a list of items into a multi-column list.

Source code in pydoc_fork\reporter\formatter_html.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
def multicolumn(
    the_list: Union[Sequence[Tuple[Any, str, Any, int]], Sequence[Tuple[str, Any]]],
    the_format: Callable[[Any], str],
    cols: int = 4,
) -> str:
    """Format a list of items into a multi-column list."""
    result = ""
    rows = (len(the_list) + cols - 1) // cols
    for col in range(cols):
        result = result + '<td width="%d%%" valign=top>' % (100 // cols)
        for i in range(rows * col, rows * col + rows):
            if i < len(the_list):
                result = result + the_format(the_list[i]) + "<br>\n"
        result = result + "</td>"
    return f'<table width="100%%" summary="list"><tr>{result}</tr></table>'

Make a link for an identifier, given name-to-URL mappings.

Source code in pydoc_fork\reporter\formatter_html.py
120
121
122
123
124
125
126
def namelink(name: str, *dicts: Dict[str, str]) -> str:
    """Make a link for an identifier, given name-to-URL mappings."""
    for the_dict in dicts:
        if name in the_dict:
            return f'<a href="{the_dict[name]}">{name}</a>'
    # LOGGER.warning(f"Failed to find link for {name}")
    return name

preformat(text)

Format literal preformatted text.

Source code in pydoc_fork\reporter\formatter_html.py
90
91
92
93
94
95
def preformat(text: str) -> str:
    """Format literal preformatted text."""
    text = escape(text.expandtabs())
    return replace(
        text, "\n\n", "\n \n", "\n\n", "\n \n", " ", "&nbsp;", "\n", "<br>\n"
    )

section(title, fgcol, bgcol, contents, width=6, prelude='', marginalia='', gap=' ')

Format a section with a heading.

Source code in pydoc_fork\reporter\formatter_html.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def section(
    title: str,
    fgcol: str,
    bgcol: str,
    contents: str,
    width: int = 6,  # used by marginalia?
    prelude: str = "",
    marginalia: str = "",  # not used
    gap: str = "&nbsp;",  # not used
) -> str:
    """Format a section with a heading."""
    if marginalia is None:
        marginalia = "<tt>" + "&nbsp;" * width + "</tt>"
    template = JINJA_ENV.get_template("section.jinja2")
    return template.render(
        title=title,
        fgcol=fgcol,
        bgcol=bgcol,
        marginalia=marginalia,
        prelude=prelude,
        contents=contents,
        gap=gap,
    )

html_repr_class

Class for safely making an HTML representation of a Python object.

HTMLRepr (Repr)

Class for safely making an HTML representation of a Python object.

__init__(self) special

Some maximums

Source code in pydoc_fork\reporter\html_repr_class.py
13
14
15
16
17
18
def __init__(self) -> None:
    """Some maximums"""
    Repr.__init__(self)
    self.maximum_list = self.maximum_tuple = 20
    self.maximum_dict = 10
    self.maximum_string = self.maximum_other = 100
escape(text) staticmethod

Simple html escaping

Source code in pydoc_fork\reporter\html_repr_class.py
21
22
23
24
25
26
27
@staticmethod
def escape(text: str) -> str:
    """Simple html escaping"""
    result = replace(text, "&", "&amp;", "<", "&lt;", ">", "&gt;")
    # if "&amp;" in result:
    #     print("possible double escape")
    return result
repr(self, x)

Delegates to Repr.repr

Source code in pydoc_fork\reporter\html_repr_class.py
29
30
31
def repr(self, x: Any) -> str:  # noqa - unhiding could break code?
    """Delegates to Repr.repr"""
    return Repr.repr(self, x)
repr1(self, x, level)

Not sure, is dead code?

Source code in pydoc_fork\reporter\html_repr_class.py
33
34
35
36
37
38
39
def repr1(self, x: Any, level: int) -> str:
    """Not sure, is dead code?"""
    if hasattr(type(x), "__name__"):
        method_name = "repr_" + "_".join(type(x).__name__.split())
        if hasattr(self, method_name):
            return cast(str, getattr(self, method_name)(x, level))
    return self.escape(cram(stripid(repr(x)), self.maximum_other))
repr_instance(self, x, level)

Repr, but squash it into a window

Source code in pydoc_fork\reporter\html_repr_class.py
59
60
61
62
63
64
65
66
def repr_instance(self, x: Any, level: int) -> str:
    """Repr, but squash it into a window"""
    # noinspection PyBroadException
    try:
        return self.escape(cram(stripid(repr(x)), self.maximum_string))
    # pylint: disable=broad-except
    except BaseException:
        return self.escape(f"<{x.__class__.__name__} instance>")
repr_str(self, x, _)

Repr, but squash it into a window

Source code in pydoc_fork\reporter\html_repr_class.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def repr_string(self, x: str, _: int) -> str:
    """Repr, but squash it into a window"""
    test = cram(x, self.maximum_string)
    test_repr = repr(test)
    if "\\" in test and "\\" not in replace(test_repr, r"\\", ""):
        # Backslashes are only literal in the string and are never
        # needed to make any special characters, so show a raw string.
        return "r" + test_repr[0] + self.escape(test) + test_repr[0]

    return re.sub(
        r'((\\[\\abfnrtv\'"]|\\[0-9]..|\\x..|\\u....)+)',
        f'<span style="color:{inline_styles.REPR_COLOR}">' + r"\1" + "</span>",
        self.escape(test_repr),
    )
repr_string(self, x, _)

Repr, but squash it into a window

Source code in pydoc_fork\reporter\html_repr_class.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def repr_string(self, x: str, _: int) -> str:
    """Repr, but squash it into a window"""
    test = cram(x, self.maximum_string)
    test_repr = repr(test)
    if "\\" in test and "\\" not in replace(test_repr, r"\\", ""):
        # Backslashes are only literal in the string and are never
        # needed to make any special characters, so show a raw string.
        return "r" + test_repr[0] + self.escape(test) + test_repr[0]

    return re.sub(
        r'((\\[\\abfnrtv\'"]|\\[0-9]..|\\x..|\\u....)+)',
        f'<span style="color:{inline_styles.REPR_COLOR}">' + r"\1" + "</span>",
        self.escape(test_repr),
    )
repr_unicode(self, x, _)

Repr, but squash it into a window

Source code in pydoc_fork\reporter\html_repr_class.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def repr_string(self, x: str, _: int) -> str:
    """Repr, but squash it into a window"""
    test = cram(x, self.maximum_string)
    test_repr = repr(test)
    if "\\" in test and "\\" not in replace(test_repr, r"\\", ""):
        # Backslashes are only literal in the string and are never
        # needed to make any special characters, so show a raw string.
        return "r" + test_repr[0] + self.escape(test) + test_repr[0]

    return re.sub(
        r'((\\[\\abfnrtv\'"]|\\[0-9]..|\\x..|\\u....)+)',
        f'<span style="color:{inline_styles.REPR_COLOR}">' + r"\1" + "</span>",
        self.escape(test_repr),
    )

inline_styles

Style sheet for hardcoded styles

jinja_code

Jinja setup

JINJA_ENV

Object to let Jinja find template folder

rst_support

Rst to HTML function Credits: https://stackoverflow.com/a/49047197/33264

HTMLFragmentTranslator (HTMLTranslator)

Minimum to call docutils

__init__(self, document) special

setup

Source code in pydoc_fork\reporter\rst_support.py
13
14
15
16
17
18
19
def __init__(self, document: Any) -> None:
    """setup"""
    HTMLTranslator.__init__(self, document)
    self.head_prefix = ["", "", "", "", ""]
    self.body_prefix: List[Any] = []
    self.body_suffix: List[Any] = []
    self.stylesheet: List[Any] = []
astext(self)

minimum to call docutils

Source code in pydoc_fork\reporter\rst_support.py
21
22
23
def astext(self) -> str:
    """minimum to call docutils"""
    return "".join(self.body)

rst_to_html(text)

Convert rst string to html string

Source code in pydoc_fork\reporter\rst_support.py
30
31
32
def rst_to_html(text: str) -> str:
    """Convert rst string to html string"""
    return core.publish_string(text, writer=html_fragment_writer).decode("utf-8")

string_utils

String manipulation

cram(text, maximum_length)

Omit part of a string if needed to make it fit in a maximum length.

Source code in pydoc_fork\reporter\string_utils.py
21
22
23
24
25
26
27
def cram(text: str, maximum_length: int) -> str:
    """Omit part of a string if needed to make it fit in a maximum length."""
    if len(text) > maximum_length:
        pre = max(0, (maximum_length - 3) // 2)
        post = max(0, maximum_length - 3 - pre)
        return text[:pre] + "..." + text[len(text) - post :]
    return text

replace(text, *pairs)

Do a series of global replacements on a string.

replace(“abc xyz”, “xyz”, “abc”) ‘abc abc’

Source code in pydoc_fork\reporter\string_utils.py
10
11
12
13
14
15
16
17
18
def replace(text: str, *pairs: str) -> str:
    """Do a series of global replacements on a string.
    >>> replace("abc xyz", "xyz", "abc")
    'abc abc'
    """
    while pairs:
        text = pairs[1].join(text.split(pairs[0]))
        pairs = pairs[2:]
    return text

stripid(text)

Remove the hexadecimal id from a Python object representation.

Source code in pydoc_fork\reporter\string_utils.py
33
34
35
36
def stripid(text: str) -> str:
    """Remove the hexadecimal id from a Python object representation."""
    # The behaviour of %p is implementation-dependent in terms of case.
    return _re_stripid.sub(r"\1", text)

settings

Configuration options that could be used by anything.

Also global variables

PYTHONDOCS

Module docs for core modules are assumed to be in

https://docs.python.org/X.Y/library/

This can be overridden by setting the PYTHONDOCS environment variable to a different URL or to a local directory containing the Library Reference Manual pages.

load_config(path)

Copy config from toml to globals

Source code in pydoc_fork\settings.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def load_config(path: Optional[str]):
    """Copy config from toml to globals"""
    global PREFER_DOCS_PYTHON_ORG
    global DOCUMENT_INTERNALS
    global SKIP_MODULES
    global ONLY_NAMED_AND_SUBS

    pairs = parse_toml(path)
    if pairs:
        LOGGER.debug(f"Found config at {path}")
    PREFER_DOCS_PYTHON_ORG = pairs.get("PREFER_DOCS_PYTHON_ORG", False)
    DOCUMENT_INTERNALS = pairs.get("DOCUMENT_INTERNALS", True)
    SKIP_MODULES = pairs.get("SKIP_MODULES", ["typing"])
    ONLY_NAMED_AND_SUBS= pairs.get("ONLY_NAMED_AND_SUBS", False)

parse_toml(path_string)

Parse toml

Source code in pydoc_fork\settings.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def parse_toml(path_string: Optional[str]) -> Dict[str, Any]:
    """Parse toml"""
    if not path_string:
        path = pathlib.Path(os.getcwd())
    else:
        path = pathlib.Path(path_string)
    toml_path = path / "pyproject.toml"
    if not toml_path.exists():
        return {}
    with open(toml_path, encoding="utf8") as handle:
        pyproject_toml = tomli.loads(handle.read())
    config = pyproject_toml.get("tool", {}).get("pydoc_fork", {})
    loose_matching = {
        k.replace("--", "").replace("-", "_"): v for k, v in config.items()
    }
    return loose_matching