#hide
#default_exp showdoc
#export
from showdoc.lookup import *
import inspect,ast
from fastcore.all import *
from enum import Enum,EnumMeta
from textwrap import dedent
try: from IPython.display import Markdown,display
except ModuleNotFoundError: Markdown=None
from fastcore.test import *
import typing,numpy
Create documentation directly from python functions and classes
showdoc needs the following information to display for a symbol:
class, def, or enum)#export
def _unwrapped_func(x):
"Unwrap properties, typedispatch, and functools.wraps decorated functions"
if hasattr(x,'first'): x = x.first()
return getattr(getattr(x,'__wrapped__',x), "fget", x)
For testing we will use the following definitions:
def _f(f): return f
e = Enum('a',['b','c'])
class _T(int):
"The class `_T`"
def __init__(self, x:numpy.ndarray): ...
def m(self, a:typing.Union[int,str]=0)->numpy.ndarray:
"A docstring mentioning "
return numpy.array([1])
@_f
def n(self): ...
@property
def t(self):
"A property"
...
objs = L(e,_T,_T.m,_T.n,_T.t,max,typing.Union[int,str])
defs = L(e,_T,_f)
#export
def _name(o):
o = _unwrapped_func(o)
return str(try_attrs(o, '__name__', '__origin__', '_name')).split('.')[-1]
test_eq(objs.map(_name), ['a','_T','m','n','t','max','Union'])
#export
def qualname(o):
o = _unwrapped_func(o)
return getattr(o,'__qualname__', repr(o))
#export
def _code(o): return f'<code>{o}</code>'
def _qualname(o):
o = _unwrapped_func(o)
return _code(getattr(o,'__qualname__', repr(o)))
def _display_md(f, its): [display(Markdown(f(o))) for o in its]
#export
def nbdev_setting(mod, key, default=None):
try: return nbdev_idx_mods[mod]['settings'][key]
except KeyError: return default
#export
def sourcelink_url(o):
"Source link to `o`"
o = _unwrapped_func(o)
try: line = inspect.getsourcelines(o)[1]
except Exception: return None
mod = o.__module__
return f"{nbdev_setting(mod, 'git_url', '')}{mod.replace('.', '/')}.py#L{line}"
def _sourcelink(o):
url = sourcelink_url(o)
if url is None: return ''
return f'<a href="{url}" class="source_link" style="float:right">[source]</a>'
sourcelink_url(ShowdocLookup)
'https://github.com/fastai/showdoc/tree/master/showdoc/lookup.py#L30'
#export
def _docstring(o):
res = inspect.getdoc(o)
if not res: return ''
if "\n\n" in res or "\n " in res: res = f"```\n{res}\n```"
return res
def _basecls(o):
res = getattr(o,'__bases__',[None])[0]
if res: res = _name(res)
return f" :: `{res}`" if res else ''
#export
def typename(o):
"Representation of type `t`"
if getattr(o, '__args__', None): return str(o).split('.')[-1]
res = _name(o)
mod = getattr(o,'__module__','builtins')
if mod=='builtins': return res
return f"{mod}.{res}"
def _type_repr(t): return f":`{typename(t)}`" if t else ''
L(typing.Union[int,str],int,L).map(typename)
(#3) ['Union[int, str]','int','fastcore.foundation.L']
#export
def _param(p):
_arg_prefixes = {inspect._VAR_POSITIONAL: '\*', inspect._VAR_KEYWORD:'\*\*'}
arg_prefix = _arg_prefixes.get(p.kind, '') # asterisk prefix for *args and **kwargs
res = f"**{arg_prefix}{_code(p.name)}**"
res += _type_repr(empty2none(getattr(p,'annotation',None)))
if p.default != p.empty:
default = getattr(p.default, 'func', p.default) # partial
res += f'=*`{getattr(default,"__name__",default)}`*'
return res
def _args(x):
"Formats function params to `param:Type=val` with markdown styling"
try: sig = inspect.signature(x)
except ValueError: return _code(re.search(r"(\([^)]*\))", x.__doc__).group(1)) # C functions
except TypeError: return '' # properties
fmt_params = [_param(v) for k,v in sig.parameters.items() if k not in ('self','cls')]
res = f"({', '.join(fmt_params)})"
ret = anno_dict(x).get('return',None)
if ret: res += f" -> `{typename(ret)}`"
return res
#export
@typedispatch
def format_showdoc(x:typing.Callable):
"Markdown formatted version of `x`"
return f'{_code("def")} {_qualname(x)}{_args(x)}'
@typedispatch
def format_showdoc(x): return _qualname(x)
Markdown(format_showdoc(_T.m))
def _T.m(a:Union[int, str]=0) -> numpy.ndarray
#export
@typedispatch
def format_showdoc(x:type):
ar = _qualname(x)
if inspect.isclass(x): ar = f"{_code('class')} {ar}"
return ar + _args(x) + _basecls(x)
Markdown(format_showdoc(_T))
class _T(x:numpy.ndarray) :: int
#export
@typedispatch
def format_showdoc(x:(Enum,EnumMeta)):
vals = ', '.join(L(x.__members__).map(_code("{}")))
return f'{_code("enum")} = [{vals}]'
Markdown(format_showdoc(e))
enum = [b, c]
#export
_det_tmpl = """<details>
<summary>source</summary>
```python
{code}
```
</details>
"""
def show_sourcecode(o, maxlines=15):
"Collapsible section showing source, without signature or docstring"
try: src = inspect.getsource(o)
except TypeError: return '' # builtin
tree = ast.parse(dedent(src)).body[0]
start,end = tree.body[0].lineno,tree.body[-1].end_lineno
if end-start>maxlines: return '' # too big
body_src = dedent('\n'.join(src.splitlines()[start:end]))
return _det_tmpl.format(code=body_src)
Markdown(show_sourcecode(ShowdocLookup))
def __init__(self, strip_libs=None, incl_libs=None, skip_mods=None):
skip_mods,strip_libs = setify(skip_mods),L(strip_libs)
if incl_libs is not None: incl_libs = (L(incl_libs)+strip_libs).unique()
self.entries = filter_keys(nbdev_idxs, lambda k: incl_libs is None or k in incl_libs)
py_syms = merge(*L(o.modidx['syms'].values() for o in self.entries.values()).concat())
for m in strip_libs:
_d = self.entries[m].modidx
stripped = {remove_prefix(k,f"{mod}."):v
for mod,dets in _d['syms'].items() if mod not in skip_mods
for k,v in dets.items()}
py_syms = merge(stripped, py_syms)
self.syms = py_syms
def __getitem__(self, s): return self.syms.get(s, None)
Markdown(show_sourcecode(ShowdocLookup.linkify))
in_fence=False
lines = md.splitlines()
for i,l in enumerate(lines):
if l.startswith("```"): in_fence=not in_fence
elif not l.startswith(' ') and not in_fence: lines[i] = self._link_line(l, L(skipped))
return '\n'.join(lines)
#export
def show_doc(elt, doc_string=True, name=None, title_level=None, disp=True, default_level=2):
"Show documentation for element `elt`. Supported types: class, function, and enum."
elt = getattr(elt, '__func__', elt)
args = format_showdoc(elt)
title_level = title_level or default_level
doc = f'<h{title_level} id="{_qualname(elt)}" class="doc_header">{_name(elt)}{_sourcelink(elt)}</h{title_level}>\n\n'
if args: doc += f'> {args}\n\n'
doc += show_sourcecode(elt) + _docstring(elt)
doc = showdoc_lookup().linkify(doc)
if disp:
if Markdown: display(Markdown(doc))
else: print(doc)
else: return doc
objs[:-1].map(show_doc);
enum= [b,c]
An enumeration.
_T.m(a:Union[int, str]=0) ->numpy.ndarray
return numpy.array([1])
A method returning numpy.ndarray
max(iterable, *[, default=obj, key=func])
max(iterable, *[, default=obj, key=func]) -> value
max(arg1, arg2, *args, *[, key=func]) -> value
With a single iterable argument, return its biggest item. The
default keyword-only argument specifies an object to return if
the provided iterable is empty.
With two or more arguments, return the largest argument.
#export
def nbdev_module(sym):
return nested_idx(nbdev_idx_mods, sym.__module__, 'syms', sym.__module__)
def nbdev_doclink(sym):
nbmod = nbdev_module(sym)
if not nbmod: return ''
k = sym.__module__
if not inspect.ismodule(sym): k += '.' + qualname(sym)
return nbmod[k]
nbdev_doclink(ShowdocLookup)
'https://fastai.github.io/showdoc.lookup#ShowdocLookup'
#export
def doc(elt):
"Show `show_doc` info in preview window when used in a notebook"
md = show_doc(elt, disp=False)
doc_link = nbdev_doclink(elt)
if doc_link is not None:
md += f'\n\n<a href="{doc_link}" target="_blank" rel="noreferrer noopener">Show in docs</a>'
display(Markdown(md))
doc(ShowdocLookup)
classShowdocLookup(strip_libs=None,incl_libs=None,skip_mods=None) ::object
def __init__(self, strip_libs=None, incl_libs=None, skip_mods=None):
skip_mods,strip_libs = setify(skip_mods),L(strip_libs)
if incl_libs is not None: incl_libs = (L(incl_libs)+strip_libs).unique()
self.entries = filter_keys(nbdev_idxs, lambda k: incl_libs is None or k in incl_libs)
py_syms = merge(*L(o.modidx['syms'].values() for o in self.entries.values()).concat())
for m in strip_libs:
_d = self.entries[m].modidx
stripped = {remove_prefix(k,f"{mod}."):v
for mod,dets in _d['syms'].items() if mod not in skip_mods
for k,v in dets.items()}
py_syms = merge(stripped, py_syms)
self.syms = py_syms
def __getitem__(self, s): return self.syms.get(s, None)
Mapping from symbol names to URLs with docs
from nbdev.doclinks import nbdev_build_lib
nbdev_build_lib()