Roughly a UI component for classes
import builtins
import inspect
import sys
from collections import deque
from typing import Any, Callable, Dict, List, Optional, Union, cast
from pydoc_fork import inline_styles
from pydoc_fork.custom_types import TypeLike
from pydoc_fork.format_data import docdata
from pydoc_fork.format_other import docother
from pydoc_fork.formatter_html import escape, markup, section
from pydoc_fork.utils import (
_split_list,
classify_class_attrs,
classname,
getdoc,
sort_attributes,
visiblename,
)
Make a link for a class.
def classlink(the_object: Union[TypeLike, type], modname: str) -> str:
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)
def docclass(
the_object: TypeLike,
name: str = "",
mod: str = "",
funcs: Dict[str, str] = {}, # noqa - clean up later
classes: Dict[str, str] = {}, # noqa - clean up later
*ignored: List[Any], Produce HTML documentation for a class object.
) -> str:
realname = the_object.__name__
name = name or realname
bases = the_object.__bases__
contents: List[str] = []
push = contents.append
Cute little class to pump out a horizontal rule between sections.
class HorizontalRule:
def __init__(self) -> None:
self.needone = 0
Skip
def maybe(self) -> None:
if self.needone:
push("<hr>\n")
self.needone = 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")
Not sure
def spill(
msg: str, attrs_in: List[Any], predicate: Callable[[Any], Any]
) -> List[Any]:
ok, attrs = _split_list(attrs_in, predicate)
if ok:
hr.maybe()
push(msg)
for name, _, _, value in ok:
try:
value = getattr(the_object, name)
except Exception: # nosec
Some descriptors may meet a failure in their get. (bug #1785)
push(
docdata(
value,
name,
mod, unused
)
)
else:
circular ref pylint: disable=import-outside-toplevel
from pydoc_fork.format_page import document
push(document(value, name, mod, funcs, classes, mdict, the_object))
push("\n")
return attrs
Not sure
def spilldescriptors(
msg: str,
attrs_in: List[Any], # Tuple[str, str, type, "object"]
predicate: Callable[[Any], bool],
) -> List[Any]:
ok, attrs = _split_list(attrs_in, predicate)
if ok:
hr.maybe()
push(msg)
for name, _, _, value in ok:
push(
docdata(
value,
name,
mod, ignored
)
)
return attrs
Not sure
def spilldata(
msg: str, attrs_in: List[Any], predicate: Callable[[Any], bool]
) -> List[Any]:
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, mdict)
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)
]
mdict = {}
for key, _, _, value in attrs:
mdict[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).
mdict[value] = anchor
except TypeError:
pass # nosec
while attrs:
if mro:
thisclass = mro.popleft()
else:
thisclass = attrs[0][2]
is_thisclass: Callable[[Any], Any] = lambda t: t[2] is thisclass
attrs, inherited = _split_list(attrs, is_thisclass)
if the_object is not builtins.object and thisclass is builtins.object:
attrs = inherited
continue
if thisclass is the_object:
tag = "defined here"
else:
tag = f"inherited from {classlink(thisclass, the_object.__module__)}"
tag += ":<br>\n"
sort_attributes(attrs, the_object)
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 attrs == [] # nosec
attrs = inherited
contents_as_string = "".join(contents) # type got redefined
if name == realname:
title = f'<a name="{name}">class <strong>{realname}</strong></a>'
else:
title = f'<strong>{name}</strong> = <a name="{name}">class {realname}</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:
argspec = str(signature)
if argspec and argspec != "()":
this will cause double escape on -> escape(argspec)
decl = name + argspec + "\n\n"
doc = getdoc(the_object)
if decl:
doc = decl + (doc or "")
doc = markup(doc, funcs, classes, mdict)
doc = doc and f"<tt>{doc}<br> </tt>"
return section(title, "#000000", "#ffc8d8", contents_as_string, 3, doc)
Produce HTML for a class tree as given by inspect.getclasstree().
def formattree(tree: List[Any], modname: str, parent: Optional[Any] = None) -> str:
result = ""
for entry in tree:
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 = formattree(entry, modname, class_object)
result = result + f"<dd>\n{tree}</dd>\n"
return f"<dl>\n{result}</dl>\n"