From 373a91d2dfed10f39a64886cca0855f9900ecc11 Mon Sep 17 00:00:00 2001 From: Jeremy Howard Date: Sun, 19 Jan 2025 10:37:29 +1000 Subject: [PATCH] fixes #664 --- fastcore/docments.py | 20 +++++--- nbs/06_docments.ipynb | 117 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 108 insertions(+), 29 deletions(-) diff --git a/fastcore/docments.py b/fastcore/docments.py index 02efe9ed..f68a5b3d 100644 --- a/fastcore/docments.py +++ b/fastcore/docments.py @@ -73,9 +73,9 @@ def _param_locs(s, returns=True, args_kwargs=False): if isinstance(defn, (FunctionDef, AsyncFunctionDef)): res = {arg.lineno:arg.arg for arg in defn.args.args} # Add *args if present - if defn.args.vararg and args_kwargs: res[defn.args.vararg.lineno] = defn.args.vararg.arg + if defn.args.vararg: res[defn.args.vararg.lineno] = defn.args.vararg.arg # Add keyword-only args - if args_kwargs: res.update({arg.lineno:arg.arg for arg in defn.args.kwonlyargs}) + res.update({arg.lineno:arg.arg for arg in defn.args.kwonlyargs}) # Add **kwargs if present if defn.args.kwarg and args_kwargs: res[defn.args.kwarg.lineno] = defn.args.kwarg.arg if returns and defn.returns: res[defn.returns.lineno] = 'return' @@ -98,9 +98,12 @@ def _get_comment(line, arg, comments, parms): line -= 1 return dedent('\n'.join(reversed(res))) if res else None -def _get_full(anno, name, default, docs): - if anno==empty and default!=empty: anno = type(default) - return AttrDict(docment=docs.get(name), anno=anno, default=default) +def _get_full(p, docs): + anno = p.annotation + if anno==empty: + if p.default!=empty: anno = type(p.default) + elif p.kind in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD): anno = p.kind + return AttrDict(docment=docs.get(p.name), anno=anno, default=p.default) # %% ../nbs/06_docments.ipynb def _merge_doc(dm, npdoc): @@ -148,8 +151,8 @@ def _docments(s, returns=True, eval_str=False, args_kwargs=False): docs = {arg:_get_comment(line, arg, comments, parms) for line,arg in parms.items()} sig = signature_ex(s, True) - res = {arg:_get_full(p.annotation, p.name, p.default, docs) for arg,p in sig.parameters.items()} - if returns: res['return'] = _get_full(sig.return_annotation, 'return', empty, docs) + res = {name:_get_full(p, docs) for name,p in sig.parameters.items()} + if returns: res['return'] = AttrDict(docment=docs.get('return'), anno=sig.return_annotation, default=empty) res = _merge_docs(res, nps) if eval_str: hints = type_hints(s) @@ -159,8 +162,9 @@ def _docments(s, returns=True, eval_str=False, args_kwargs=False): # %% ../nbs/06_docments.ipynb @delegates(_docments) -def docments(elt, full=False, **kwargs): +def docments(elt, full=False, args_kwargs=False, **kwargs): "Generates a `docment`" + if full: args_kwargs=True r = {} params = set(signature(elt).parameters) params.add('return') diff --git a/nbs/06_docments.ipynb b/nbs/06_docments.ipynb index 1d1fd537..6c2eac6b 100644 --- a/nbs/06_docments.ipynb +++ b/nbs/06_docments.ipynb @@ -245,9 +245,9 @@ " if isinstance(defn, (FunctionDef, AsyncFunctionDef)):\n", " res = {arg.lineno:arg.arg for arg in defn.args.args}\n", " # Add *args if present\n", - " if defn.args.vararg and args_kwargs: res[defn.args.vararg.lineno] = defn.args.vararg.arg\n", + " if defn.args.vararg: res[defn.args.vararg.lineno] = defn.args.vararg.arg\n", " # Add keyword-only args\n", - " if args_kwargs: res.update({arg.lineno:arg.arg for arg in defn.args.kwonlyargs})\n", + " res.update({arg.lineno:arg.arg for arg in defn.args.kwonlyargs})\n", " # Add **kwargs if present\n", " if defn.args.kwarg and args_kwargs: res[defn.args.kwarg.lineno] = defn.args.kwarg.arg\n", " if returns and defn.returns: res[defn.returns.lineno] = 'return'\n", @@ -258,6 +258,27 @@ " return None" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{2: 'a', 3: 'b', 4: 'return'}" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "parms = _param_locs(add)\n", + "parms" + ] + }, { "cell_type": "code", "execution_count": null, @@ -284,9 +305,32 @@ " line -= 1\n", " return dedent('\\n'.join(reversed(res))) if res else None\n", "\n", - "def _get_full(anno, name, default, docs):\n", - " if anno==empty and default!=empty: anno = type(default)\n", - " return AttrDict(docment=docs.get(name), anno=anno, default=default)" + "def _get_full(p, docs):\n", + " anno = p.annotation\n", + " if anno==empty:\n", + " if p.default!=empty: anno = type(p.default)\n", + " elif p.kind in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD): anno = p.kind\n", + " return AttrDict(docment=docs.get(p.name), anno=anno, default=p.default)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'the 1st number to add'" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "_get_comment(2, 'a', {2: ' the 1st number to add'}, parms)" ] }, { @@ -395,8 +439,8 @@ " docs = {arg:_get_comment(line, arg, comments, parms) for line,arg in parms.items()}\n", "\n", " sig = signature_ex(s, True)\n", - " res = {arg:_get_full(p.annotation, p.name, p.default, docs) for arg,p in sig.parameters.items()}\n", - " if returns: res['return'] = _get_full(sig.return_annotation, 'return', empty, docs)\n", + " res = {name:_get_full(p, docs) for name,p in sig.parameters.items()}\n", + " if returns: res['return'] = AttrDict(docment=docs.get('return'), anno=sig.return_annotation, default=empty)\n", " res = _merge_docs(res, nps)\n", " if eval_str:\n", " hints = type_hints(s)\n", @@ -413,8 +457,9 @@ "source": [ "#|export\n", "@delegates(_docments)\n", - "def docments(elt, full=False, **kwargs):\n", + "def docments(elt, full=False, args_kwargs=False, **kwargs):\n", " \"Generates a `docment`\"\n", + " if full: args_kwargs=True\n", " r = {}\n", " params = set(signature(elt).parameters)\n", " params.add('return')\n", @@ -472,6 +517,13 @@ "docments(add)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`args_kwargs=True` adds args and kwargs docs too:" + ] + }, { "cell_type": "code", "execution_count": null, @@ -484,7 +536,7 @@ "{ 'a': 'the 1st number to add',\n", " 'args': 'some args',\n", " 'b': 'the 2nd number to add',\n", - " 'kwargs': 'Passed to the `example` function',\n", + " 'kwargs': None,\n", " 'return': 'the result of adding `a` to `b`'}\n", "```" ], @@ -492,7 +544,7 @@ "{'args': 'some args',\n", " 'a': 'the 1st number to add',\n", " 'b': 'the 2nd number to add',\n", - " 'kwargs': 'Passed to the `example` function',\n", + " 'kwargs': None,\n", " 'return': 'the result of adding `a` to `b`'}" ] }, @@ -517,7 +569,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If you pass `full=True`, the values are `dict` of defaults, types, and docments as values. Note that the type annotation is inferred from the default value, if the annotation is empty and a default is supplied." + "If you pass `full=True`, the values are `dict` of defaults, types, and docments as values. Note that the type annotation is inferred from the default value, if the annotation is empty and a default is supplied. (Note that for `full`, `args_kwargs=True` is always set too.)" ] }, { @@ -532,19 +584,31 @@ "{ 'a': { 'anno': ,\n", " 'default': ,\n", " 'docment': 'the 1st number to add'},\n", + " 'args': { 'anno': <_ParameterKind.VAR_POSITIONAL: 2>,\n", + " 'default': ,\n", + " 'docment': 'some args'},\n", " 'b': { 'anno': ,\n", " 'default': 0,\n", " 'docment': 'the 2nd number to add'},\n", + " 'kwargs': { 'anno': <_ParameterKind.VAR_KEYWORD: 4>,\n", + " 'default': ,\n", + " 'docment': None},\n", " 'return': { 'anno': ,\n", " 'default': ,\n", " 'docment': 'the result of adding `a` to `b`'}}\n", "```" ], "text/plain": [ - "{'a': {'docment': 'the 1st number to add',\n", + "{'args': {'docment': 'some args',\n", + " 'anno': <_ParameterKind.VAR_POSITIONAL: 2>,\n", + " 'default': inspect._empty},\n", + " 'a': {'docment': 'the 1st number to add',\n", " 'anno': int,\n", " 'default': inspect._empty},\n", " 'b': {'docment': 'the 2nd number to add', 'anno': int, 'default': 0},\n", + " 'kwargs': {'docment': None,\n", + " 'anno': <_ParameterKind.VAR_KEYWORD: 4>,\n", + " 'default': inspect._empty},\n", " 'return': {'docment': 'the result of adding `a` to `b`',\n", " 'anno': int,\n", " 'default': inspect._empty}}" @@ -968,11 +1032,21 @@ "data": { "text/markdown": [ "```json\n", - "{'a': 'First', 'b': 'Second', 'return': None}\n", + "{ 'a': {'anno': , 'default': 2, 'docment': 'First'},\n", + " 'b': { 'anno': 'str',\n", + " 'default': ,\n", + " 'docment': 'Second'},\n", + " 'return': { 'anno': ,\n", + " 'default': ,\n", + " 'docment': None}}\n", "```" ], "text/plain": [ - "{'a': 'First', 'return': None, 'b': 'Second'}" + "{'a': {'docment': 'First', 'anno': int, 'default': 2},\n", + " 'return': {'docment': None,\n", + " 'anno': inspect._empty,\n", + " 'default': inspect._empty},\n", + " 'b': {'docment': 'Second', 'anno': 'str', 'default': inspect._empty}}" ] }, "execution_count": null, @@ -981,13 +1055,7 @@ } ], "source": [ - "def _a(a:int=2): return a # First\n", - "\n", - "@delegates(_a)\n", - "def _b(b:str, # Second\n", - " **kwargs): \n", - " return b, (_a(**kwargs)) \n", - "docments(_b)" + "docments(_b, full=True)" ] }, { @@ -1142,6 +1210,13 @@ "#|hide\n", "import nbdev; nbdev.nbdev_export()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": {