{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "hide_input": false }, "outputs": [], "source": [ "#hide\n", "#default_exp showdoc\n", "#default_cls_lvl 3\n", "from nbdev.showdoc import show_doc" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "from nbdev.imports import *\n", "from nbdev.export import *\n", "from nbdev.sync import *\n", "from nbconvert import HTMLExporter\n", "from fastcore.docments import docments, isclass, _clean_comment, _tokens, _param_locs, _get_comment\n", "from fastcore.utils import IN_NOTEBOOK\n", "from fastcore.xtras import get_source_link, _unwrapped_type_dispatch_func\n", "\n", "import string\n", "from tokenize import COMMENT\n", "\n", "if IN_NOTEBOOK:\n", " from IPython.display import Markdown,display\n", " from IPython.core import page" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Show doc\n", "\n", "> Functions to show the doc cells in notebooks" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All the automatic documentation of functions and classes are generated with the `show_doc` function. It displays the name, arguments, docstring along with a link to the source code on GitHub." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Gather the information" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The inspect module lets us know quickly if an object is a function or a class but it doesn't distinguish classes and enums." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def is_enum(cls):\n", " \"Check if `cls` is an enum or another type of class\"\n", " return type(cls) in (enum.Enum, enum.EnumMeta)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "e = enum.Enum('e', 'a b')\n", "assert is_enum(e)\n", "assert not is_enum(e.__class__)\n", "assert not is_enum(int)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Links to documentation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def is_lib_module(name):\n", " \"Test if `name` is a library module.\"\n", " if name.startswith('_'): return False\n", " try:\n", " _ = importlib.import_module(f'{get_config().lib_name}.{name}')\n", " return True\n", " except: return False" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "assert is_lib_module('export')\n", "assert not is_lib_module('transform')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "re_digits_first = re.compile('^[0-9]+[a-z]*_')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def try_external_doc_link(name, packages):\n", " \"Try to find a doc link for `name` in `packages`\"\n", " for p in packages:\n", " try:\n", " mod = importlib.import_module(f\"{p}._nbdev\")\n", " try_pack = source_nb(name, is_name=True, mod=mod)\n", " if try_pack:\n", " page = re_digits_first.sub('', try_pack).replace('.ipynb', '')\n", " return f'{mod.doc_url}{page}#{name}'\n", " except ModuleNotFoundError: return None" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This function will only work for other packages built with `nbdev`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_eq(try_external_doc_link('get_name', ['nbdev']), 'https://nbdev.fast.ai/sync#get_name')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def is_doc_name(name):\n", " \"Test if `name` corresponds to a notebook that could be converted to a doc page\"\n", " for f in get_config().path(\"nbs_path\").glob(f'*{name}.ipynb'):\n", " if re_digits_first.sub('', f.name) == f'{name}.ipynb': return True\n", " return False" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_eq(is_doc_name('flaags'),False)\n", "test_eq(is_doc_name('export'),True)\n", "test_eq(is_doc_name('index'),True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def doc_link(name, include_bt=True):\n", " \"Create link to documentation for `name`.\"\n", " cname = f'`{name}`' if include_bt else name\n", " try:\n", " #Link to modules\n", " if is_lib_module(name) and is_doc_name(name): return f\"[{cname}]({get_config().doc_baseurl}{name}.html)\"\n", " #Link to local functions\n", " try_local = source_nb(name, is_name=True)\n", " if try_local:\n", " page = re_digits_first.sub('', try_local).replace('.ipynb', '')\n", " return f'[{cname}]({get_config().doc_baseurl}{page}.html#{name})'\n", " ##Custom links\n", " mod = get_nbdev_module()\n", " link = mod.custom_doc_links(name)\n", " return f'[{cname}]({link})' if link is not None else cname\n", " except: return cname" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This function will generate links for modules (pointing to the html conversion of the corresponding notebook) and functions (pointing to the html conversion of the notebook where they were defined, with the first anchor found before). If the function/module is not part of the library you are writing, it will call the function `custom_doc_links` generated in `_nbdev` (you can customize it to your needs) and just return the name between backticks if that function returns `None`.\n", "\n", "For instance, fastai has the following `custom_doc_links` that tries to find a doc link for `name` in fastcore then nbdev (in this order):\n", "``` python\n", "def custom_doc_links(name): \n", " from nbdev.showdoc import try_external_doc_link\n", " return try_external_doc_link(name, ['fastcore', 'nbdev'])\n", "```\n", "\n", "Please note that module links only work if your notebook names \"correspond\" to your module names:\n", "\n", "| Notebook name | Doc name | Module name | Module file | Can doc link? |\n", "|--------------------|----------------|-------------|--------------|---------------|\n", "| export.ipynb | export.html | export | export.py | Yes |\n", "| 00_export.ipynb | export.html | export | export.py | Yes |\n", "| 00a_export.ipynb | export.html | export | export.py | Yes |\n", "| export_1.ipynb | export_1.html | export | export.py | No |\n", "| 03_data.core.ipynb | data.core.html | data.core | data/core.py | Yes |\n", "| 03_data_core.ipynb | data_core.html | data.core | data/core.py | No |" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_eq(doc_link('export'), f'[`export`](/export.html)')\n", "test_eq(doc_link('DocsTestClass'), f'[`DocsTestClass`](/export.html#DocsTestClass)')\n", "test_eq(doc_link('DocsTestClass.test'), f'[`DocsTestClass.test`](/export.html#DocsTestClass.test)')\n", "test_eq(doc_link('Tenso'),'`Tenso`')\n", "test_eq(doc_link('_nbdev'), f'`_nbdev`')\n", "test_eq(doc_link('__main__'), f'`__main__`')\n", "test_eq(doc_link('flags'), '`flags`') # we won't have a flags doc page even though we do have a flags module" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "_re_backticks = re.compile(r\"\"\"\n", "# Catches any link of the form \\[`obj`\\](old_link) or just `obj`,\n", "# to either update old links or add the link to the docs of obj\n", "\\[` # Opening [ and `\n", "([^`]*) # Catching group with anything but a `\n", "`\\] # ` then closing ]\n", "(?: # Beginning of non-catching group\n", "\\( # Opening (\n", "[^)]* # Anything but a closing )\n", "\\) # Closing )\n", ") # End of non-catching group\n", "| # OR\n", "` # Opening `\n", "([^`]*) # Anything but a `\n", "` # Closing `\n", "\"\"\", re.VERBOSE)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def add_doc_links(text, elt=None):\n", " \"Search for doc links for any item between backticks in `text` and insert them\"\n", " def _replace_link(m):\n", " try:\n", " if m.group(2) in inspect.signature(elt).parameters: return f'`{m.group(2)}`'\n", " except: pass\n", " return doc_link(m.group(1) or m.group(2))\n", " return _re_backticks.sub(_replace_link, text)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This function not only add links to backtick keywords, it also update the links that are already in the text (in case they have changed)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tst = add_doc_links('This is an example of `DocsTestClass`')\n", "test_eq(tst, \"This is an example of [`DocsTestClass`](/export.html#DocsTestClass)\")\n", "tst = add_doc_links('This is an example of [`DocsTestClass`](old_link.html)')\n", "test_eq(tst, \"This is an example of [`DocsTestClass`](/export.html#DocsTestClass)\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Names in backticks will not be converted to links if `elt` has a parameter of the same name" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def t(a,export):\n", " \"Test func that uses 'export' as a parameter name and has `export` in its doc string\"\n", "assert '[`export`](/export.html)' not in add_doc_links(t.__doc__, t)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Names in backticks _used in markdown links_ will be updated like normal" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def t(a,export):\n", " \"Test func that uses 'export' as a parameter name and has [`export`]() in its doc string\"\n", "assert '[`export`](/export.html)' in add_doc_links(t.__doc__, t)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "# if the name in backticks is not a param name, links will be added/updated like normal\n", "def t(a,exp):\n", " \"Test func with `export` in its doc string\"\n", "assert '[`export`](/export.html)' in add_doc_links(t.__doc__, t)\n", "def t(a,exp):\n", " \"Test func with [`export`]() in its doc string\"\n", "assert '[`export`](/export.html)' in add_doc_links(t.__doc__, t)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If `elt` is a class, `add_doc_links` looks at parameter names used in `__init__`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class T:\n", " def __init__(self, add_doc_links): pass\n", "test_eq(add_doc_links('Lets talk about `add_doc_links`'), \n", " 'Lets talk about [`add_doc_links`](/showdoc.html#add_doc_links)')\n", "test_eq(add_doc_links('Lets talk about `add_doc_links`', T), 'Lets talk about `add_doc_links`')\n", "test_eq(add_doc_links('Lets talk about `doc_link`'), \n", " 'Lets talk about [`doc_link`](/showdoc.html#doc_link)')\n", "test_eq(add_doc_links('Lets talk about `doc_link`', T), \n", " 'Lets talk about [`doc_link`](/showdoc.html#doc_link)')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Links to source" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As important as the source code, we want to quickly jump to where the function is defined when we are in a development notebook." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "_re_header = re.compile(r\"\"\"\n", "# Catches any header in markdown with the title in group 1\n", "^\\s* # Beginning of text followed by any number of whitespace\n", "\\#+ # One # or more\n", "\\s* # Any number of whitespace\n", "(.*) # Catching group with anything\n", "$ # End of text\n", "\"\"\", re.VERBOSE)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def colab_link(path):\n", " \"Get a link to the notebook at `path` on Colab\"\n", " cfg = get_config()\n", " res = f'https://colab.research.google.com/github/{cfg.user}/{cfg.lib_name}/blob/{cfg.branch}/{cfg.path(\"nbs_path\").name}/{path}.ipynb'\n", " display(Markdown(f'[Open `{path}` in Colab]({res})'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "[Open `02_showdoc` in Colab](https://colab.research.google.com/github/fastai/nbdev/blob/master/nbs/02_showdoc.ipynb)" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "colab_link('02_showdoc')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def get_nb_source_link(func, local=False, is_name=None):\n", " \"Return a link to the notebook where `func` is defined.\"\n", " func = _unwrapped_type_dispatch_func(func)\n", " pref = '' if local else get_config().git_url.replace('github.com', 'nbviewer.jupyter.org/github')+ get_config().path(\"nbs_path\").name+'/'\n", " is_name = is_name or isinstance(func, str)\n", " src = source_nb(func, is_name=is_name, return_all=True)\n", " if src is None: return '' if is_name else get_source_link(func)\n", " find_name,nb_name = src\n", " nb = read_nb(nb_name)\n", " pat = re.compile(f'^{find_name}\\s+=|^(def|class)\\s+{find_name}\\s*\\(', re.MULTILINE)\n", " if len(find_name.split('.')) == 2:\n", " clas,func = find_name.split('.')\n", " pat2 = re.compile(f'@patch\\s*\\ndef\\s+{func}\\s*\\([^:]*:\\s*{clas}\\s*(?:,|\\))')\n", " else: pat2 = None\n", " for i,cell in enumerate(nb['cells']):\n", " if cell['cell_type'] == 'code':\n", " if re.search(pat, cell['source']): break\n", " if pat2 is not None and re.search(pat2, cell['source']): break\n", " if re.search(pat, cell['source']) is None and (pat2 is not None and re.search(pat2, cell['source']) is None):\n", " return '' if is_name else get_function_source(func)\n", " header_pat = re.compile(r'^\\s*#+\\s*(.*)$')\n", " while i >= 0:\n", " cell = nb['cells'][i]\n", " if cell['cell_type'] == 'markdown' and _re_header.search(cell['source']):\n", " title = _re_header.search(cell['source']).groups()[0]\n", " anchor = '-'.join([s for s in title.split(' ') if len(s) > 0])\n", " return f'{pref}{nb_name}#{anchor}'\n", " i-=1\n", " return f'{pref}{nb_name}'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_eq(get_nb_source_link(DocsTestClass.test), get_nb_source_link(DocsTestClass))\n", "test_eq(get_nb_source_link('DocsTestClass'), get_nb_source_link(DocsTestClass))\n", "\n", "test_eq(get_nb_source_link(check_re, local=True), f'00_export.ipynb#Finding-patterns')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can either pass an object or its name (by default `is_name` will look if `func` is a string or not to decide if it's `True` or `False`, but you can override if there is some inconsistent behavior). `local` will return a local link, otherwise it will point to a the notebook on Google Colab." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def nb_source_link(func, is_name=None, disp=True, local=True):\n", " \"Show a relative link to the notebook where `func` is defined\"\n", " is_name = is_name or isinstance(func, str)\n", " func_name = func if is_name else qual_name(func)\n", " link = get_nb_source_link(func, local=local, is_name=is_name)\n", " text = func_name if local else f'{func_name} (GitHub)'\n", " if disp: display(Markdown(f'[{text}]({link})'))\n", " else: return link" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This function assumes you are in one notebook in the development folder, otherwise you can use `disp=False` to get the relative link. You can either pass an object or its name (by default `is_name` will look if `func` is a string or not to decide if it's `True` or `False`, but you can override if there is some inconsistent behavior)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "[check_re (GitHub)](https://nbviewer.jupyter.org/github/fastai/nbdev/tree/master/nbs/00_export.ipynb#Finding-patterns)" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "test_eq(nb_source_link(check_re, disp=False), f'00_export.ipynb#Finding-patterns')\n", "test_eq(nb_source_link('check_re', disp=False), f'00_export.ipynb#Finding-patterns')\n", "nb_source_link(check_re, local=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Show documentation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "from fastcore.script import Param" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _format_annos(anno, highlight=False):\n", " \"Returns a clean string representation of `anno` from either the `__qualname__` if it is a base class, or `str()` if not\"\n", " annos = listify(anno)\n", " if len(annos) == 0: return \"None\" # If anno is none, listify has a length of 0\n", " new_anno = \"(\" if len(annos) > 1 else \"\"\n", " def _inner(o): return getattr(o, '__qualname__', str(o)) if '<' in str(o) else str(o)\n", " for i, anno in enumerate(annos):\n", " new_anno += _inner(anno) if not highlight else f'{doc_link(_inner(anno))}'\n", " # if \".\" in new_anno: new_anno = new_anno.split('.')[-1]\n", " if len(annos) > 1 and i < len(annos) - 1:\n", " new_anno += ', '\n", " return f'{new_anno})' if len(annos) > 1 else new_anno" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "from typing import Union, Tuple, List\n", "test_eq(_format_annos(Union[int,float]), 'typing.Union[int, float]')\n", "test_eq(_format_annos(Tuple[int,float]), 'typing.Tuple[int, float]')\n", "test_ne(_format_annos(Tuple[int,float]), 'typing.Tuple[int,float]')\n", "test_eq(_format_annos((int,float)), '(int, float)')\n", "test_eq(_format_annos(int), 'int')\n", "test_eq(_format_annos(L), 'L')\n", "test_eq(_format_annos(L, highlight=True), '`L`')\n", "test_eq(_format_annos((L,list), highlight=True), '(`L`, `list`)')\n", "test_eq(_format_annos(None), \"None\")\n", "test_eq(_format_annos(Union[List[str],str]), 'typing.Union[typing.List[str], str]')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def type_repr(t):\n", " \"Representation of type `t` (in a type annotation)\"\n", " if (isinstance(t, Param)): return f'\"{t.help}\"'\n", " if getattr(t, '__args__', None):\n", " args = t.__args__\n", " if len(args)==2 and args[1] == type(None):\n", " return f'`Optional`\\[{type_repr(args[0])}\\]'\n", " reprs = ', '.join([_format_annos(o, highlight=True) for o in args])\n", " return f'{doc_link(get_name(t))}\\[{reprs}\\]'\n", " else: return doc_link(_format_annos(t))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The representation tries to find doc links if possible." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tst = type_repr(Optional[DocsTestClass])\n", "test_eq(tst, '`Optional`\\\\[[`DocsTestClass`](/export.html#DocsTestClass)\\\\]')\n", "tst = type_repr(Union[int, float])\n", "test_eq(tst, '`Union`\\\\[`int`, `float`\\\\]')\n", "test_eq(type_repr(Param(\"description\")), '\"description\"')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "_arg_prefixes = {inspect._VAR_POSITIONAL: '\\*', inspect._VAR_KEYWORD:'\\*\\*'}\n", "\n", "def format_param(p):\n", " \"Formats function param to `param:Type=val` with font weights: param=bold, val=italic\"\n", " arg_prefix = _arg_prefixes.get(p.kind, '') # asterisk prefix for *args and **kwargs\n", " res = f\"**{arg_prefix}`{p.name}`**\"\n", " if hasattr(p, 'annotation') and p.annotation != p.empty: res += f':{type_repr(p.annotation)}'\n", " if p.default != p.empty:\n", " default = getattr(p.default, 'func', p.default) #For partials\n", " if hasattr(default,'__name__'): default = getattr(default, '__name__')\n", " else: default = repr(default)\n", " if is_enum(default.__class__): #Enum have a crappy repr\n", " res += f'=*`{default.__class__.__name__}.{default.name}`*'\n", " else: res += f'=*`{default}`*'\n", " return res" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sig = inspect.signature(notebook2script)\n", "params = [format_param(p) for _,p in sig.parameters.items()]\n", "test_eq(params, ['**`fname`**=*`None`*', '**`silent`**=*`False`*', '**`to_dict`**=*`False`*', '**`bare`**=*`False`*'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def o(a:(list, int)): return a\n", "sig = inspect.signature(o)\n", "params = [format_param(p) for _,p in sig.parameters.items()]\n", "test_eq(params, ['**`a`**:`(list, int)`'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _format_enum_doc(enum, full_name):\n", " \"Formatted `enum` definition to show in documentation\"\n", " vals = ', '.join(enum.__members__.keys())\n", " return f'{full_name}',f'Enum = [{vals}]'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "tst = _format_enum_doc(e, 'e')\n", "test_eq(tst, ('e', 'Enum = [a, b]'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _escape_chars(s):\n", " return s.replace('_', '\\_')\n", "\n", "def _format_func_doc(func, full_name=None):\n", " \"Formatted `func` definition to show in documentation\"\n", " try:\n", " sig = inspect.signature(func)\n", " fmt_params = [format_param(param) for name,param\n", " in sig.parameters.items() if name not in ('self','cls')]\n", " except: fmt_params = []\n", " name = f'{full_name or func.__name__}'\n", " arg_str = f\"({', '.join(fmt_params)})\"\n", " f_name = f\"class {name}\" if inspect.isclass(func) else name\n", " return f'{f_name}',f'{name}{arg_str}'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "test_eq(_format_func_doc(notebook2script), ('notebook2script', \n", "'notebook2script(**`fname`**=*`None`*, **`silent`**=*`False`*, **`to_dict`**=*`False`*, **`bare`**=*`False`*)'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _format_cls_doc(cls, full_name):\n", " \"Formatted `cls` definition to show in documentation\"\n", " parent_class = inspect.getclasstree([cls])[-1][0][1][0]\n", " name,args = _format_func_doc(cls, full_name)\n", " if parent_class != object: args += f' :: {doc_link(get_name(parent_class))}'\n", " return name,args" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "test_eq(_format_cls_doc(DocsTestClass, 'DocsTestClass'), ('class DocsTestClass', \n", " 'DocsTestClass()'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _has_docment(elt): \n", " comments = {o.start[0]:_clean_comment(o.string) for o in _tokens(elt) if o.type==COMMENT}\n", " params = _param_locs(elt, returns=True)\n", " comments = [_get_comment(line,arg,comments,params) for line,arg in params.items()]\n", " return any(c is not None for c in comments)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "def _test_a(\n", " a:int, # has two docments\n", " b:int, # Test\n", "):\n", " \"A test func\"\n", " return a+b\n", "\n", "test_eq(_has_docment(_test_a), True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "def _test_b(\n", " a:int, # has one docment\n", " b:int,\n", "):\n", " \"A test func\"\n", " return a+b\n", "\n", "test_eq(_has_docment(_test_b), True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "def _test_c(a:int,b:int):\n", " \"A test func\"\n", " return a+b\n", "\n", "test_eq(_has_docment(_test_c), False)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _generate_arg_string(argument_dict, has_docment=False, monospace=False):\n", " \"Turns a dictionary of argument information into a useful docstring\"\n", " arg_string = '||Type|Default|'\n", " border_string = '|---|---|---|'\n", " if has_docment:\n", " arg_string += 'Details|'\n", " border_string += '---|'\n", " arg_string+= f'\\n{border_string}\\n'\n", " for key, item in argument_dict.items():\n", " is_required=True\n", " if key == 'return': continue\n", " if item['default'] != inspect._empty:\n", " if item['default'] == '':\n", " item['default'] = '\"\"'\n", " is_required = False\n", " arg_string += f\"|**`{key}`**|\"\n", " details_string = \"\"\n", " if item['anno'] == None: item['anno'] = NoneType\n", " if (item[\"default\"] == None and item['anno'] == NoneType) or item['anno'] == inspect._empty:\n", " details_string += \"|\"\n", " else:\n", " details_string += f\"`{_format_annos(item['anno']).replace('|', 'or')}`|\"\n", " details_string += \"|\" if is_required else f\"`{_format_annos(item['default'])}`|\"\n", " if has_docment:\n", " if item['docment']:\n", " item['docment'] = item['docment'].replace('\\n', '
')\n", " details_string += f\"{item['docment']}|\" if item['docment'] is not None else \"*No Content*|\"\n", " arg_string += add_doc_links(details_string)\n", " arg_string += '\\n'\n", " return arg_string" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "args = {\n", " \"full\": {\n", " \"docment\":\"Test me!\", \n", " \"anno\":int, \n", " \"default\":0\n", " },\n", " \"partial\": {\n", " \"docment\": \"Test x2\",\n", " \"anno\": float,\n", " \"default\": inspect._empty\n", " },\n", " \"none\": {\n", " \"docment\": None,\n", " \"anno\": inspect._empty,\n", " \"default\": inspect._empty\n", " },\n", " \"multitype\": {\n", " \"docment\":\"Testing pipe!\",\n", " \"anno\": 'int | float',\n", " \"default\": inspect._empty\n", " },\n", " \"multitype-basetypes\" : {\n", " \"docment\":\"Testing typing!\",\n", " \"anno\": Union[int,float],\n", " \"default\": inspect._empty\n", " },\n", " \"multiline-docstring\": {\n", " \"docment\": \"This is my first\\nThis is my second\",\n", " \"anno\": int,\n", " \"default\": 2\n", " },\n", " \"multi-none\": {\n", " \"docment\": \"Blah\",\n", " \"anno\": NoneType,\n", " \"default\": None\n", " },\n", " \"default-none\": {\n", " \"docment\": \"Blah blah\",\n", " \"anno\": int,\n", " \"default\": None\n", " } \n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "_str = \"||Type|Default|Details|\\n|---|---|---|---|\\n|**`full`**|`int`|`0`|Test me!|\\n\"\n", "test_eq(_generate_arg_string({\"full\":args[\"full\"]}, has_docment=True), _str)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "_str = \"||Type|Default|Details|\\n|---|---|---|---|\\n|**`partial`**|`float`||Test x2|\\n\"\n", "test_eq(_generate_arg_string({\"partial\":args[\"partial\"]}, has_docment=True), _str)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "_str = '||Type|Default|\\n|---|---|---|\\n|**`none`**|||\\n'\n", "test_eq(_generate_arg_string({\"none\":args[\"none\"]}, has_docment=False), _str)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "_str = '||Type|Default|Details|\\n|---|---|---|---|\\n|**`multitype`**|`int or float`||Testing pipe!|\\n'\n", "test_eq(_generate_arg_string({\"multitype\":args[\"multitype\"]}, has_docment=True), _str)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "_str = '||Type|Default|Details|\\n|---|---|---|---|\\n|**`multitype-basetypes`**|`typing.Union[int, float]`||Testing typing!|\\n'\n", "test_eq(_generate_arg_string({\"multitype-basetypes\":args[\"multitype-basetypes\"]}, has_docment=True), _str)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "_str = '||Type|Default|Details|\\n|---|---|---|---|\\n|**`multiline-docstring`**|`int`|`2`|This is my first
This is my second|\\n'\n", "test_eq(_generate_arg_string({\"multiline-docstring\":args[\"multiline-docstring\"]}, has_docment=True), _str)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "_str = '||Type|Default|Details|\\n|---|---|---|---|\\n|**`default-none`**|`int`|`None`|Blah blah|\\n'\n", "test_eq(_generate_arg_string({\"default-none\":args[\"default-none\"]}, has_docment=True), _str)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "_str = '||Type|Default|Details|\\n|---|---|---|---|\\n|**`multi-none`**||`None`|Blah|\\n'\n", "test_eq(_generate_arg_string({\"multi-none\":args[\"multi-none\"]}, has_docment=True), _str)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _generate_return_string(return_dict:dict, has_docment=False):\n", " \"Turns a dictionary of return information into a useful docstring\"\n", " if return_dict['anno'] is None:\n", " if not return_dict['docment']: return ''\n", " else: return_dict['anno'] = NoneType\n", " anno = _format_annos(return_dict['anno']).replace('|', 'or')\n", " return_string = f\"|**Returns**|`{anno}`||\"\n", " if has_docment:\n", " if return_dict['docment']:\n", " return_dict['docment'] = return_dict['docment'].replace('\\n', '
')\n", " else: return_dict['docment'] = ''\n", " return return_string if not has_docment else f\"{return_string}{return_dict['docment']}|\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "_return_dict = {\n", " \"full\":{\n", " \"docment\": \"Sum of parts\", \n", " \"anno\":float, \n", " \"default\":inspect._empty\n", " },\n", " # We can't have a situation of a `docment` without \n", " # annotations on a return, so we only test an annotation\n", " \"partial\":{\n", " \"docment\": None, \n", " \"anno\":float, \n", " \"default\":inspect._empty\n", " },\n", " \"none\":{\n", " \"docment\": None, \n", " \"anno\":inspect._empty, \n", " \"default\":inspect._empty\n", " },\n", " \"typing\":{\n", " \"docment\": \"Sum of parts\",\n", " \"anno\":Union[int,float],\n", " \"default\":inspect._empty\n", " },\n", " \"anno-none\": {\n", " \"docment\": None,\n", " \"anno\": None,\n", " \"default\": inspect._empty\n", " },\n", " \"none-with-docment\": {\n", " \"docment\": \"We return nothing\",\n", " \"anno\": None,\n", " \"default\": inspect._empty\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "_str = \"|**Returns**|`float`||Sum of parts|\"\n", "test_eq(_generate_return_string(_return_dict[\"full\"], has_docment=True), _str)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "_str = \"|**Returns**|`float`||\"\n", "test_eq(_generate_return_string(_return_dict[\"partial\"], has_docment=False), _str)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "_str = \"|**Returns**|`_empty`||\"\n", "test_eq(_generate_return_string(_return_dict[\"none\"], has_docment=False), _str)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "_str = \"|**Returns**|`typing.Union[int, float]`||Sum of parts|\"\n", "test_eq(_generate_return_string(_return_dict[\"typing\"], has_docment=True), _str)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "test_eq(_generate_return_string(_return_dict[\"anno-none\"]), '')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "_str = '|**Returns**|`NoneType`||We return nothing|'\n", "test_eq(_generate_return_string(_return_dict[\"none-with-docment\"], has_docment=True), _str)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _is_static(func):\n", " \"Checks whether `func` is a static method in a class\"\n", " name = qual_name(func)\n", " if len(name.split(\".\")) == 2:\n", " cls, nm = name.split('.')\n", " cls = getattr(sys.modules[func.__module__], cls)\n", " method_type = inspect.getattr_static(cls, nm)\n", " return isinstance(method_type, staticmethod)\n", " return False" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "class _A:\n", " def __init__(self, \n", " a, # First number\n", " b # Second number\n", " ):\n", " self.a = a\n", " self.b = b\n", "\n", " @classmethod\n", " def from_self(cls, *args):\n", " return cls(*args)\n", " \n", " @staticmethod\n", " def _add(\n", " a, # First val\n", " b # Second val\n", " ): return a+b\n", "\n", " def add(self # Should be ignored\n", " ): return self._add(self.a, self.b)\n", "\n", "test_eq(_is_static(_A), False)\n", "test_eq(_is_static(_A.__init__), False)\n", "test_eq(_is_static(_A.add), False)\n", "test_eq(_is_static(_A._add), True)\n", "test_eq(_is_static(_A.from_self), False)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _format_args(elt, ment_dict:dict = None, kwargs = [], monospace=False, is_class=False):\n", " \"Generates a formatted argument string, potentially from an existing `ment_dict`\"\n", " if ment_dict is None:\n", " ment_dict = docments(elt, full=True)\n", " arg_string = \"\"\n", " return_string = \"\"\n", " if not _is_static(elt) and is_class:\n", " ment_dict.pop(\"self\", {})\n", " ment_dict.pop(\"cls\", {})\n", " ret = ment_dict.pop(\"return\", None)\n", " has_docment = _has_docment(elt)\n", " if len(ment_dict.keys()) > 0:\n", " if len(kwargs) > 0:\n", " kwarg_dict = filter_keys(ment_dict, lambda x: x in kwargs)\n", " ment_dict = filter_keys(ment_dict, lambda x: x not in kwargs)\n", " arg_string = _generate_arg_string(ment_dict, has_docment)\n", " arg_string += \"|||**Valid Keyword Arguments**||\\n\"\n", " arg_string += _generate_arg_string(kwarg_dict, has_docment, monospace=monospace).replace(\"||Type|Default|Details|\\n|---|---|---|---|\\n\", \"\")\n", " else:\n", " arg_string = _generate_arg_string(ment_dict, has_docment, monospace=monospace)\n", " if not ret[\"anno\"] == inspect._empty:\n", " return_string = _generate_return_string(ret, has_docment)\n", " return arg_string + return_string" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "def addition(\n", " a:int, # The first number to add\n", " b:float, # The second number to add\n", ") -> (int,float): # The sum of `a` and `b`\n", " \"Adds two numbers together\"\n", " return a+b\n", "test_eq(_format_args(addition), '||Type|Default|Details|\\n|---|---|---|---|\\n|**`a`**|`int`||The first number to add|\\n|**`b`**|`float`||The second number to add|\\n|**Returns**|`(int, float)`||The sum of `a` and `b`|')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "def addition(\n", " # The first number to add\n", " # The starting value\n", " a:int,\n", " # The second number to add\n", " # The addend\n", " b:float = 2\n", ") -> (int,float): # The sum of `a` and `b`\n", " \"Adds two numbers together\"\n", " return a+b\n", "test_eq(_format_args(addition), '||Type|Default|Details|\\n|---|---|---|---|\\n|**`a`**|`int`||The first number to add
The starting value|\\n|**`b`**|`float`|`2`|The second number to add
The addend|\\n|**Returns**|`(int, float)`||The sum of `a` and `b`|')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "def addition(\n", " # The first number to add\n", " # The starting value\n", " a:int,\n", " # The second number to add\n", " # The addend\n", " b:float = 2\n", " # The sum of `a` and `b`\n", " # Our return\n", ") -> (int,float):\n", " \"Adds two numbers together\"\n", " return a+b\n", "test_eq(_format_args(addition), '||Type|Default|Details|\\n|---|---|---|---|\\n|**`a`**|`int`||The first number to add
The starting value|\\n|**`b`**|`float`|`2`|The second number to add
The addend|\\n|**Returns**|`(int, float)`||The sum of `a` and `b`
Our return|')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "test_eq(_format_args(_A._add, is_class=True), '||Type|Default|Details|\\n|---|---|---|---|\\n|**`a`**|||First val|\\n|**`b`**|||Second val|\\n')\n", "test_eq(_format_args(_A.__init__, is_class=True), '||Type|Default|Details|\\n|---|---|---|---|\\n|**`a`**|||First number|\\n|**`b`**|||Second number|\\n')\n", "test_eq(_format_args(_A.add, is_class=True), '')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def is_source_available(\n", " elt, # A python object\n", "):\n", " \"Checks if it is possible to return the source code of `elt` mimicking `inspect.getfile`\"\n", " if inspect.ismodule(elt):\n", " return True if getattr(object, '__file__', None) else False\n", " elif isclass(elt):\n", " if hasattr(elt, '__module__'):\n", " module = sys.modules.get(elt.__module__)\n", " return True if getattr(module, '__file__', None) else False\n", " elif getattr(elt, '__name__', None) == \"\":\n", " return False\n", " elif inspect.ismethod(elt) or inspect.isfunction(elt) or inspect.istraceback(elt) or inspect.isframe(elt) or inspect.iscode(elt):\n", " return True\n", " elif is_enum(elt):\n", " return False\n", " return False" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "from fastcore.dispatch import typedispatch\n", "@typedispatch\n", "def _typ_test(\n", " a:int, # An integer\n", " b:int # A second integer\n", ") -> float:\n", " \"Perform op\"\n", " return a+b\n", "\n", "class _TypTest:\n", " def __init__(a,b):\n", " self.a = a\n", " self.b = b\n", " \n", "class abc(object):\n", " @staticmethod\n", " def static_method():\n", " print(\"Hi,this tutorial is about static class in python\")\n", " @classmethod\n", " def class_method(cls):\n", " print(\"Hi,this tutorial is about static class in python\")\n", " \n", "@patch\n", "def myTestFunc(self:_TypTest):\n", " print(\"This is a patched func\")\n", " \n", "from abc import ABC, abstractmethod\n", " \n", "class AbstractClass(ABC):\n", " @abstractmethod\n", " def noofsides(self):\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "test_eq(is_source_available(lambda x: x+1), False) # lambda\n", "test_eq(is_source_available(int), False) # builtin type\n", "test_eq(is_source_available(addition), True) # func\n", "test_eq(is_source_available(getattr), False) # builtin func\n", "test_eq(is_source_available(_typ_test), False) # typedispatch\n", "test_eq(is_source_available(e), False) # enum\n", "test_eq(is_source_available(_TypTest), False) # class\n", "test_eq(is_source_available(_TypTest.__init__), True) # class\n", "test_eq(is_source_available(_TypTest.myTestFunc), True) # patch\n", "test_eq(is_source_available(abc.static_method), True) # static\n", "test_eq(is_source_available(abc), False) # static object\n", "test_eq(is_source_available(abc.class_method), True) # class method\n", "test_eq(is_source_available(AbstractClass), False) # abstract class\n", "test_eq(is_source_available(AbstractClass.noofsides), True) # abstract method" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _handle_delegates(elt):\n", " \"Generates a `docment` dict handling `@delegates` and returns names of the kwargs in `elt`\"\n", " kwargs = []\n", " arg_dict = docments(elt, full=True)\n", " delwrap_dict = docments(elt.__delwrap__, full=True)\n", " drop = arg_dict.keys()\n", " for k,v in arg_dict.items():\n", " if k in delwrap_dict.keys() and v[\"docment\"] is None and k != \"return\":\n", " kwargs.append(k)\n", " if delwrap_dict[k][\"docment\"] is not None:\n", " v[\"docment\"] = delwrap_dict[k][\"docment\"] + f\" passed to `{qual_name(elt.__delwrap__)}`\"\n", " else:\n", " v['docment'] = f\"Argument passed to `{qual_name(elt.__delwrap__)}`\"\n", " return arg_dict, kwargs" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "from fastcore.meta import delegates\n", "def _a(\n", " a:int=2, # First\n", "):\n", " return a\n", "\n", "@delegates(_a)\n", "def _b(\n", " b:str, # Second\n", " **kwargs\n", "):\n", " return b, (_a(**kwargs))\n", "_docment = ({\"b\":AttrDict({\"docment\":\"Second\",\"anno\":str,\"default\":inspect._empty}),\n", " \"a\":AttrDict({\"docment\":\"First passed to `_a`\",\"anno\":int,\"default\":2}),\n", " \"return\": AttrDict({\"docment\":None,\"anno\":inspect._empty,\"default\":inspect._empty})},\n", " [\"a\"])\n", "test_eq(_handle_delegates(_b),_docment)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _get_docments(elt, with_return=False, ment_dict=None, kwargs=[], monospace=False, is_class=False):\n", " \"Grabs docments for `elt` and formats with a potential `ment_dict` and valid kwarg names\"\n", " s = f\"\\n\\n{_format_args(elt, ment_dict=ment_dict, kwargs=kwargs, monospace=monospace, is_class=is_class)}\"\n", " if not with_return: s = s.split(\"|**Returns**|\")[0]\n", " return s" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "_s = '\\n\\n||Type|Default|Details|\\n|---|---|---|---|\\n|**`a`**|`int`|`2`|First|\\n'\n", "test_eq(_get_docments(_a), _s)\n", "_s = '\\n\\n||Type|Default|Details|\\n|---|---|---|---|\\n|**`b`**|`str`||Second|\\n|**`a`**|`int`|`2`|*No Content*|\\n'\n", "test_eq(_get_docments(_b), _s)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def show_doc(elt, doc_string:bool=True, name=None, title_level=None, disp=True, default_cls_level=2, show_all_docments=False, verbose=False):\n", " \"Show documentation for element `elt` with potential input documentation. Supported types: class, function, and enum.\"\n", " elt = getattr(elt, '__func__', elt)\n", " qname = name or qual_name(elt)\n", " is_class = '.' in qname or inspect.isclass\n", " if inspect.isclass(elt):\n", " if is_enum(elt): name,args = _format_enum_doc(elt, qname)\n", " else: name,args = _format_cls_doc (elt, qname)\n", " elif callable(elt): name,args = _format_func_doc(elt, qname)\n", " else: name,args = f\"{qname}\", ''\n", " link = get_source_link(elt)\n", " source_link = f'[source]'\n", " title_level = title_level or (default_cls_level if inspect.isclass(elt) else 4)\n", " doc = f'{name}{source_link}'\n", " doc += f'\\n\\n> {args}\\n\\n' if len(args) > 0 else '\\n\\n'\n", " s = ''\n", " try:\n", " monospace = get_config().d.getboolean('monospace_docstrings', False)\n", " except FileNotFoundError:\n", " monospace = False\n", " if doc_string and inspect.getdoc(elt):\n", " s = inspect.getdoc(elt)\n", " # doc links don't work inside markdown pre/code blocks\n", " s = f'```\\n{s}\\n```' if monospace else add_doc_links(s, elt)\n", " doc += s\n", " if len(args) > 0:\n", " if hasattr(elt, '__init__') and isclass(elt):\n", " elt = elt.__init__\n", " if is_source_available(elt):\n", " if show_all_docments or _has_docment(elt):\n", " if hasattr(elt, \"__delwrap__\"):\n", " arg_dict, kwargs = _handle_delegates(elt)\n", " doc += _get_docments(elt, ment_dict=arg_dict, with_return=True, kwargs=kwargs, monospace=monospace, is_class=is_class)\n", " else:\n", " doc += _get_docments(elt, monospace=monospace, is_class=is_class)\n", " elif verbose:\n", " print(f'Warning: `docments` annotations will not work for built-in modules, classes, functions, and `enums` and are unavailable for {qual_name(elt)}. They will not be shown')\n", " if disp: display(Markdown(doc))\n", " else: return doc" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`doc_string` determines if we show the docstring of the function or not. `name` can be used to provide an alternative to the name automatically found. `title_level` determines the level of the anchor (default 3 for classes and 4 for functions). If `disp` is `False`, the function returns the markdown code instead of displaying it. If `doc_string` is `True` and `monospace_docstrings` is set to `True` in `settings.ini`, the docstring of the function is formatted in a code block to preserve whitespace." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For instance\n", "```python\n", "show_doc(notebook2script)\n", "```\n", "will display" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

notebook2script[source]

\n", "\n", "> notebook2script(**`fname`**=*`None`*, **`silent`**=*`False`*, **`to_dict`**=*`False`*, **`bare`**=*`False`*)\n", "\n", "Convert notebooks matching `fname` to modules" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_doc(notebook2script)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "from fastcore.meta import delegates\n", "def _a(\n", " a:int=2, # First\n", "):\n", " return a\n", "\n", "@delegates(_a)\n", "def _b(\n", " b:str, # Second\n", " **kwargs\n", "):\n", " return b, (_a(**kwargs))\n", "\n", "def _c(\n", " b:str, # Second\n", " a:int=2, # Blah\n", "):\n", " return b, a\n", "@delegates(_c)\n", "def _d(\n", " c:int, # First\n", " b:str,\n", " **kwargs\n", "):\n", " return c, _c(b, **kwargs)\n", "\n", "_str = '

_b[source]

\\n\\n> _b(**`b`**:`str`, **`a`**:`int`=*`2`*)\\n\\n\\n\\n||Type|Default|Details|\\n|---|---|---|---|\\n|**`b`**|`str`||Second|\\n|||**Valid Keyword Arguments**||\\n|**`a`**|`int`|`2`|First passed to `_a`|\\n'\n", "test_eq(show_doc(_b, disp=False), _str)\n", "_str = '

_d[source]

\\n\\n> _d(**`c`**:`int`, **`b`**:`str`, **`a`**:`int`=*`2`*)\\n\\n\\n\\n||Type|Default|Details|\\n|---|---|---|---|\\n|**`c`**|`int`||First|\\n|||**Valid Keyword Arguments**||\\n|**`b`**|`str`||Second passed to `_c`|\\n|**`a`**|`int`|`2`|Blah passed to `_c`|\\n'\n", "test_eq(show_doc(_d, disp=False), _str)\n", "_str = '

_A._add[source]

\\n\\n> _A._add(**`a`**, **`b`**)\\n\\n\\n\\n||Type|Default|Details|\\n|---|---|---|---|\\n|**`a`**|||First val|\\n|**`b`**|||Second val|\\n'\n", "test_eq(show_doc(_A._add, disp=False), _str)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "def _test_linked_param(\n", " show_doc:callable # The function `show_doc`\n", "):\n", " return show_doc\n", "assert show_doc(_test_linked_param, disp=False).count(\"`show_doc`\") == 3\n", "assert show_doc(_test_linked_param, disp=False).count(\"**`show_doc`**\") == 2 # In arg string and docment default\n", "assert show_doc(_test_linked_param, disp=False).count(\"[`show_doc`]\") == 1 # In docment itself" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "def t(a,exp):\n", " \"Test func with `export` in its doc string\"\n", "assert '[`export`](/export.html)' in show_doc(t, disp=False)\n", "def t(a,export):\n", " \"Test func that uses 'export' as a parameter name and has `export` in its doc string\"\n", "assert '[`export`](/export.html)' not in show_doc(t, disp=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Integration test -" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

class DocsTestClass[source]

\n", "\n", "> DocsTestClass()\n", "\n", "for tests only" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#hide\n", "show_doc(DocsTestClass)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

DocsTestClass.test[source]

\n", "\n", "> DocsTestClass.test()\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#hide\n", "show_doc(DocsTestClass.test)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

check_re[source]

\n", "\n", "> check_re(**`cell`**, **`pat`**, **`code_only`**=*`True`*)\n", "\n", "Check if `cell` contains a line with regex `pat`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#hide\n", "show_doc(check_re)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

e[source]

\n", "\n", "> Enum = [a, b]\n", "\n", "An enumeration." ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#hide\n", "show_doc(e)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

test_func_with_args_and_links[source]

\n", "\n", "> test_func_with_args_and_links(**`foo`**, **`bar`**)\n", "\n", "Doc link: [`show_doc`](/showdoc.html#show_doc).\n", "Args:\n", " foo: foo\n", " bar: bar\n", "Returns:\n", " None" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "

test_func_with_args_and_links[source]

\n", "\n", "> test_func_with_args_and_links(**`foo`**, **`bar`**)\n", "\n", "```\n", "Doc link: `show_doc`.\n", "Args:\n", " foo: foo\n", " bar: bar\n", "Returns:\n", " None\n", "```" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#hide\n", "def test_func_with_args_and_links(foo, bar):\n", " \"\"\"\n", " Doc link: `show_doc`.\n", " Args:\n", " foo: foo\n", " bar: bar\n", " Returns:\n", " None\n", " \"\"\"\n", " pass\n", "\n", "show_doc(test_func_with_args_and_links)\n", "get_config()[\"monospace_docstrings\"] = \"True\"\n", "show_doc(test_func_with_args_and_links)\n", "get_config()[\"monospace_docstrings\"] = \"False\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The doc command" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def md2html(md):\n", " \"Convert markdown `md` to HTML code\"\n", " import nbconvert\n", " if nbconvert.__version__ < '5.5.0': return HTMLExporter().markdown2html(md)\n", " else: return HTMLExporter().markdown2html(collections.defaultdict(lambda: collections.defaultdict(dict)), md)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def get_doc_link(func):\n", " mod = inspect.getmodule(func)\n", " module = mod.__name__.replace('.', '/') + '.py'\n", " try:\n", " nbdev_mod = importlib.import_module(mod.__package__.split('.')[0] + '._nbdev')\n", " try_pack = source_nb(func, mod=nbdev_mod)\n", " if try_pack:\n", " page = '.'.join(try_pack.partition('_')[-1:]).replace('.ipynb', '')\n", " return f'{nbdev_mod.doc_url}{page}#{qual_name(func)}'\n", " except: return None" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_eq(get_doc_link(notebook2script), 'https://nbdev.fast.ai/export#notebook2script')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#test\n", "from nbdev.sync import get_name\n", "test_eq(get_doc_link(get_name), 'https://nbdev.fast.ai/sync#get_name')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "# Fancy CSS needed to make raw Jupyter rendering look nice\n", "_TABLE_CSS = \"\"\"\"\"\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def doc(elt:int, show_all_docments:bool=True):\n", " \"Show `show_doc` info in preview window when used in a notebook\"\n", " md = show_doc(elt, disp=False, show_all_docments=show_all_docments)\n", " doc_link = get_doc_link(elt)\n", " if doc_link is not None:\n", " md += f'\\n\\nShow in docs'\n", " output = md2html(md)\n", " if IN_COLAB: get_ipython().run_cell_magic(u'html', u'', output + _TABLE_CSS)\n", " else:\n", " try: page.page({'text/html': output + _TABLE_CSS})\n", " except: display(Markdown(md))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Export -" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Converted 00_export.ipynb.\n", "Converted 01_sync.ipynb.\n", "Converted 02_showdoc.ipynb.\n", "Converted 03_export2html.ipynb.\n", "Converted 04_test.ipynb.\n", "Converted 05_merge.ipynb.\n", "Converted 06_cli.ipynb.\n", "Converted 07_clean.ipynb.\n", "Converted 99_search.ipynb.\n", "Converted example.ipynb.\n", "Converted index.ipynb.\n", "Converted nbdev_comments.ipynb.\n", "Converted tutorial.ipynb.\n", "Converted tutorial_colab.ipynb.\n" ] } ], "source": [ "#hide\n", "from nbdev.export import notebook2script\n", "notebook2script()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "jupytext": { "split_at_heading": true }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 4 }