def parse_source(doc: Docstring, obj: Any): """Parses parameters' docstring to inspect type and description from source. Examples: >>> from mkapi.core.base import Base >>> doc = Docstring() >>> parse_source(doc, Base) >>> section = doc['Parameters'] >>> section['name'].to_tuple() ('name', 'str, optional', 'Name of self.') >>> section = doc['Attributes'] >>> section['html'].to_tuple() ('html', 'str', 'HTML output after conversion.') """ signature = get_signature(obj) name = "Parameters" section = signature[name] if name in doc: section = section.merge(doc[name], force=True) if section: doc.set_section(section, replace=True) name = "Attributes" section = signature[name] if name not in doc and not section: return doc[name].update(section) if is_dataclass(obj) and "Parameters" in doc: for item in doc["Parameters"].items: if item.name in section: doc[name].set_item(item)
def postprocess(doc: Docstring, obj: Any): parse_bases(doc, obj) if inspect.ismodule(obj) or inspect.isclass(obj): parse_attribute(doc, obj) if isinstance(obj, property): parse_property(doc, obj) if not callable(obj): return signature = get_signature(obj) if signature.signature is None: return def get_type(type: str) -> Type: if type.startswith("("): # tuple type = type[1:-1] return Type(type) if doc["Parameters"] is not None: for item in doc["Parameters"].items: if not item.type and item.name in signature.parameters: item.type = get_type(signature.parameters[item.name]) if "{default}" in item.desc.markdown and item.name in signature: default = signature.defaults[item.name] item.desc.markdown = item.desc.markdown.replace( "{default}", default) if doc["Attributes"] is not None and signature.attributes: for item in doc["Attributes"].items: if not item.type and item.name in signature.attributes: item.type = get_type(signature.attributes[item.name]) for name in ["Returns", "Yields"]: section = doc[name] if section is not None and not section.type: section.type = Type(getattr(signature, name.lower())) if doc["Returns"] is None and doc["Yields"] is None: from mkapi.core.node import get_kind kind = get_kind(obj) if kind == "generator": doc.type = Type(signature.yields) else: doc.type = Type(signature.returns) for section in doc.sections: if section.name in ["Example", "Examples"]: break if section.markdown: section.markdown = replace_link(obj, section.markdown) else: for item in section.items: if item.markdown: item.markdown = replace_link(obj, item.markdown)
def parse_bases(doc: Docstring, obj: Any): """Parses base classes to create a Base(s) line.""" if not inspect.isclass(obj) or not hasattr(obj, "mro"): return objs = get_mro(obj)[1:] if not objs: return types = [get_link(obj, include_module=True) for obj in objs] items = [Item(type=Type(type)) for type in types if type] doc.set_section(Section("Bases", items=items))
def parse_property(doc: Docstring, obj: Any): """Parses property's docstring to inspect type.""" doc.type = Type(get_signature(obj.fget).returns) if not doc.type: section = doc.sections[0] markdown = section.markdown type, markdown = preprocess.split_type(markdown) if type: doc.type = Type(type) section.markdown = markdown
def get_docstring(obj: Any) -> Docstring: """Returns a [Docstring]() instance.""" doc = inspect.getdoc(obj) if doc: sections = [] for section in split_section(doc): sections.append(get_section(*section)) docstring = Docstring(sections) else: return Docstring() postprocess(docstring, obj) return docstring
def postprocess(doc: Docstring, obj: Any): parse_bases(doc, obj) parse_source(doc, obj) if not callable(obj): return signature = get_signature(obj) if signature.signature is None: return if "Parameters" in doc: for item in doc["Parameters"].items: description = item.description if "{default}" in description.name and item.name in signature: default = signature.defaults[item.name] description.markdown = description.name.replace( "{default}", default) for name in ["Returns", "Yields"]: if name in doc: section = doc[name] if not section.type: section.type = Type(getattr(signature, name.lower())) if "Returns" not in doc and "Yields" not in doc: from mkapi.core.node import get_kind kind = get_kind(obj) if kind == "generator": doc.type = Type(signature.yields) else: doc.type = Type(signature.returns) sections: List[Section] = [] for section in doc.sections: if section.name not in ["Example", "Examples"]: for base in section: base.markdown = replace_link(obj, base.markdown) if section.name in ["Note", "Notes", "Warning", "Warnings"]: markdown = preprocess.admonition(section.name, section.markdown) if sections and sections[-1].name == "": sections[-1].markdown += "\n\n" + markdown continue else: section.name = "" section.markdown = markdown sections.append(section) doc.sections = sections
def get_docstring(obj: Any) -> Docstring: """Returns a [Docstring]() instance.""" doc = inspect.getdoc(obj) if doc: sections = [] for section in split_section(doc): sections.append(get_section(*section)) docstring = Docstring(sections) elif inspect.isclass(obj) and hasattr(obj, "mro"): bases = obj.mro()[1:-1] if not bases: return Docstring() docstring = Docstring([Section()]) else: return Docstring() postprocess(docstring, obj) return docstring
def test_docstring_copy(): d = Docstring() a = Section("Parameters") d.set_section(a) assert "Parameters" in d assert d["Parameters"] is a a = Section("Arguments") d.set_section(a, copy=True) assert "Arguments" in d assert d["Arguments"] is not a