diff --git a/fastcore/_nbdev.py b/fastcore/_nbdev.py index 60616f32..6f66eee2 100644 --- a/fastcore/_nbdev.py +++ b/fastcore/_nbdev.py @@ -253,6 +253,8 @@ "get_dataclass_source": "06_docments.ipynb", "get_source": "06_docments.ipynb", "empty": "06_docments.ipynb", + "get_name": "06_docments.ipynb", + "qual_name": "06_docments.ipynb", "docments": "06_docments.ipynb", "test_sig": "07_meta.ipynb", "FixSigMeta": "07_meta.ipynb", diff --git a/fastcore/docments.py b/fastcore/docments.py index d7edede2..51d97eca 100644 --- a/fastcore/docments.py +++ b/fastcore/docments.py @@ -4,7 +4,8 @@ from __future__ import annotations -__all__ = ['docstring', 'parse_docstring', 'isdataclass', 'get_dataclass_source', 'get_source', 'empty', 'docments'] +__all__ = ['docstring', 'parse_docstring', 'isdataclass', 'get_dataclass_source', 'get_source', 'empty', 'get_name', + 'qual_name', 'docments'] # Cell #nbdev_comment from __future__ import annotations @@ -18,7 +19,7 @@ from inspect import getsource,isfunction,ismethod,isclass,signature,Parameter from dataclasses import dataclass, is_dataclass from .utils import * - +from .meta import delegates from fastcore import docscrape from inspect import isclass,getdoc @@ -108,7 +109,23 @@ def _merge_docs(dms, npdocs): return params # Cell -def docments(s, full=False, returns=True, eval_str=False): +def get_name(obj): + "Get the name of `obj`" + if hasattr(obj, '__name__'): return obj.__name__ + elif getattr(obj, '_name', False): return obj._name + elif hasattr(obj,'__origin__'): return str(obj.__origin__).split('.')[-1] #for types + elif type(obj)==property: return _get_property_name(obj) + else: return str(obj).split('.')[-1] + +# Cell +def qual_name(obj): + "Get the qualified name of `obj`" + if hasattr(obj,'__qualname__'): return obj.__qualname__ + if ismethod(obj): return f"{get_name(obj.__self__)}.{get_name(fn)}" + return get_name(obj) + +# Cell +def _docments(s, returns=True, eval_str=False): "`dict` of parameter names to 'docment-style' comments in function or string `s`" nps = parse_docstring(s) if isclass(s) and not is_dataclass(s): s = s.__init__ # Constructor for a class @@ -125,5 +142,20 @@ def docments(s, full=False, returns=True, eval_str=False): hints = type_hints(s) for k,v in res.items(): if k in hints: v['anno'] = hints.get(k) + return res + +# Cell +@delegates(_docments) +def docments(elt, full=False, **kwargs): + "Generates a `docment`" + res = _docments(elt, **kwargs) + if hasattr(elt, "__delwrap__"): #for delegates + delwrap_dict = _docments(elt.__delwrap__, **kwargs) + for k,v in res.items(): + if k in delwrap_dict and v["docment"] is None and k != "return": + if delwrap_dict[k]["docment"] is not None: + v["docment"] = delwrap_dict[k]["docment"] + f" passed to `{qual_name(elt.__delwrap__)}`" + else: v['docment'] = f"Argument passed to `{qual_name(elt.__delwrap__)}`" + if not full: res = {k:v['docment'] for k,v in res.items()} return AttrDict(res) \ No newline at end of file diff --git a/nbs/06_docments.ipynb b/nbs/06_docments.ipynb index 4d661e22..75a2ba7e 100644 --- a/nbs/06_docments.ipynb +++ b/nbs/06_docments.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -20,7 +20,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -36,14 +36,14 @@ "from inspect import getsource,isfunction,ismethod,isclass,signature,Parameter\n", "from dataclasses import dataclass, is_dataclass\n", "from fastcore.utils import *\n", - "\n", + "from fastcore.meta import delegates\n", "from fastcore import docscrape\n", "from inspect import isclass,getdoc" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -76,7 +76,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -108,7 +108,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -136,7 +136,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -151,7 +151,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -160,7 +160,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -173,7 +173,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -195,7 +195,7 @@ " 'Returns': Parameter(name='', type='int', desc=['the result of adding `a` to `b`'])}" ] }, - "execution_count": null, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -213,7 +213,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -261,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -271,7 +271,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -292,7 +292,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -312,12 +312,74 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "def _get_property_name(p):\n", + " \"Get the name of property `p`\"\n", + " if hasattr(p, 'fget'):\n", + " return p.fget.func.__qualname__ if hasattr(p.fget, 'func') else p.fget.__qualname__\n", + " else: return next(iter(re.findall(r'\\'(.*)\\'', str(p)))).split('.')[-1]" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "#export\n", + "def get_name(obj):\n", + " \"Get the name of `obj`\"\n", + " if hasattr(obj, '__name__'): return obj.__name__\n", + " elif getattr(obj, '_name', False): return obj._name\n", + " elif hasattr(obj,'__origin__'): return str(obj.__origin__).split('.')[-1] #for types\n", + " elif type(obj)==property: return _get_property_name(obj)\n", + " else: return str(obj).split('.')[-1]" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "test_eq(get_name(in_ipython), 'in_ipython')\n", + "test_eq(get_name(L.map), 'map')" + ] + }, + { + "cell_type": "code", + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "#export\n", - "def docments(s, full=False, returns=True, eval_str=False):\n", + "def qual_name(obj):\n", + " \"Get the qualified name of `obj`\"\n", + " if hasattr(obj,'__qualname__'): return obj.__qualname__\n", + " if ismethod(obj): return f\"{get_name(obj.__self__)}.{get_name(fn)}\"\n", + " return get_name(obj)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "assert qual_name(docscrape) == 'fastcore.docscrape'" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "#export\n", + "def _docments(s, returns=True, eval_str=False):\n", " \"`dict` of parameter names to 'docment-style' comments in function or string `s`\"\n", " nps = parse_docstring(s)\n", " if isclass(s) and not is_dataclass(s): s = s.__init__ # Constructor for a class\n", @@ -334,6 +396,28 @@ " hints = type_hints(s)\n", " for k,v in res.items():\n", " if k in hints: v['anno'] = hints.get(k)\n", + " return res" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "#export\n", + "@delegates(_docments)\n", + "def docments(elt, full=False, **kwargs):\n", + " \"Generates a `docment`\"\n", + " res = _docments(elt, **kwargs)\n", + " if hasattr(elt, \"__delwrap__\"): #for delegates\n", + " delwrap_dict = _docments(elt.__delwrap__, **kwargs)\n", + " for k,v in res.items():\n", + " if k in delwrap_dict and v[\"docment\"] is None and k != \"return\":\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: v['docment'] = f\"Argument passed to `{qual_name(elt.__delwrap__)}`\"\n", + " \n", " if not full: res = {k:v['docment'] for k,v in res.items()}\n", " return AttrDict(res)" ] @@ -347,7 +431,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -365,7 +449,7 @@ " 'return': 'the result of adding `a` to `b`'}" ] }, - "execution_count": null, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -383,7 +467,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -411,7 +495,7 @@ " 'default': inspect._empty}}" ] }, - "execution_count": null, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -422,7 +506,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "metadata": {}, "outputs": [ { @@ -450,7 +534,7 @@ " 'default': inspect._empty}}" ] }, - "execution_count": null, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -468,7 +552,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 24, "metadata": {}, "outputs": [ { @@ -484,7 +568,7 @@ "{'docment': 'the 1st number to add', 'anno': int, 'default': inspect._empty}" ] }, - "execution_count": null, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -502,7 +586,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ @@ -519,7 +603,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "metadata": {}, "outputs": [ { @@ -539,7 +623,7 @@ " 'return': \"The result is calculated using Python's builtin `+` operator.\"}" ] }, - "execution_count": null, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -557,7 +641,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 27, "metadata": {}, "outputs": [], "source": [ @@ -576,7 +660,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 28, "metadata": {}, "outputs": [ { @@ -590,7 +674,7 @@ "{'self': None, 'a': 'First operand', 'b': '2nd operand', 'return': None}" ] }, - "execution_count": null, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -601,7 +685,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 29, "metadata": {}, "outputs": [ { @@ -615,7 +699,7 @@ "{'self': None, 'return': 'Integral result of addition operator'}" ] }, - "execution_count": null, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -633,7 +717,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 30, "metadata": {}, "outputs": [ { @@ -664,7 +748,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 31, "metadata": {}, "outputs": [ { @@ -682,7 +766,7 @@ " 'return': 'the result of adding `a` to `b`'}" ] }, - "execution_count": null, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -700,7 +784,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 32, "metadata": {}, "outputs": [], "source": [ @@ -718,7 +802,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 33, "metadata": {}, "outputs": [ { @@ -746,7 +830,7 @@ " 'return': {'docment': 'the result', 'anno': 'int', 'default': inspect._empty}}" ] }, - "execution_count": null, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -764,7 +848,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 34, "metadata": {}, "outputs": [ { @@ -778,7 +862,7 @@ "{'name': None, 'age': None, 'weight': None, 'return': None}" ] }, - "execution_count": null, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -802,7 +886,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 35, "metadata": {}, "outputs": [], "source": [ @@ -830,7 +914,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 36, "metadata": {}, "outputs": [], "source": [ @@ -844,6 +928,132 @@ "test_eq(docments(_F.class_method), {'foo': 'docment for parameter foo', 'return': None})" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Docments even works with `@delegates`:" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [], + "source": [ + "#|hide\n", + "\n", + "# Test delegates\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)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```json\n", + "{ 'a': {'anno': 'int', 'default': 2, 'docment': 'First'},\n", + " 'return': { 'anno': ,\n", + " 'default': ,\n", + " 'docment': None}}\n", + "```" + ], + "text/plain": [ + "{'a': {'docment': 'First', 'anno': 'int', 'default': 2},\n", + " 'return': {'docment': None,\n", + " 'anno': inspect._empty,\n", + " 'default': inspect._empty}}" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docments(_a, full=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```json\n", + "{ 'a': {'anno': 'int', 'default': 2, 'docment': 'Blah passed to `_c`'},\n", + " 'b': { 'anno': 'str',\n", + " 'default': ,\n", + " 'docment': 'Second passed to `_c`'},\n", + " 'c': {'anno': 'int', 'default': , 'docment': 'First'},\n", + " 'return': { 'anno': ,\n", + " 'default': ,\n", + " 'docment': None}}\n", + "```" + ], + "text/plain": [ + "{'c': {'docment': 'First', 'anno': 'int', 'default': inspect._empty},\n", + " 'b': {'docment': 'Second passed to `_c`',\n", + " 'anno': 'str',\n", + " 'default': inspect._empty},\n", + " 'a': {'docment': 'Blah passed to `_c`', 'anno': 'int', 'default': 2},\n", + " 'return': {'docment': None,\n", + " 'anno': inspect._empty,\n", + " 'default': inspect._empty}}" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "docments(_d, full=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [], + "source": [ + "test_eq(docments(_b, full=True)['a']['docment'],'First passed to `_a`')\n", + "test_eq(docments(_c, full=True)['b']['docment'],'Second')\n", + "test_eq(docments(_d, full=True)['b']['docment'], 'Second passed to `_c`')\n", + "_argset = {'a', 'b', 'c', 'return'}\n", + "test_eq(set(docments(_d, full=True).keys()).intersection(_argset), _argset) # _d has the args a,b,c and return" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -853,7 +1063,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 41, "metadata": {}, "outputs": [ { @@ -887,6 +1097,18 @@ "display_name": "Python 3 (ipykernel)", "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.9.7" } }, "nbformat": 4,