{ "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'{_name(elt)}{_sourcelink(elt)}\\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 }