Skip to content

Commit 34a7990

Browse files
committed
refactor docments
1 parent 8ca9570 commit 34a7990

5 files changed

Lines changed: 629 additions & 331 deletions

File tree

fastcore/_modidx.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -252,14 +252,15 @@
252252
'fastcore/docments.py'),
253253
'fastcore.docments.DocmentTbl.return_str': ( 'docments.html#docmenttbl.return_str',
254254
'fastcore/docments.py'),
255-
'fastcore.docments.DocmentsText': ('docments.html#docmentstext', 'fastcore/docments.py'),
256-
'fastcore.docments.DocmentsText.__str__': ('docments.html#docmentstext.__str__', 'fastcore/docments.py'),
257-
'fastcore.docments.DocmentsText._fmt_param': ( 'docments.html#docmentstext._fmt_param',
258-
'fastcore/docments.py'),
259-
'fastcore.docments.DocmentsText._repr_markdown_': ( 'docments.html#docmentstext._repr_markdown_',
260-
'fastcore/docments.py'),
261-
'fastcore.docments.DocmentsText._ret_str': ( 'docments.html#docmentstext._ret_str',
262-
'fastcore/docments.py'),
255+
'fastcore.docments.DocmentText': ('docments.html#docmenttext', 'fastcore/docments.py'),
256+
'fastcore.docments.DocmentText.__init__': ('docments.html#docmenttext.__init__', 'fastcore/docments.py'),
257+
'fastcore.docments.DocmentText.__str__': ('docments.html#docmenttext.__str__', 'fastcore/docments.py'),
258+
'fastcore.docments.DocmentText._fmt_param': ( 'docments.html#docmenttext._fmt_param',
259+
'fastcore/docments.py'),
260+
'fastcore.docments.DocmentText._repr_markdown_': ( 'docments.html#docmenttext._repr_markdown_',
261+
'fastcore/docments.py'),
262+
'fastcore.docments.DocmentText._ret_str': ('docments.html#docmenttext._ret_str', 'fastcore/docments.py'),
263+
'fastcore.docments.DocmentText.params': ('docments.html#docmenttext.params', 'fastcore/docments.py'),
263264
'fastcore.docments.MarkdownRenderer': ('docments.html#markdownrenderer', 'fastcore/docments.py'),
264265
'fastcore.docments.MarkdownRenderer._repr_markdown_': ( 'docments.html#markdownrenderer._repr_markdown_',
265266
'fastcore/docments.py'),
@@ -282,13 +283,10 @@
282283
'fastcore/docments.py'),
283284
'fastcore.docments._bold': ('docments.html#_bold', 'fastcore/docments.py'),
284285
'fastcore.docments._clean_comment': ('docments.html#_clean_comment', 'fastcore/docments.py'),
285-
'fastcore.docments._docments': ('docments.html#_docments', 'fastcore/docments.py'),
286286
'fastcore.docments._docstring': ('docments.html#_docstring', 'fastcore/docments.py'),
287287
'fastcore.docments._escape_markdown': ('docments.html#_escape_markdown', 'fastcore/docments.py'),
288-
'fastcore.docments._ext_link': ('docments.html#_ext_link', 'fastcore/docments.py'),
289288
'fastcore.docments._f_name': ('docments.html#_f_name', 'fastcore/docments.py'),
290289
'fastcore.docments._fmt_anno': ('docments.html#_fmt_anno', 'fastcore/docments.py'),
291-
'fastcore.docments._fmt_sig': ('docments.html#_fmt_sig', 'fastcore/docments.py'),
292290
'fastcore.docments._fullname': ('docments.html#_fullname', 'fastcore/docments.py'),
293291
'fastcore.docments._get_comment': ('docments.html#_get_comment', 'fastcore/docments.py'),
294292
'fastcore.docments._get_full': ('docments.html#_get_full', 'fastcore/docments.py'),
@@ -304,7 +302,6 @@
304302
'fastcore.docments._parses': ('docments.html#_parses', 'fastcore/docments.py'),
305303
'fastcore.docments._show_param': ('docments.html#_show_param', 'fastcore/docments.py'),
306304
'fastcore.docments._tokens': ('docments.html#_tokens', 'fastcore/docments.py'),
307-
'fastcore.docments._wrap_sig': ('docments.html#_wrap_sig', 'fastcore/docments.py'),
308305
'fastcore.docments.docments': ('docments.html#docments', 'fastcore/docments.py'),
309306
'fastcore.docments.docstring': ('docments.html#docstring', 'fastcore/docments.py'),
310307
'fastcore.docments.extract_docstrings': ('docments.html#extract_docstrings', 'fastcore/docments.py'),

fastcore/basics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ def _eval_param(ann, k, v):
402402
return Parameter(v.name, v.kind, annotation=ann[k], default=v.default)
403403

404404
if not eval_str: return signature(obj)
405-
if _ispy3_10(): return signature(obj, eval_str=eval_str)
405+
# if _ispy3_10(): return signature(obj, eval_str=eval_str)
406406
sig = signature(obj)
407407
if sig is None: return None
408408
ann = type_hints(obj)

fastcore/docments.py

Lines changed: 53 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/04_docments.ipynb.
44

5-
# %% ../nbs/04_docments.ipynb 2
6-
from __future__ import annotations
5+
# %% auto 0
6+
__all__ = ['empty', 'docstring', 'parse_docstring', 'isdataclass', 'get_dataclass_source', 'get_source', 'get_name', 'qual_name',
7+
'docments', 'sig_source', 'extract_docstrings', 'DocmentTbl', 'DocmentList', 'DocmentText', 'sig2str',
8+
'ShowDocRenderer', 'MarkdownRenderer']
79

10+
# %% ../nbs/04_docments.ipynb
811
import re,ast,inspect
912
from tokenize import tokenize,COMMENT
1013
from ast import parse,FunctionDef,AsyncFunctionDef,AnnAssign
@@ -17,12 +20,7 @@
1720
from .meta import delegates
1821
from . import docscrape
1922
from textwrap import fill
20-
from inspect import isclass,getdoc
21-
22-
# %% auto 0
23-
__all__ = ['empty', 'docstring', 'parse_docstring', 'isdataclass', 'get_dataclass_source', 'get_source', 'get_name', 'qual_name',
24-
'docments', 'sig2str', 'sig_source', 'extract_docstrings', 'DocmentTbl', 'DocmentList', 'DocmentsText',
25-
'ShowDocRenderer', 'MarkdownRenderer']
23+
from inspect import isclass,getdoc,signature
2624

2725
# %% ../nbs/04_docments.ipynb
2826
def docstring(sym):
@@ -112,7 +110,8 @@ def _get_full(p, docs):
112110
# %% ../nbs/04_docments.ipynb
113111
def _merge_doc(dm, npdoc):
114112
if not npdoc: return dm
115-
if not dm.anno or dm.anno==empty: dm.anno = npdoc.type
113+
if not isinstance(dm, dict): return dm or '\n'.join(npdoc.desc)
114+
# if not dm.anno or dm.anno==empty: dm.anno = npdoc.type
116115
if not dm.docment: dm.docment = '\n'.join(npdoc.desc)
117116
return dm
118117

@@ -146,64 +145,25 @@ def qual_name(obj):
146145
return get_name(obj)
147146

148147
# %% ../nbs/04_docments.ipynb
149-
def _docments(s, returns=True, eval_str=False, args_kwargs=False):
150-
"`dict` of parameter names to 'docment-style' comments in function or string `s`"
148+
def docments(s, full=False, eval_str=False, returns=True, args_kwargs=False):
149+
"Get docments for `s`"
150+
if isclass(s) and not is_dataclass(s): s = s.__init__
151+
try: sig = signature_ex(s, eval_str=eval_str)
152+
except ValueError: return AttrDict()
151153
nps = parse_docstring(s)
152-
if isclass(s) and not is_dataclass(s): s = s.__init__ # Constructor for a class
153-
comments = {o.start[0]:_clean_comment(o.string) for o in _tokens(s) if o.type==COMMENT}
154-
parms = _param_locs(s, returns=returns, args_kwargs=args_kwargs) or {}
155-
docs = {arg:_get_comment(line, arg, comments, parms) for line,arg in parms.items()}
156-
157-
sig = signature_ex(s, True)
158-
res = {name:_get_full(p, docs) for name,p in sig.parameters.items()}
159-
if returns: res['return'] = AttrDict(docment=docs.get('return'), anno=sig.return_annotation, default=empty)
160-
res = _merge_docs(res, nps)
161-
if eval_str:
162-
hints = type_hints(s)
163-
for k,v in res.items():
164-
if k in hints: v['anno'] = hints.get(k)
165-
return res
166-
167-
# %% ../nbs/04_docments.ipynb
168-
@delegates(_docments)
169-
def docments(elt, full=False, args_kwargs=False, **kwargs):
170-
"Generates a `docment`"
171-
if full: args_kwargs=True
172-
r = {}
173-
params = set(signature(elt).parameters)
174-
params.add('return')
175-
176-
def _update_docments(f, r):
177-
if hasattr(f, '__delwrap__'): _update_docments(f.__delwrap__, r)
178-
r.update({k:v for k,v in _docments(f, **kwargs).items() if k in params
179-
and (v.get('docment', None) or not nested_idx(r, k, 'docment'))})
180-
181-
_update_docments(elt, r)
182-
if not full: r = {k:v['docment'] for k,v in r.items()}
183-
return AttrDict(r)
184-
185-
# %% ../nbs/04_docments.ipynb
186-
def sig2str(func):
187-
"Generate function signature with docments as comments"
188-
docs = docments(func, full=True)
189-
params = []
190-
for k,v in docs.items():
191-
if k == 'return': continue
192-
anno = getattr(v['anno'], '__name__', str(v['anno'])) if v['anno'] != inspect._empty else ''
193-
if '|' in str(v['anno']): anno = str(v['anno'])
194-
p = k + (f':{anno}' if anno and anno != 'inspect._empty' else '')
195-
if v['default'] != inspect._empty:
196-
d = getattr(v['default'], '__name__', v['default']) if hasattr(v['default'], '__name__') else v['default']
197-
p += f'={d}' if d is not None else '=None'
198-
if v['docment']: p += f' # {v["docment"]}'
199-
params.append(p)
154+
docs = {}
155+
while s:
156+
p = _param_locs(s, returns=returns, args_kwargs=args_kwargs) or {}
157+
c = {o.start[0]:_clean_comment(o.string) for o in _tokens(s) if o.type==COMMENT}
158+
for k,v in p.items():
159+
if v not in docs: docs[v] = _get_comment(k, v, c, p)
160+
s = getattr(s, '__delwrap__', None)
200161

201-
ret = docs.get('return', {})
202-
ret_str = ':'
203-
if ret and ret.get('anno')!=inspect._empty:
204-
ret_str = f"->{getattr(ret['anno'], '__name__', str(ret['anno']))}" + (f': # {ret["docment"]}' if ret.get('docment') else ':')
205-
doc_str = f' "{func.__doc__}"' if func.__doc__ else ''
206-
return f"def {func.__name__}(\n " + ",\n ".join(params) + f"\n){ret_str}\n{doc_str}"
162+
res = {k:_get_full(v, docs) if full else docs.get(k) for k,v in sig.parameters.items()}
163+
if returns:
164+
if full: res['return'] = AttrDict(docment=docs.get('return'), anno=sig.return_annotation, default=empty)
165+
else: res['return'] = docs.get('return')
166+
return AttrDict(_merge_docs(res, nps))
207167

208168
# %% ../nbs/04_docments.ipynb
209169
def sig_source(obj):
@@ -294,7 +254,7 @@ def __init__(self, obj, verbose=True, returns=True):
294254
super().__init__(obj)
295255
self.verbose = verbose
296256
self.returns = False if isdataclass(obj) else returns
297-
try: self.params = L(signature_ex(obj, eval_str=True).parameters.keys())
257+
try: self.params = L(signature(obj, eval_str=True).parameters.keys())
298258
except (ValueError,TypeError): self.params=[]
299259
for d in self.dm.values(): d['docment'] = ifnone(d['docment'], inspect._empty)
300260

@@ -354,7 +314,11 @@ def _repr_markdown_(self): return '\n'.join(self._fmt(k,v) for k,v in self.dm.it
354314
__repr__=__str__=_repr_markdown_
355315

356316
# %% ../nbs/04_docments.ipynb
357-
class DocmentsText(_DocmentBase):
317+
class DocmentText(_DocmentBase):
318+
def __init__(self, obj, maxline=160, docstring=True):
319+
super().__init__(obj)
320+
self.maxline,self.docstring = maxline,docstring
321+
358322
def _fmt_param(self, nm, p):
359323
anno,default = p.get('anno',empty), p.get('default',empty)
360324
return nm + (f':{_maybe_nm(anno)}' if anno != empty else '') + (f'={repr(default)}' if default != empty else '')
@@ -366,20 +330,31 @@ def _ret_str(self):
366330
doc = f" # {ret['docment']}" if ret.get('docment') else ''
367331
return f"){anno}:{doc}"
368332

333+
@property
334+
def params(self): return [(self._fmt_param(k,v), v.get('docment','')) for k,v in self.dm.items() if k != 'return']
335+
369336
def __str__(self):
370-
params = [(self._fmt_param(k,v), v.get('docment','')) for k,v in self.dm.items() if k != 'return']
371337
lines,curr = [],[]
372-
for fmt,doc in params:
338+
for fmt,doc in self.params:
339+
comment = f' # {doc}' if doc else ''
340+
if curr and len(', '.join(curr))+len(fmt)+len(comment)>self.maxline:
341+
lines.append(', '.join(curr) + ',')
342+
curr = []
373343
curr.append(fmt)
374-
if doc: lines.append((', '.join(curr), doc)); curr = []
375-
if curr: lines.append((', '.join(curr), ''))
376-
body = [f"{line}{'' if i==len(lines)-1 else ','}{f' # {doc}' if doc else ''}" for i,(line,doc) in enumerate(lines)]
377-
docstr = f' "{self.obj.__doc__}"' if self.obj.__doc__ else ''
378-
return f"def {self.obj.__name__}(\n " + "\n ".join(body) + f"\n{self._ret_str}\n{docstr}"
344+
if doc: lines.append(', '.join(curr) + ',' + comment); curr = []
345+
if curr: lines.append(', '.join(curr))
346+
body = '\n '.join(lines)
347+
docstr = f' "{self.obj.__doc__}"' if self.docstring and self.obj.__doc__ else ''
348+
return f"def {self.obj.__name__}(\n {body}\n{self._ret_str}\n{docstr}"
379349

380350
__repr__ = __str__
381351
def _repr_markdown_(self): return f"```python\n{self}\n```"
382352

353+
# %% ../nbs/04_docments.ipynb
354+
def sig2str(func, maxline=160):
355+
"Generate function signature with docments as comments"
356+
return DocmentText(func, maxline=maxline, docstring=False)
357+
383358
# %% ../nbs/04_docments.ipynb
384359
def _docstring(sym):
385360
npdoc = parse_docstring(sym)
@@ -391,17 +366,17 @@ def _fullname(o):
391366
return name if module is None or module in ('__main__','builtins') else module + '.' + name
392367

393368
class ShowDocRenderer:
394-
def __init__(self, sym, name:str|None=None, title_level:int=3):
369+
def __init__(self, sym, name:str|None=None, title_level:int=3, maxline:int=120):
395370
"Show documentation for `sym`"
396371
sym = getattr(sym, '__wrapped__', sym)
397372
sym = getattr(sym, 'fget', None) or getattr(sym, 'fset', None) or sym
398373
store_attr()
399374
self.nm = name or qual_name(sym)
400375
self.isfunc = inspect.isfunction(sym)
401-
try: self.sig = signature_ex(sym, eval_str=True)
376+
try: self.sig = signature(sym, eval_str=True)
402377
except (ValueError,TypeError): self.sig = None
403378
self.docs = _docstring(sym)
404-
self.dm = DocmentList(sym)
379+
self.dm = DocmentText(sym, maxline=maxline, docstring=False)
405380
self.fn = _fullname(sym)
406381

407382
__repr__ = basic_repr()
@@ -420,30 +395,15 @@ def _show_param(param):
420395
return res
421396

422397
# %% ../nbs/04_docments.ipynb
423-
def _fmt_sig(sig):
424-
if sig is None: return ''
425-
p = {k:v for k,v in sig.parameters.items()}
426-
_params = [_show_param(p[k]) for k in p.keys() if k != 'self']
427-
return "(" + ', '.join(_params) + ")"
428-
429-
def _wrap_sig(s):
430-
"wrap a signature to appear on multiple lines if necessary."
431-
pad = '> ' + ' ' * 5
432-
indent = pad + ' ' * (s.find('(') + 1)
433-
return fill(s, width=80, initial_indent=pad, subsequent_indent=indent)
434-
435398
def _ital_first(s:str):
436399
"Surround first line with * for markdown italics, preserving leading spaces"
437400
return re.sub(r'^(\s*)(.+)', r'\1*\2*', s, count=1)
438401

439402
# %% ../nbs/04_docments.ipynb
440-
def _ext_link(url, txt, xtra=""): return f'[{txt}]({url}){{target="_blank" {xtra}}}'
441-
442403
class MarkdownRenderer(ShowDocRenderer):
443404
"Markdown renderer for `show_doc`"
444405
def _repr_markdown_(self):
445-
doc = _wrap_sig(f"{self.nm} {_fmt_sig(self.sig)}") if self.sig else ''
406+
doc = f'```python\n\n{self.dm}\n\n```'
446407
if self.docs: doc += f"\n\n{_ital_first(self.docs)}"
447-
if self.dm.has_docment: doc += f"\n\n{self.dm}"
448408
return doc
449409
__repr__=__str__=_repr_markdown_

nbs/01_basics.ipynb

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2213,7 +2213,7 @@
22132213
" return Parameter(v.name, v.kind, annotation=ann[k], default=v.default)\n",
22142214
"\n",
22152215
" if not eval_str: return signature(obj)\n",
2216-
" if _ispy3_10(): return signature(obj, eval_str=eval_str)\n",
2216+
" # if _ispy3_10(): return signature(obj, eval_str=eval_str)\n",
22172217
" sig = signature(obj)\n",
22182218
" if sig is None: return None\n",
22192219
" ann = type_hints(obj)\n",
@@ -8063,13 +8063,7 @@
80638063
]
80648064
}
80658065
],
8066-
"metadata": {
8067-
"kernelspec": {
8068-
"display_name": "python3",
8069-
"language": "python",
8070-
"name": "python3"
8071-
}
8072-
},
8066+
"metadata": {},
80738067
"nbformat": 4,
80748068
"nbformat_minor": 5
80758069
}

0 commit comments

Comments
 (0)