{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"hide_input": false
},
"outputs": [],
"source": [
"#hide\n",
"#default_exp showdoc"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"from showdoc.lookup import *\n",
"\n",
"import inspect,ast\n",
"from fastcore.all import *\n",
"from enum import Enum,EnumMeta\n",
"from textwrap import dedent\n",
"\n",
"try: from IPython.display import Markdown,display\n",
"except ModuleNotFoundError: Markdown=None"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"from fastcore.test import *\n",
"import typing,numpy"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Showdoc\n",
"\n",
"> Create documentation directly from python functions and classes"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`showdoc` needs the following information to display for a symbol:\n",
"\n",
"- the header level; this will be passed as a param\n",
"- the name\n",
"- the qualified name\n",
"- the prefix (`class`, `def`, or `enum`)\n",
"- link to the source if available\n",
"- the doc string\n",
"- the base class(es), if appropriate\n",
"- parameters, with types and defaults if provided"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Basic Details"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"def _unwrapped_func(x):\n",
" \"Unwrap properties, typedispatch, and functools.wraps decorated functions\"\n",
" if hasattr(x,'first'): x = x.first()\n",
" return getattr(getattr(x,'__wrapped__',x), \"fget\", x)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For testing we will use the following definitions:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def _f(f): return f\n",
"\n",
"e = Enum('a',['b','c'])\n",
"\n",
"class _T(int):\n",
" \"The class `_T`\"\n",
" def __init__(self, x:numpy.ndarray): ...\n",
" def m(self, a:typing.Union[int,str]=0)->numpy.ndarray:\n",
" \"A docstring mentioning \"\n",
" return numpy.array([1])\n",
" @_f\n",
" def n(self): ...\n",
" @property\n",
" def t(self):\n",
" \"A property\"\n",
" ...\n",
"\n",
"objs = L(e,_T,_T.m,_T.n,_T.t,max,typing.Union[int,str])\n",
"defs = L(e,_T,_f)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"def _name(o):\n",
" o = _unwrapped_func(o)\n",
" return str(try_attrs(o, '__name__', '__origin__', '_name')).split('.')[-1]"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"test_eq(objs.map(_name), ['a','_T','m','n','t','max','Union'])"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"def qualname(o):\n",
" o = _unwrapped_func(o)\n",
" return getattr(o,'__qualname__', repr(o))"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"def _code(o): return f'{o}'\n",
"\n",
"def _qualname(o):\n",
" o = _unwrapped_func(o)\n",
" return _code(getattr(o,'__qualname__', repr(o)))"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"def _display_md(f, its): [display(Markdown(f(o))) for o in its]"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"def nbdev_setting(mod, key, default=None):\n",
" try: return nbdev_idx_mods[mod]['settings'][key]\n",
" except KeyError: return default"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"def sourcelink_url(o):\n",
" \"Source link to `o`\"\n",
" o = _unwrapped_func(o)\n",
" try: line = inspect.getsourcelines(o)[1]\n",
" except Exception: return None\n",
" mod = o.__module__\n",
" return f\"{nbdev_setting(mod, 'git_url', '')}{mod.replace('.', '/')}.py#L{line}\"\n",
"\n",
"def _sourcelink(o):\n",
" url = sourcelink_url(o)\n",
" if url is None: return ''\n",
" return f'[source]'"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'https://github.com/fastai/showdoc/tree/master/showdoc/lookup.py#L30'"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sourcelink_url(ShowdocLookup)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"def _docstring(o):\n",
" res = inspect.getdoc(o)\n",
" if not res: return ''\n",
" if \"\\n\\n\" in res or \"\\n \" in res: res = f\"```\\n{res}\\n```\"\n",
" return res\n",
"\n",
"def _basecls(o):\n",
" res = getattr(o,'__bases__',[None])[0]\n",
" if res: res = _name(res)\n",
" return f\" :: `{res}`\" if res else ''"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Parameter list"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"def typename(o):\n",
" \"Representation of type `t`\"\n",
" if getattr(o, '__args__', None): return str(o).split('.')[-1]\n",
" res = _name(o)\n",
" mod = getattr(o,'__module__','builtins')\n",
" if mod=='builtins': return res\n",
" return f\"{mod}.{res}\"\n",
"\n",
"def _type_repr(t): return f\":`{typename(t)}`\" if t else ''"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(#3) ['Union[int, str]','int','fastcore.foundation.L']"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"L(typing.Union[int,str],int,L).map(typename)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"def _param(p):\n",
" _arg_prefixes = {inspect._VAR_POSITIONAL: '\\*', inspect._VAR_KEYWORD:'\\*\\*'}\n",
" arg_prefix = _arg_prefixes.get(p.kind, '') # asterisk prefix for *args and **kwargs\n",
" res = f\"**{arg_prefix}{_code(p.name)}**\"\n",
" res += _type_repr(empty2none(getattr(p,'annotation',None)))\n",
" if p.default != p.empty:\n",
" default = getattr(p.default, 'func', p.default) # partial\n",
" res += f'=*`{getattr(default,\"__name__\",default)}`*'\n",
" return res\n",
"\n",
"def _args(x):\n",
" \"Formats function params to `param:Type=val` with markdown styling\"\n",
" try: sig = inspect.signature(x)\n",
" except ValueError: return _code(re.search(r\"(\\([^)]*\\))\", x.__doc__).group(1)) # C functions\n",
" except TypeError: return '' # properties\n",
"\n",
" fmt_params = [_param(v) for k,v in sig.parameters.items() if k not in ('self','cls')]\n",
" res = f\"({', '.join(fmt_params)})\"\n",
" ret = anno_dict(x).get('return',None)\n",
" if ret: res += f\" -> `{typename(ret)}`\"\n",
" return res"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"@typedispatch\n",
"def format_showdoc(x:typing.Callable):\n",
" \"Markdown formatted version of `x`\"\n",
" return f'{_code(\"def\")} {_qualname(x)}{_args(x)}'\n",
"\n",
"@typedispatch\n",
"def format_showdoc(x): return _qualname(x)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"def _T.m(**a**:`Union[int, str]`=*`0`*) -> `numpy.ndarray`"
],
"text/plain": [
""
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Markdown(format_showdoc(_T.m))"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"@typedispatch\n",
"def format_showdoc(x:type):\n",
" ar = _qualname(x)\n",
" if inspect.isclass(x): ar = f\"{_code('class')} {ar}\"\n",
" return ar + _args(x) + _basecls(x)"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"class _T(**x**:`numpy.ndarray`) :: `int`"
],
"text/plain": [
""
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Markdown(format_showdoc(_T))"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"@typedispatch\n",
"def format_showdoc(x:(Enum,EnumMeta)):\n",
" vals = ', '.join(L(x.__members__).map(_code(\"{}\")))\n",
" return f'{_code(\"enum\")} = [{vals}]'"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"enum = [b, c]"
],
"text/plain": [
""
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Markdown(format_showdoc(e))"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"_det_tmpl = \"\"\"\n",
"source
\n",
"\n",
"```python\n",
"{code}\n",
"```\n",
" \n",
"\n",
"\"\"\"\n",
"\n",
"def show_sourcecode(o, maxlines=15):\n",
" \"Collapsible section showing source, without signature or docstring\"\n",
" try: src = inspect.getsource(o)\n",
" except TypeError: return '' # builtin\n",
" tree = ast.parse(dedent(src)).body[0]\n",
" start,end = tree.body[0].lineno,tree.body[-1].end_lineno\n",
" if end-start>maxlines: return '' # too big\n",
" body_src = dedent('\\n'.join(src.splitlines()[start:end]))\n",
" return _det_tmpl.format(code=body_src)"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"\n",
"source
\n",
"\n",
"```python\n",
"def __init__(self, strip_libs=None, incl_libs=None, skip_mods=None):\n",
" skip_mods,strip_libs = setify(skip_mods),L(strip_libs)\n",
" if incl_libs is not None: incl_libs = (L(incl_libs)+strip_libs).unique()\n",
" self.entries = filter_keys(nbdev_idxs, lambda k: incl_libs is None or k in incl_libs)\n",
" py_syms = merge(*L(o.modidx['syms'].values() for o in self.entries.values()).concat())\n",
" for m in strip_libs:\n",
" _d = self.entries[m].modidx\n",
" stripped = {remove_prefix(k,f\"{mod}.\"):v\n",
" for mod,dets in _d['syms'].items() if mod not in skip_mods\n",
" for k,v in dets.items()}\n",
" py_syms = merge(stripped, py_syms)\n",
" self.syms = py_syms\n",
"\n",
"def __getitem__(self, s): return self.syms.get(s, None)\n",
"```\n",
" \n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Markdown(show_sourcecode(ShowdocLookup))"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"\n",
"source
\n",
"\n",
"```python\n",
"in_fence=False\n",
"lines = md.splitlines()\n",
"for i,l in enumerate(lines):\n",
" if l.startswith(\"```\"): in_fence=not in_fence\n",
" elif not l.startswith(' ') and not in_fence: lines[i] = self._link_line(l, L(skipped))\n",
"return '\\n'.join(lines)\n",
"```\n",
" \n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Markdown(show_sourcecode(ShowdocLookup.linkify))"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"def show_doc(elt, doc_string=True, name=None, title_level=None, disp=True, default_level=2):\n",
" \"Show documentation for element `elt`. Supported types: class, function, and enum.\"\n",
" elt = getattr(elt, '__func__', elt)\n",
" args = format_showdoc(elt)\n",
" title_level = title_level or default_level\n",
" doc = f'\\n\\n'\n",
" if args: doc += f'> {args}\\n\\n'\n",
" doc += show_sourcecode(elt) + _docstring(elt)\n",
" doc = showdoc_lookup().linkify(doc)\n",
" if disp:\n",
" if Markdown: display(Markdown(doc))\n",
" else: print(doc)\n",
" else: return doc"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"a\" class=\"doc_header\">a
\n",
"\n",
"> enum = [b, c]\n",
"\n",
"An enumeration."
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"_T\" class=\"doc_header\">_T
\n",
"\n",
"> class _T(**x**:[`numpy.ndarray`](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray)) :: `int`\n",
"\n",
"The class `_T`"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"_T.m\" class=\"doc_header\">m[source]
\n",
"\n",
"> _T.m(**a**:`Union[int, str]`=*`0`*) -> [`numpy.ndarray`](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray)\n",
"\n",
"\n",
"source
\n",
"\n",
"```python\n",
"return numpy.array([1])\n",
"```\n",
" \n",
"\n",
"A method returning [`numpy.ndarray`](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray)"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"_T.n\" class=\"doc_header\">n[source]
\n",
"\n",
"> _T.n()\n",
"\n",
"\n",
"source
\n",
"\n",
"```python\n",
"\n",
"```\n",
" \n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"_T.t\" class=\"doc_header\">t[source]
\n",
"\n",
"> _T.t\n",
"\n",
"A property"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/markdown": [
"max\" class=\"doc_header\">max
\n",
"\n",
"> max(iterable, *[, default=obj, key=func])\n",
"\n",
"```\n",
"max(iterable, *[, default=obj, key=func]) -> value\n",
"max(arg1, arg2, *args, *[, key=func]) -> value\n",
"\n",
"With a single iterable argument, return its biggest item. The\n",
"default keyword-only argument specifies an object to return if\n",
"the provided iterable is empty.\n",
"With two or more arguments, return the largest argument.\n",
"```"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"objs[:-1].map(show_doc);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### The doc command"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"def nbdev_module(sym):\n",
" return nested_idx(nbdev_idx_mods, sym.__module__, 'syms', sym.__module__)\n",
"\n",
"def nbdev_doclink(sym):\n",
" nbmod = nbdev_module(sym)\n",
" if not nbmod: return ''\n",
" k = sym.__module__\n",
" if not inspect.ismodule(sym): k += '.' + qualname(sym)\n",
" return nbmod[k]"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'https://fastai.github.io/showdoc.lookup#ShowdocLookup'"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nbdev_doclink(ShowdocLookup)"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"def doc(elt):\n",
" \"Show `show_doc` info in preview window when used in a notebook\"\n",
" md = show_doc(elt, disp=False)\n",
" doc_link = nbdev_doclink(elt)\n",
" if doc_link is not None:\n",
" md += f'\\n\\nShow in docs'\n",
" display(Markdown(md))"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"ShowdocLookup\" class=\"doc_header\">ShowdocLookup[source]
\n",
"\n",
"> class ShowdocLookup(**strip_libs**=*`None`*, **incl_libs**=*`None`*, **skip_mods**=*`None`*) :: `object`\n",
"\n",
"\n",
"source
\n",
"\n",
"```python\n",
"def __init__(self, strip_libs=None, incl_libs=None, skip_mods=None):\n",
" skip_mods,strip_libs = setify(skip_mods),L(strip_libs)\n",
" if incl_libs is not None: incl_libs = (L(incl_libs)+strip_libs).unique()\n",
" self.entries = filter_keys(nbdev_idxs, lambda k: incl_libs is None or k in incl_libs)\n",
" py_syms = merge(*L(o.modidx['syms'].values() for o in self.entries.values()).concat())\n",
" for m in strip_libs:\n",
" _d = self.entries[m].modidx\n",
" stripped = {remove_prefix(k,f\"{mod}.\"):v\n",
" for mod,dets in _d['syms'].items() if mod not in skip_mods\n",
" for k,v in dets.items()}\n",
" py_syms = merge(stripped, py_syms)\n",
" self.syms = py_syms\n",
"\n",
"def __getitem__(self, s): return self.syms.get(s, None)\n",
"```\n",
" \n",
"\n",
"Mapping from symbol names to URLs with docs\n",
"\n",
"Show in docs"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"doc(ShowdocLookup)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Export -"
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {},
"outputs": [],
"source": [
"from nbdev.doclinks import nbdev_build_lib\n",
"nbdev_build_lib()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"jupytext": {
"split_at_heading": true
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.3"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": false,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 2
}