fastcore 2.2.22__tar.gz → 2.2.23__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastcore-2.2.22/fastcore.egg-info → fastcore-2.2.23}/PKG-INFO +2 -2
- {fastcore-2.2.22 → fastcore-2.2.23}/README.md +1 -1
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/__init__.py +2 -2
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/_modidx.py +3 -8
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/apisurface.py +25 -25
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/docments.py +162 -155
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/nbio.py +15 -9
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/xtras.py +9 -4
- {fastcore-2.2.22 → fastcore-2.2.23/fastcore.egg-info}/PKG-INFO +2 -2
- {fastcore-2.2.22 → fastcore-2.2.23}/CONTRIBUTING.md +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/LICENSE +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/aio.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/all.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/ansi.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/basics.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/dispatch.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/docscrape.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/editskill.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/foundation.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/funccall.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/imghdr.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/imports.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/meta.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/nb_imports.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/net.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/parallel.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/py2pyi.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/script.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/shutil.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/style.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/test.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/tools.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/transform.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/utils.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/xdg.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore/xml.py +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore.egg-info/SOURCES.txt +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore.egg-info/dependency_links.txt +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore.egg-info/entry_points.txt +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore.egg-info/requires.txt +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/fastcore.egg-info/top_level.txt +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/pyproject.toml +0 -0
- {fastcore-2.2.22 → fastcore-2.2.23}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastcore
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.23
|
|
4
4
|
Summary: Python supercharged for fastai development
|
|
5
5
|
Author-email: Jeremy Howard and Sylvain Gugger <infos@fast.ai>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -54,7 +54,7 @@ Here are some tips on using fastcore:
|
|
|
54
54
|
- **Advanced file operations**: Use the extended `Path` class, which adds methods like `ls()`, `read_json()`, and others to [`pathlib.Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path).
|
|
55
55
|
- **Flexible data structures**: Convert between dictionaries and attribute-access objects using [`dict2obj`](https://fastcore.fast.ai/xtras.html#dict2obj) and [`obj2dict`](https://fastcore.fast.ai/xtras.html#obj2dict) for more intuitive data handling.
|
|
56
56
|
- **Functional programming paradigms**: Use tools like [`compose`](https://fastcore.fast.ai/basics.html#compose), [`maps`](https://fastcore.fast.ai/basics.html#maps), and [`filter_ex`](https://fastcore.fast.ai/basics.html#filter_ex) to write more functional-style Python code.
|
|
57
|
-
- **Documentation**:
|
|
57
|
+
- **Documentation**: Document parameters and return values with [`docments`](https://fastcore.fast.ai/docments.html#docments), using source comments or `Annotated` metadata for generated APIs. [`MarkdownRenderer`](https://fastcore.fast.ai/docments.html#markdownrenderer) shows the effective signature and retains usage sections such as Notes, Raises, and Examples.
|
|
58
58
|
- **Time-aware caching**: Apply the [`timed_cache`](https://fastcore.fast.ai/xtras.html#timed_cache) decorator to add time-based expiration to the standard `lru_cache` functionality.
|
|
59
59
|
- **Simplified CLI creation**: Use `fastcore.script` to easily transform Python functions into command-line interfaces.
|
|
60
60
|
|
|
@@ -23,7 +23,7 @@ Here are some tips on using fastcore:
|
|
|
23
23
|
- **Advanced file operations**: Use the extended `Path` class, which adds methods like `ls()`, `read_json()`, and others to [`pathlib.Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path).
|
|
24
24
|
- **Flexible data structures**: Convert between dictionaries and attribute-access objects using [`dict2obj`](https://fastcore.fast.ai/xtras.html#dict2obj) and [`obj2dict`](https://fastcore.fast.ai/xtras.html#obj2dict) for more intuitive data handling.
|
|
25
25
|
- **Functional programming paradigms**: Use tools like [`compose`](https://fastcore.fast.ai/basics.html#compose), [`maps`](https://fastcore.fast.ai/basics.html#maps), and [`filter_ex`](https://fastcore.fast.ai/basics.html#filter_ex) to write more functional-style Python code.
|
|
26
|
-
- **Documentation**:
|
|
26
|
+
- **Documentation**: Document parameters and return values with [`docments`](https://fastcore.fast.ai/docments.html#docments), using source comments or `Annotated` metadata for generated APIs. [`MarkdownRenderer`](https://fastcore.fast.ai/docments.html#markdownrenderer) shows the effective signature and retains usage sections such as Notes, Raises, and Examples.
|
|
27
27
|
- **Time-aware caching**: Apply the [`timed_cache`](https://fastcore.fast.ai/xtras.html#timed_cache) decorator to add time-based expiration to the standard `lru_cache` functionality.
|
|
28
28
|
- **Simplified CLI creation**: Use `fastcore.script` to easily transform Python functions into command-line interfaces.
|
|
29
29
|
|
|
@@ -14,7 +14,7 @@ Here are some tips on using fastcore:
|
|
|
14
14
|
- **Advanced file operations**: Use the extended `Path` class, which adds methods like `ls()`, `read_json()`, and others to `pathlib.Path`.
|
|
15
15
|
- **Flexible data structures**: Convert between dictionaries and attribute-access objects using `dict2obj` and `obj2dict` for more intuitive data handling.
|
|
16
16
|
- **Functional programming paradigms**: Use tools like `compose`, `maps`, and `filter_ex` to write more functional-style Python code.
|
|
17
|
-
- **Documentation**:
|
|
17
|
+
- **Documentation**: Document parameters and return values with `docments`, using source comments or `Annotated` metadata for generated APIs. `MarkdownRenderer` shows the effective signature and retains usage sections such as Notes, Raises, and Examples.
|
|
18
18
|
- **Time-aware caching**: Apply the `timed_cache` decorator to add time-based expiration to the standard `lru_cache` functionality.
|
|
19
19
|
- **Simplified CLI creation**: Use `fastcore.script` to easily transform Python functions into command-line interfaces.
|
|
20
20
|
|
|
@@ -55,4 +55,4 @@ Modules:
|
|
|
55
55
|
- `fastcore.xml`: Concise generation of XML.
|
|
56
56
|
- `fastcore.xtras`: Utility functions used in the fastai library"""
|
|
57
57
|
|
|
58
|
-
__version__ = "2.2.
|
|
58
|
+
__version__ = "2.2.23"
|
|
@@ -315,11 +315,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
315
315
|
'fastcore.docments.DocmentTbl.__eq__': ('docments.html#docmenttbl.__eq__', 'fastcore/docments.py'),
|
|
316
316
|
'fastcore.docments.DocmentTbl.__init__': ('docments.html#docmenttbl.__init__', 'fastcore/docments.py'),
|
|
317
317
|
'fastcore.docments.DocmentTbl._columns': ('docments.html#docmenttbl._columns', 'fastcore/docments.py'),
|
|
318
|
-
'fastcore.docments.DocmentTbl._hdr_list': ('docments.html#docmenttbl._hdr_list', 'fastcore/docments.py'),
|
|
319
318
|
'fastcore.docments.DocmentTbl._repr_markdown_': ( 'docments.html#docmenttbl._repr_markdown_',
|
|
320
319
|
'fastcore/docments.py'),
|
|
321
|
-
'fastcore.docments.DocmentTbl._row': ('docments.html#docmenttbl._row', 'fastcore/docments.py'),
|
|
322
|
-
'fastcore.docments.DocmentTbl._row_list': ('docments.html#docmenttbl._row_list', 'fastcore/docments.py'),
|
|
323
320
|
'fastcore.docments.DocmentTbl.has_docment': ( 'docments.html#docmenttbl.has_docment',
|
|
324
321
|
'fastcore/docments.py'),
|
|
325
322
|
'fastcore.docments.DocmentTbl.has_return': ( 'docments.html#docmenttbl.has_return',
|
|
@@ -361,12 +358,11 @@ d = { 'settings': { 'branch': 'main',
|
|
|
361
358
|
'fastcore.docments._DocstringExtractor.visit_Module': ( 'docments.html#_docstringextractor.visit_module',
|
|
362
359
|
'fastcore/docments.py'),
|
|
363
360
|
'fastcore.docments._bold': ('docments.html#_bold', 'fastcore/docments.py'),
|
|
361
|
+
'fastcore.docments._callable_instance': ('docments.html#_callable_instance', 'fastcore/docments.py'),
|
|
364
362
|
'fastcore.docments._clean_comment': ('docments.html#_clean_comment', 'fastcore/docments.py'),
|
|
365
363
|
'fastcore.docments._clean_text_sig': ('docments.html#_clean_text_sig', 'fastcore/docments.py'),
|
|
366
364
|
'fastcore.docments._docstring': ('docments.html#_docstring', 'fastcore/docments.py'),
|
|
367
365
|
'fastcore.docments._escape_markdown': ('docments.html#_escape_markdown', 'fastcore/docments.py'),
|
|
368
|
-
'fastcore.docments._f_name': ('docments.html#_f_name', 'fastcore/docments.py'),
|
|
369
|
-
'fastcore.docments._fmt_anno': ('docments.html#_fmt_anno', 'fastcore/docments.py'),
|
|
370
366
|
'fastcore.docments._fmt_default': ('docments.html#_fmt_default', 'fastcore/docments.py'),
|
|
371
367
|
'fastcore.docments._fmt_sig': ('docments.html#_fmt_sig', 'fastcore/docments.py'),
|
|
372
368
|
'fastcore.docments._fullname': ('docments.html#_fullname', 'fastcore/docments.py'),
|
|
@@ -377,13 +373,11 @@ d = { 'settings': { 'branch': 'main',
|
|
|
377
373
|
'fastcore.docments._ital_first': ('docments.html#_ital_first', 'fastcore/docments.py'),
|
|
378
374
|
'fastcore.docments._list2row': ('docments.html#_list2row', 'fastcore/docments.py'),
|
|
379
375
|
'fastcore.docments._maybe_nm': ('docments.html#_maybe_nm', 'fastcore/docments.py'),
|
|
380
|
-
'fastcore.docments._merge_doc': ('docments.html#_merge_doc', 'fastcore/docments.py'),
|
|
381
376
|
'fastcore.docments._merge_docs': ('docments.html#_merge_docs', 'fastcore/docments.py'),
|
|
382
|
-
'fastcore.docments._non_empty_keys': ('docments.html#_non_empty_keys', 'fastcore/docments.py'),
|
|
383
377
|
'fastcore.docments._param_locs': ('docments.html#_param_locs', 'fastcore/docments.py'),
|
|
384
378
|
'fastcore.docments._parses': ('docments.html#_parses', 'fastcore/docments.py'),
|
|
385
|
-
'fastcore.docments._show_param': ('docments.html#_show_param', 'fastcore/docments.py'),
|
|
386
379
|
'fastcore.docments._tokens': ('docments.html#_tokens', 'fastcore/docments.py'),
|
|
380
|
+
'fastcore.docments._type_str': ('docments.html#_type_str', 'fastcore/docments.py'),
|
|
387
381
|
'fastcore.docments._unwrap_sym': ('docments.html#_unwrap_sym', 'fastcore/docments.py'),
|
|
388
382
|
'fastcore.docments.ann_parts': ('docments.html#ann_parts', 'fastcore/docments.py'),
|
|
389
383
|
'fastcore.docments.can_render': ('docments.html#can_render', 'fastcore/docments.py'),
|
|
@@ -650,6 +644,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
650
644
|
'fastcore.nbio.new_nb': ('nbio.html#new_nb', 'fastcore/nbio.py'),
|
|
651
645
|
'fastcore.nbio.preferred_msg_out': ('nbio.html#preferred_msg_out', 'fastcore/nbio.py'),
|
|
652
646
|
'fastcore.nbio.preferred_out': ('nbio.html#preferred_out', 'fastcore/nbio.py'),
|
|
647
|
+
'fastcore.nbio.prev_line': ('nbio.html#prev_line', 'fastcore/nbio.py'),
|
|
653
648
|
'fastcore.nbio.read_nb': ('nbio.html#read_nb', 'fastcore/nbio.py'),
|
|
654
649
|
'fastcore.nbio.render_output': ('nbio.html#render_output', 'fastcore/nbio.py'),
|
|
655
650
|
'fastcore.nbio.render_outputs': ('nbio.html#render_outputs', 'fastcore/nbio.py'),
|
|
@@ -12,12 +12,13 @@ Docs: https://fastcore.fast.ai/apisurface.html.md"""
|
|
|
12
12
|
__all__ = ['snake', 'sanitize_param_name', 'sanitized_params', 'mk_sig', 'mk_doc', 'OpGroup', 'mk_groups', 'full_docs']
|
|
13
13
|
|
|
14
14
|
# %% ../nbs/05a_apisurface.ipynb #b2797a42
|
|
15
|
-
import keyword
|
|
15
|
+
import keyword,inspect
|
|
16
16
|
from inspect import Parameter, Signature
|
|
17
17
|
from urllib.parse import urlparse, urljoin
|
|
18
18
|
from .imports import *
|
|
19
19
|
from .basics import *
|
|
20
20
|
from .xtras import UNSET
|
|
21
|
+
from .docments import ann_parts
|
|
21
22
|
|
|
22
23
|
# %% ../nbs/05a_apisurface.ipynb #475598fa
|
|
23
24
|
_pat_non_alnum = re.compile(r"[^a-zA-Z0-9]+")
|
|
@@ -32,13 +33,6 @@ def snake(s: str):
|
|
|
32
33
|
# %% ../nbs/05a_apisurface.ipynb #3e19a6d0
|
|
33
34
|
def sanitize_param_name(p): return snake(re.sub(r'\W', '_', p).strip('_'))
|
|
34
35
|
|
|
35
|
-
# %% ../nbs/05a_apisurface.ipynb #f44a2c97
|
|
36
|
-
def _mk_param(name, required, anno=None, default=None):
|
|
37
|
-
"Create a function signature parameter."
|
|
38
|
-
anno = Parameter.empty if anno is None else anno
|
|
39
|
-
if default is None: default = Parameter.empty if required else UNSET
|
|
40
|
-
return Parameter(name, kind=Parameter.POSITIONAL_OR_KEYWORD, default=default, annotation=anno)
|
|
41
|
-
|
|
42
36
|
# %% ../nbs/05a_apisurface.ipynb #c10b6dcc
|
|
43
37
|
def sanitized_params(ps):
|
|
44
38
|
"Mapping from spec param names `ps` to valid Python identifiers; exact names win collisions."
|
|
@@ -55,21 +49,28 @@ def sanitized_params(ps):
|
|
|
55
49
|
res[p] = name
|
|
56
50
|
return res
|
|
57
51
|
|
|
58
|
-
# %% ../nbs/05a_apisurface.ipynb #
|
|
52
|
+
# %% ../nbs/05a_apisurface.ipynb #f44a2c97
|
|
53
|
+
def _mk_param(name, required, anno=None, default=Parameter.empty):
|
|
54
|
+
"Create a function signature parameter."
|
|
55
|
+
anno = Parameter.empty if anno is None else anno
|
|
56
|
+
if default is Parameter.empty: default = Parameter.empty if required else UNSET
|
|
57
|
+
return Parameter(name, kind=Parameter.POSITIONAL_OR_KEYWORD, default=default, annotation=anno)
|
|
58
|
+
|
|
59
59
|
def _sort_key(o):
|
|
60
60
|
if o.default is Parameter.empty: return 0
|
|
61
61
|
if o.default is UNSET: return 1
|
|
62
62
|
return 2
|
|
63
63
|
|
|
64
|
-
# %% ../nbs/05a_apisurface.ipynb #4f63ac5e
|
|
65
64
|
def mk_sig(op, sparams=None, defaults=None):
|
|
66
|
-
"An
|
|
65
|
+
"An operation signature with parameter descriptions in `Annotated`; `defaults` makes those params optional"
|
|
67
66
|
if sparams is None: sparams = sanitized_params(op.params)
|
|
68
67
|
defaults = defaults or {}
|
|
69
68
|
params = []
|
|
70
69
|
for pname, sname in sparams.items():
|
|
71
|
-
default = defaults.get(pname, op.param_defaults.get(pname))
|
|
72
|
-
|
|
70
|
+
default = defaults.get(pname, op.param_defaults.get(pname, Parameter.empty))
|
|
71
|
+
anno = op.param_types.get(pname)
|
|
72
|
+
if doc := op.param_docs.get(pname): anno = typing.Annotated[anno or typing.Any, doc]
|
|
73
|
+
params.append(_mk_param(sname, pname in op.required_params, anno, default))
|
|
73
74
|
return Signature(sorted(params, key=_sort_key))
|
|
74
75
|
|
|
75
76
|
# %% ../nbs/05a_apisurface.ipynb #940536d9
|
|
@@ -81,14 +82,6 @@ def _op_summary(op):
|
|
|
81
82
|
base = f"{p.scheme}://{p.netloc}"
|
|
82
83
|
return re.sub(r"\]\((/[^)]+)\)", lambda m: f"]({urljoin(base, m[1].strip())})", s)
|
|
83
84
|
|
|
84
|
-
# %% ../nbs/05a_apisurface.ipynb #08067b8a
|
|
85
|
-
def _op_line(op, sig):
|
|
86
|
-
head = f"{'.'.join(snake(g) for g in listify(op.group))}.{op.name}"
|
|
87
|
-
if op.docs_url: head = f"[{head}]({op.docs_url})"
|
|
88
|
-
s = f"({', '.join(sig.parameters)})"
|
|
89
|
-
summ = _op_summary(op)
|
|
90
|
-
return f"{head}{s}: *{summ}*"
|
|
91
|
-
|
|
92
85
|
# %% ../nbs/05a_apisurface.ipynb #ed62e194
|
|
93
86
|
def mk_doc(op, sig, sparams):
|
|
94
87
|
"Render operation docstring with summary, docs URL, and parameter hints."
|
|
@@ -102,11 +95,19 @@ def mk_doc(op, sig, sparams):
|
|
|
102
95
|
for nm,p in sig.parameters.items():
|
|
103
96
|
orig = rsparams.get(nm, nm)
|
|
104
97
|
r = f"default: {p.default!r}" if p.default not in (Parameter.empty, UNSET) else "required" if orig in req else "optional"
|
|
105
|
-
ann = '' if p.annotation is Parameter.empty else p.annotation.
|
|
98
|
+
ann = '' if p.annotation is Parameter.empty else inspect.formatannotation(ann_parts(p.annotation)[0]).removeprefix('typing.')
|
|
106
99
|
desc = (op.param_docs or {}).get(orig, "")
|
|
107
100
|
lines.append(f"- {nm} ({ann}, {r}){': ' + desc if desc else ''}")
|
|
108
101
|
return "\n".join(lines)
|
|
109
102
|
|
|
103
|
+
# %% ../nbs/05a_apisurface.ipynb #08067b8a
|
|
104
|
+
def _op_line(op, sig):
|
|
105
|
+
head = f"{'.'.join(snake(g) for g in listify(op.group))}.{op.name}"
|
|
106
|
+
if op.docs_url: head = f"[{head}]({op.docs_url})"
|
|
107
|
+
s = f"({', '.join(sig.parameters)})"
|
|
108
|
+
summ = _op_summary(op)
|
|
109
|
+
return f"{head}{s}: *{summ}*"
|
|
110
|
+
|
|
110
111
|
# %% ../nbs/05a_apisurface.ipynb #e4d35409
|
|
111
112
|
class OpGroup:
|
|
112
113
|
"Namespace for grouped operations: each op is an attribute, and the repr lists them all"
|
|
@@ -123,10 +124,9 @@ class OpGroup:
|
|
|
123
124
|
def __dir__(self): return object.__dir__(self)
|
|
124
125
|
def __allow__(self): return self.ops + list(self.subgroups.values())
|
|
125
126
|
|
|
126
|
-
def _repr_markdown_(self): return self.__doc__
|
|
127
|
+
def _repr_markdown_(self): return self.__doc__ + '\n\nOverview only. Read `doc(group.operation)` for parameter details and `doc(group.subgroup)` to descend.'
|
|
127
128
|
__repr__ = _repr_markdown_
|
|
128
129
|
|
|
129
|
-
|
|
130
130
|
# %% ../nbs/05a_apisurface.ipynb #62b12f6b
|
|
131
131
|
def mk_groups(ops):
|
|
132
132
|
"Nested tree of `OpGroup`s from ops, following each op's `group` path."
|
|
@@ -152,6 +152,6 @@ def _group_docs(name, g, lvl=2):
|
|
|
152
152
|
return "\n\n".join(res)
|
|
153
153
|
|
|
154
154
|
def full_docs(groups):
|
|
155
|
-
"
|
|
155
|
+
"Markdown overview of every group and operation in a `mk_groups` tree."
|
|
156
156
|
return "\n\n".join(_group_docs(nm, g) for nm,g in sorted(groups.items()))
|
|
157
157
|
|
|
@@ -29,23 +29,27 @@ from tokenize import tokenize,COMMENT
|
|
|
29
29
|
from ast import parse,FunctionDef,AsyncFunctionDef,AnnAssign
|
|
30
30
|
from io import BytesIO
|
|
31
31
|
from textwrap import dedent
|
|
32
|
-
from
|
|
33
|
-
from
|
|
34
|
-
from dataclasses import dataclass, is_dataclass
|
|
32
|
+
from inspect import getsource,isfunction,ismethod,isclass,signature,Parameter
|
|
33
|
+
from dataclasses import is_dataclass
|
|
35
34
|
from .utils import *
|
|
36
|
-
from .meta import delegates
|
|
37
35
|
from . import docscrape
|
|
38
|
-
from textwrap import fill
|
|
39
36
|
from inspect import isclass,getdoc,signature
|
|
40
37
|
|
|
41
38
|
# %% ../nbs/04_docments.ipynb #53e21187
|
|
39
|
+
def _callable_instance(sym): return callable(sym) and not (inspect.isroutine(sym) or isclass(sym))
|
|
40
|
+
|
|
42
41
|
def docstring(sym):
|
|
43
|
-
"Get
|
|
42
|
+
"Get documentation from an object, its constructor, or its callable implementation"
|
|
44
43
|
if isinstance(sym, str): return sym
|
|
44
|
+
if isinstance(sym, partial): return docstring(sym.func)
|
|
45
|
+
if _callable_instance(sym):
|
|
46
|
+
own = inspect.getattr_static(sym, '__doc__', None)
|
|
47
|
+
if own is not getattr(type(sym), '__doc__', None): return getdoc(sym) or ''
|
|
48
|
+
return getdoc(sym.__call__) or getdoc(sym) or ''
|
|
45
49
|
res = getdoc(sym)
|
|
46
|
-
if not
|
|
47
|
-
if not res and
|
|
48
|
-
return res or
|
|
50
|
+
if not getattr(sym, '__doc__', None) and res == object.__init__.__doc__: res = None
|
|
51
|
+
if not res and isclass(sym) and sym.__init__ is not object.__init__: res = docstring(sym.__init__)
|
|
52
|
+
return res or ''
|
|
49
53
|
|
|
50
54
|
# %% ../nbs/04_docments.ipynb #1e0cf854
|
|
51
55
|
def parse_docstring(sym):
|
|
@@ -57,17 +61,45 @@ def isdataclass(s):
|
|
|
57
61
|
"Check if `s` is a dataclass but not a dataclass' instance"
|
|
58
62
|
return is_dataclass(s) and isclass(s)
|
|
59
63
|
|
|
60
|
-
# %% ../nbs/04_docments.ipynb #6549c4b2
|
|
61
64
|
def get_dataclass_source(s):
|
|
62
65
|
"Get source code for dataclass `s`"
|
|
63
66
|
return getsource(s) if not getattr(s, "__module__") == '__main__' else ""
|
|
64
67
|
|
|
65
|
-
# %% ../nbs/04_docments.ipynb #cac59989
|
|
66
68
|
def get_source(s):
|
|
67
|
-
"Get source
|
|
69
|
+
"Get source for a function, callable implementation, dataclass, or source string"
|
|
68
70
|
if isinstance(s,str): return s
|
|
71
|
+
s = inspect.unwrap(s)
|
|
72
|
+
if isinstance(s, partial): s = s.func
|
|
73
|
+
if _callable_instance(s): s = s.__call__
|
|
69
74
|
return getsource(s) if isfunction(s) or ismethod(s) else get_dataclass_source(s) if isdataclass(s) else None
|
|
70
75
|
|
|
76
|
+
# %% ../nbs/04_docments.ipynb #40e3f274
|
|
77
|
+
def _get_property_name(p):
|
|
78
|
+
"Get the name of property `p`"
|
|
79
|
+
if hasattr(p, 'fget'): return p.fget.func.__qualname__ if hasattr(p.fget, 'func') else p.fget.__qualname__
|
|
80
|
+
else: return next(iter(re.findall(r'\'(.*)\'', str(p)))).split('.')[-1]
|
|
81
|
+
|
|
82
|
+
def get_name(obj):
|
|
83
|
+
"Get the name of `obj`"
|
|
84
|
+
if isinstance(obj, partial):
|
|
85
|
+
nm = get_name(obj.func)
|
|
86
|
+
args = [repr(a) for a in obj.args] + [f'{k}={repr(v)}' for k,v in obj.keywords.items()]
|
|
87
|
+
return f"{nm}[partial: {', '.join(args)}]"
|
|
88
|
+
if hasattr(obj, '__name__'): return obj.__name__
|
|
89
|
+
elif getattr(obj, '_name', False): return obj._name
|
|
90
|
+
elif hasattr(obj,'__origin__'): return str(obj.__origin__).split('.')[-1]
|
|
91
|
+
elif type(obj)==property: return _get_property_name(obj)
|
|
92
|
+
elif callable(obj): return type(obj).__name__
|
|
93
|
+
else: return str(obj).split('.')[-1]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# %% ../nbs/04_docments.ipynb #5e273f22
|
|
97
|
+
def qual_name(obj):
|
|
98
|
+
"Get the qualified name of `obj`"
|
|
99
|
+
if hasattr(obj,'__qualname__'): return obj.__qualname__
|
|
100
|
+
if ismethod(obj): return f"{get_name(obj.__self__)}.{get_name(obj)}"
|
|
101
|
+
return get_name(obj)
|
|
102
|
+
|
|
71
103
|
# %% ../nbs/04_docments.ipynb #91c0d15f
|
|
72
104
|
def _parses(s):
|
|
73
105
|
"Parse source of function, method, or dataclass `s`"
|
|
@@ -87,13 +119,14 @@ def _clean_comment(s):
|
|
|
87
119
|
res = _clean_re.findall(s)
|
|
88
120
|
return res[0] if res else None
|
|
89
121
|
|
|
122
|
+
# %% ../nbs/04_docments.ipynb #448097bb
|
|
90
123
|
def _param_locs(s, returns=True, args_kwargs=False):
|
|
91
124
|
"`dict` of parameter line numbers to names"
|
|
92
125
|
body = _parses(s).body
|
|
93
126
|
if len(body)==1:
|
|
94
127
|
defn = body[0]
|
|
95
128
|
if isinstance(defn, (FunctionDef, AsyncFunctionDef)):
|
|
96
|
-
res = {arg.lineno:arg.arg for arg in defn.args.args}
|
|
129
|
+
res = {arg.lineno:arg.arg for arg in [*defn.args.posonlyargs, *defn.args.args]}
|
|
97
130
|
# Add *args if present
|
|
98
131
|
if defn.args.vararg: res[defn.args.vararg.lineno] = defn.args.vararg.arg
|
|
99
132
|
# Add keyword-only args
|
|
@@ -127,49 +160,6 @@ def _get_full(p, docs, eval_str=False):
|
|
|
127
160
|
elif eval_str: anno = None
|
|
128
161
|
return AttrDict(docment=docs.get(p.name), anno=anno, default=p.default, kind=p.kind)
|
|
129
162
|
|
|
130
|
-
# %% ../nbs/04_docments.ipynb #1b4d817c
|
|
131
|
-
def _merge_doc(dm, npdoc):
|
|
132
|
-
if not npdoc: return dm
|
|
133
|
-
if not isinstance(dm, dict): return dm or '\n'.join(npdoc.desc)
|
|
134
|
-
# if not dm.anno or dm.anno==empty: dm.anno = npdoc.type
|
|
135
|
-
if not dm.docment: dm.docment = '\n'.join(npdoc.desc)
|
|
136
|
-
return dm
|
|
137
|
-
|
|
138
|
-
def _merge_docs(dms, npdocs):
|
|
139
|
-
npparams = npdocs['Parameters']
|
|
140
|
-
params = {nm:_merge_doc(dm,npparams.get(nm,None)) for nm,dm in dms.items()}
|
|
141
|
-
if 'return' in dms: params['return'] = _merge_doc(dms['return'], npdocs['Returns'])
|
|
142
|
-
return params
|
|
143
|
-
|
|
144
|
-
# %% ../nbs/04_docments.ipynb #40e3f274
|
|
145
|
-
def _get_property_name(p):
|
|
146
|
-
"Get the name of property `p`"
|
|
147
|
-
if hasattr(p, 'fget'):
|
|
148
|
-
return p.fget.func.__qualname__ if hasattr(p.fget, 'func') else p.fget.__qualname__
|
|
149
|
-
else: return next(iter(re.findall(r'\'(.*)\'', str(p)))).split('.')[-1]
|
|
150
|
-
|
|
151
|
-
# %% ../nbs/04_docments.ipynb #da0465e3
|
|
152
|
-
def get_name(obj):
|
|
153
|
-
"Get the name of `obj`"
|
|
154
|
-
if isinstance(obj, partial):
|
|
155
|
-
nm = get_name(obj.func)
|
|
156
|
-
args = [repr(a) for a in obj.args] + [f'{k}={repr(v)}' for k,v in obj.keywords.items()]
|
|
157
|
-
return f"{nm}[partial: {', '.join(args)}]"
|
|
158
|
-
if hasattr(obj, '__name__'): return obj.__name__
|
|
159
|
-
elif getattr(obj, '_name', False): return obj._name
|
|
160
|
-
elif hasattr(obj,'__origin__'): return str(obj.__origin__).split('.')[-1]
|
|
161
|
-
elif type(obj)==property: return _get_property_name(obj)
|
|
162
|
-
elif callable(obj): return type(obj).__name__
|
|
163
|
-
else: return str(obj).split('.')[-1]
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
# %% ../nbs/04_docments.ipynb #5e273f22
|
|
167
|
-
def qual_name(obj):
|
|
168
|
-
"Get the qualified name of `obj`"
|
|
169
|
-
if hasattr(obj,'__qualname__'): return obj.__qualname__
|
|
170
|
-
if ismethod(obj): return f"{get_name(obj.__self__)}.{get_name(fn)}"
|
|
171
|
-
return get_name(obj)
|
|
172
|
-
|
|
173
163
|
# %% ../nbs/04_docments.ipynb #2e865627
|
|
174
164
|
def ann_parts(anno):
|
|
175
165
|
"The underlying type and metadata tuple of an `Annotated`, else `(anno, ())`"
|
|
@@ -178,9 +168,16 @@ def ann_parts(anno):
|
|
|
178
168
|
t = None if args[0] is type(None) else args[0]
|
|
179
169
|
return t, args[1:]
|
|
180
170
|
|
|
171
|
+
# %% ../nbs/04_docments.ipynb #1b4d817c
|
|
172
|
+
def _merge_docs(dms, npdocs):
|
|
173
|
+
for nm,dm in dms.items():
|
|
174
|
+
npdoc = npdocs['Returns'] if nm=='return' else npdocs['Parameters'].get(nm)
|
|
175
|
+
if npdoc and not dm.docment: dm.docment = '\n'.join(npdoc.desc)
|
|
176
|
+
return dms
|
|
177
|
+
|
|
181
178
|
# %% ../nbs/04_docments.ipynb #9b62ab20
|
|
182
179
|
def docments(s, full=False, eval_str=False, returns=True, args_kwargs=False):
|
|
183
|
-
"Get
|
|
180
|
+
"Get parameter and return documentation from comments, `Annotated` metadata, and NumPy docstrings"
|
|
184
181
|
if isclass(s) and not is_dataclass(s): s = s.__init__
|
|
185
182
|
try: sig = signature_ex(s, eval_str=eval_str)
|
|
186
183
|
except (ValueError, TypeError): return AttrDict()
|
|
@@ -192,18 +189,13 @@ def docments(s, full=False, eval_str=False, returns=True, args_kwargs=False):
|
|
|
192
189
|
for k,v in p.items():
|
|
193
190
|
if v not in docs: docs[v] = _get_comment(k, v, c, p)
|
|
194
191
|
s = getattr(s, '__delwrap__', None)
|
|
195
|
-
|
|
196
|
-
res = {k:_get_full(v, docs, eval_str=eval_str)
|
|
197
|
-
if returns:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
am = first(o for o in ann_parts(p.annotation)[1] if isinstance(o,str))
|
|
203
|
-
if am is None: continue
|
|
204
|
-
if full: res[k].docment = am
|
|
205
|
-
else: res[k] = am
|
|
206
|
-
return AttrDict(_merge_docs(res, nps))
|
|
192
|
+
|
|
193
|
+
res = {k:_get_full(v, docs, eval_str=eval_str) for k,v in sig.parameters.items()}
|
|
194
|
+
if returns: res['return'] = AttrDict(docment=docs.get('return'), anno=sig.return_annotation, default=empty)
|
|
195
|
+
for dm in res.values():
|
|
196
|
+
if dm.docment is None: dm.docment = first(o for o in ann_parts(dm.anno)[1] if isinstance(o,str))
|
|
197
|
+
res = _merge_docs(res, nps)
|
|
198
|
+
return AttrDict(res if full else {k:v.docment for k,v in res.items()})
|
|
207
199
|
|
|
208
200
|
# %% ../nbs/04_docments.ipynb #40cdbeb2
|
|
209
201
|
def sig_source(obj):
|
|
@@ -221,7 +213,7 @@ def _get_params(node):
|
|
|
221
213
|
if node.args.kwarg: params.append(f"**{node.args.kwarg.arg}")
|
|
222
214
|
return ", ".join(params)
|
|
223
215
|
|
|
224
|
-
# %% ../nbs/04_docments.ipynb #
|
|
216
|
+
# %% ../nbs/04_docments.ipynb #5dc1b730
|
|
225
217
|
class _DocstringExtractor(ast.NodeVisitor):
|
|
226
218
|
def __init__(self): self.docstrings,self.cls,self.cls_init = {},None,None
|
|
227
219
|
|
|
@@ -252,7 +244,7 @@ class _DocstringExtractor(ast.NodeVisitor):
|
|
|
252
244
|
if module_doc: self.docstrings['_module'] = (module_doc, "")
|
|
253
245
|
self.generic_visit(node)
|
|
254
246
|
|
|
255
|
-
# %% ../nbs/04_docments.ipynb #
|
|
247
|
+
# %% ../nbs/04_docments.ipynb #ac56083a
|
|
256
248
|
def extract_docstrings(code):
|
|
257
249
|
"Create a dict from function/class/method names to tuples of docstrings and param lists"
|
|
258
250
|
extractor = _DocstringExtractor()
|
|
@@ -260,21 +252,19 @@ def extract_docstrings(code):
|
|
|
260
252
|
return extractor.docstrings
|
|
261
253
|
|
|
262
254
|
# %% ../nbs/04_docments.ipynb #e4e1b815
|
|
263
|
-
def _non_empty_keys(d:dict): return L([k for k,v in d.items() if v != inspect._empty])
|
|
264
255
|
def _bold(s): return f'**{s}**' if s.strip() else s
|
|
265
256
|
|
|
257
|
+
def _maybe_nm(o):
|
|
258
|
+
if (o == inspect._empty): return ''
|
|
259
|
+
else: return o.__name__ if hasattr(o, '__name__') else str(o)
|
|
260
|
+
|
|
266
261
|
# %% ../nbs/04_docments.ipynb #ac070254
|
|
267
262
|
def _escape_markdown(s):
|
|
268
263
|
for c in '|^': s = re.sub(rf'\\?\{c}', rf'\{c}', s)
|
|
269
264
|
return s.replace('\n', '<br>')
|
|
270
265
|
|
|
271
|
-
# %% ../nbs/04_docments.ipynb #ced78f56
|
|
272
|
-
def _maybe_nm(o):
|
|
273
|
-
if (o == inspect._empty): return ''
|
|
274
|
-
else: return o.__name__ if hasattr(o, '__name__') else str(o)
|
|
275
|
-
|
|
276
266
|
# %% ../nbs/04_docments.ipynb #fe6d83f1
|
|
277
|
-
def _list2row(l:list): return '| '+' | '.join(
|
|
267
|
+
def _list2row(l:list): return '| '+' | '.join(_escape_markdown(_maybe_nm(o)) for o in l) + ' |'
|
|
278
268
|
|
|
279
269
|
# %% ../nbs/04_docments.ipynb #44ac2e4f
|
|
280
270
|
class _DocmentBase:
|
|
@@ -285,49 +275,42 @@ class _DocmentBase:
|
|
|
285
275
|
@property
|
|
286
276
|
def has_docment(self): return any(v.get('docment') for v in self.dm.values())
|
|
287
277
|
|
|
288
|
-
# %% ../nbs/04_docments.ipynb #
|
|
278
|
+
# %% ../nbs/04_docments.ipynb #f7c36bf3
|
|
289
279
|
class DocmentTbl(_DocmentBase):
|
|
290
|
-
_map =
|
|
280
|
+
_map = dict(anno='Type', default='Default', docment='Details')
|
|
291
281
|
|
|
292
282
|
def __init__(self, obj, verbose=True, returns=True):
|
|
293
283
|
"Compute the docment table string"
|
|
294
284
|
super().__init__(obj)
|
|
295
285
|
self.verbose = verbose
|
|
296
286
|
self.returns = False if isdataclass(obj) else returns
|
|
297
|
-
|
|
298
|
-
except (ValueError,TypeError): self.params=[]
|
|
287
|
+
self.params = L(k for k in self.dm if k!='return')
|
|
299
288
|
for d in self.dm.values(): d['docment'] = ifnone(d['docment'], inspect._empty)
|
|
300
289
|
|
|
301
290
|
@property
|
|
302
291
|
def _columns(self):
|
|
303
|
-
"
|
|
304
|
-
cols = set(flatten(L(self.dm.values()).filter().map(_non_empty_keys)))
|
|
292
|
+
"Fields with at least one non-empty value"
|
|
305
293
|
candidates = self._map if self.verbose else {'docment': 'Details'}
|
|
306
|
-
return {k:v for k,v in candidates.items() if k in
|
|
294
|
+
return {k:v for k,v in candidates.items() if any(d[k] != empty for d in self.dm.values())}
|
|
307
295
|
|
|
308
296
|
@property
|
|
309
|
-
def has_docment(self): return 'docment' in self._columns and self.
|
|
297
|
+
def has_docment(self): return 'docment' in self._columns and bool(self.params)
|
|
310
298
|
|
|
311
299
|
@property
|
|
312
|
-
def has_return(self): return self.returns and
|
|
313
|
-
|
|
314
|
-
def _row(self, nm, props): return [nm] + [props[c] for c in self._columns]
|
|
315
|
-
|
|
316
|
-
@property
|
|
317
|
-
def _row_list(self):
|
|
318
|
-
ordered_params = [(p, self.dm[p]) for p in self.params if p != 'self' and p in self.dm]
|
|
319
|
-
return L([self._row(nm, props) for nm,props in ordered_params])
|
|
320
|
-
|
|
321
|
-
@property
|
|
322
|
-
def _hdr_list(self): return [' '] + [_bold(l) for l in L(self._columns.values())]
|
|
300
|
+
def has_return(self): return self.returns and any(v != empty for v in self.dm.get('return', {}).values())
|
|
323
301
|
|
|
324
302
|
@property
|
|
325
303
|
def hdr_str(self):
|
|
326
|
-
|
|
327
|
-
return
|
|
304
|
+
hdr = [' '] + [_bold(v) for v in self._columns.values()]
|
|
305
|
+
return _list2row(hdr) + '\n' + _list2row(['---'] * len(hdr))
|
|
328
306
|
|
|
329
307
|
@property
|
|
330
|
-
def params_str(self):
|
|
308
|
+
def params_str(self):
|
|
309
|
+
cols,rows = self._columns,[]
|
|
310
|
+
for nm in self.params:
|
|
311
|
+
row = [nm] + [self.dm[nm][c] for c in cols]
|
|
312
|
+
rows.append(_list2row(row))
|
|
313
|
+
return '\n'.join(rows)
|
|
331
314
|
|
|
332
315
|
@property
|
|
333
316
|
def return_str(self): return _list2row(['**Returns**']+[_bold(_maybe_nm(self.dm['return'][c])) for c in self._columns])
|
|
@@ -342,6 +325,7 @@ class DocmentTbl(_DocmentBase):
|
|
|
342
325
|
__str__ = _repr_markdown_
|
|
343
326
|
__repr__ = basic_repr()
|
|
344
327
|
|
|
328
|
+
|
|
345
329
|
# %% ../nbs/04_docments.ipynb #5af61ab1
|
|
346
330
|
class DocmentList(_DocmentBase):
|
|
347
331
|
def _fmt(self, nm, p):
|
|
@@ -353,12 +337,6 @@ class DocmentList(_DocmentBase):
|
|
|
353
337
|
def _repr_markdown_(self): return '\n'.join(self._fmt(k,v) for k,v in self.dm.items())
|
|
354
338
|
__repr__=__str__=_repr_markdown_
|
|
355
339
|
|
|
356
|
-
# %% ../nbs/04_docments.ipynb #6355b569
|
|
357
|
-
def _clean_text_sig(obj):
|
|
358
|
-
if not (sig := getattr(obj, '__text_signature__', None)): return None
|
|
359
|
-
sig = re.sub(r'\$\w+,?\s*', '', sig)
|
|
360
|
-
return get_name(obj) + sig.replace('<unrepresentable>', '...')
|
|
361
|
-
|
|
362
340
|
# %% ../nbs/04_docments.ipynb #cfcc46bf
|
|
363
341
|
def _fmt_sig(name, params, ret_str, maxline, prefix='def'):
|
|
364
342
|
"Format function signature with params and docment comments"
|
|
@@ -369,7 +347,9 @@ def _fmt_sig(name, params, ret_str, maxline, prefix='def'):
|
|
|
369
347
|
lines.append(', '.join(curr) + ',')
|
|
370
348
|
curr = []
|
|
371
349
|
curr.append(fmt)
|
|
372
|
-
if doc:
|
|
350
|
+
if doc:
|
|
351
|
+
lines.append(', '.join(curr) + ',' + comment)
|
|
352
|
+
curr = []
|
|
373
353
|
if curr: lines.append(', '.join(curr))
|
|
374
354
|
pstr = '\n '.join(lines)
|
|
375
355
|
if not pstr: return f"{prefix} {name}({ret_str}"
|
|
@@ -381,6 +361,11 @@ def _fmt_default(o):
|
|
|
381
361
|
return o.__name__ if hasattr(o, '__name__') else repr(o)
|
|
382
362
|
|
|
383
363
|
# %% ../nbs/04_docments.ipynb #7f5e5282
|
|
364
|
+
def _type_str(anno):
|
|
365
|
+
anno = ann_parts(anno)[0]
|
|
366
|
+
return inspect.formatannotation(anno).removeprefix('typing.').strip("'")
|
|
367
|
+
|
|
368
|
+
# %% ../nbs/04_docments.ipynb #4c78c84f
|
|
384
369
|
class DocmentText(_DocmentBase):
|
|
385
370
|
def __init__(self, obj, maxline=110, docstring=True):
|
|
386
371
|
super().__init__(obj)
|
|
@@ -389,46 +374,79 @@ class DocmentText(_DocmentBase):
|
|
|
389
374
|
def _fmt_param(self, nm, p):
|
|
390
375
|
anno,default,kind = p.get('anno',empty), p.get('default',empty), p.get('kind')
|
|
391
376
|
pre = '*' if kind==Parameter.VAR_POSITIONAL else '**' if kind==Parameter.VAR_KEYWORD else ''
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
ret = self.dm.get('return', {})
|
|
397
|
-
anno = f"->{_maybe_nm(ret.get('anno',empty))}" if ret.get('anno',empty) != empty else ''
|
|
398
|
-
doc = f" # {ret['docment']}" if ret.get('docment') else ''
|
|
399
|
-
return f"){anno}:{doc}"
|
|
400
|
-
|
|
401
|
-
@property
|
|
402
|
-
def params(self): return [(self._fmt_param(k,v), v.get('docment','')) for k,v in self.dm.items() if k != 'return']
|
|
403
|
-
|
|
404
|
-
def __str__(self):
|
|
405
|
-
o = self.obj
|
|
406
|
-
if not callable(o):
|
|
407
|
-
doc = docstring(o)
|
|
408
|
-
docstr = f'\n "{doc}"' if self.docstring and doc else ''
|
|
409
|
-
return f"{type(o).__name__} instance: {truncstr(repr(o), 80)}{docstr}"
|
|
410
|
-
is_inst = callable(o) and not (isfunction(o) or isclass(o) or inspect.isbuiltin(o) or ismethod(o))
|
|
411
|
-
prefix = 'async def' if inspect.iscoroutinefunction(o) else 'def'
|
|
412
|
-
nm = get_name(o) if hasattr(o, '__name__') else f'{type(o).__name__}.__call__' if is_inst else get_name(o)
|
|
413
|
-
if (sig := _clean_text_sig(o)) and not self.params: sig_str = f"{prefix} {sig}"
|
|
414
|
-
else: sig_str = _fmt_sig(nm, self.params, self._ret_str, self.maxline, prefix=prefix)
|
|
415
|
-
doc = getattr(o.__call__, '__doc__', None) if is_inst else o.__doc__
|
|
416
|
-
docstr = f' "{doc}"' if self.docstring and doc else ''
|
|
417
|
-
return f"{sig_str}\n{docstr}" if docstr else sig_str
|
|
418
|
-
|
|
419
|
-
__repr__ = __str__
|
|
377
|
+
anno = f':{_type_str(anno)}' if anno != empty else ''
|
|
378
|
+
default = f'={_fmt_default(default)}' if default != empty else ''
|
|
379
|
+
return pre + nm + anno + default
|
|
380
|
+
|
|
420
381
|
def _repr_markdown_(self): return f"```python\n{self}\n```"
|
|
421
382
|
|
|
383
|
+
# %% ../nbs/04_docments.ipynb #ae2624ea
|
|
384
|
+
@patch(as_prop=True)
|
|
385
|
+
def params(self:DocmentText):
|
|
386
|
+
res,prev,star = [],None,False
|
|
387
|
+
for k,v in self.dm.items():
|
|
388
|
+
if k=='return': continue
|
|
389
|
+
kind = v.get('kind')
|
|
390
|
+
if prev==Parameter.POSITIONAL_ONLY and kind!=prev: res.append(('/', None))
|
|
391
|
+
if kind==Parameter.KEYWORD_ONLY and not star:
|
|
392
|
+
res.append(('*', None))
|
|
393
|
+
star = True
|
|
394
|
+
if kind==Parameter.VAR_POSITIONAL: star = True
|
|
395
|
+
res.append((self._fmt_param(k,v), v.get('docment','')))
|
|
396
|
+
prev = kind
|
|
397
|
+
if prev==Parameter.POSITIONAL_ONLY: res.append(('/', None))
|
|
398
|
+
return res
|
|
399
|
+
|
|
400
|
+
# %% ../nbs/04_docments.ipynb #35564516
|
|
401
|
+
def _clean_text_sig(obj):
|
|
402
|
+
if not (sig := getattr(obj, '__text_signature__', None)): return None
|
|
403
|
+
sig = re.sub(r'\$\w+,?\s*', '', sig)
|
|
404
|
+
return get_name(obj) + sig.replace('<unrepresentable>', '...')
|
|
405
|
+
|
|
406
|
+
@patch(as_prop=True)
|
|
407
|
+
def _ret_str(self:DocmentText):
|
|
408
|
+
ret = self.dm.get('return', {})
|
|
409
|
+
anno = f"->{_type_str(ret['anno'])}" if ret.get('anno',empty) != empty else ''
|
|
410
|
+
doc = f" # {ret['docment']}" if ret.get('docment') else ''
|
|
411
|
+
return f"){anno}:{doc}"
|
|
412
|
+
|
|
413
|
+
# %% ../nbs/04_docments.ipynb #2309edc7
|
|
414
|
+
@patch
|
|
415
|
+
def __str__(self:DocmentText):
|
|
416
|
+
o = self.obj
|
|
417
|
+
doc = docstring(o)
|
|
418
|
+
docstr = f'\n "{doc}"' if self.docstring and doc else ''
|
|
419
|
+
if not callable(o): return f"{type(o).__name__} instance: {truncstr(repr(o), 80)}{docstr}"
|
|
420
|
+
is_inst = _callable_instance(o)
|
|
421
|
+
async_ = inspect.iscoroutinefunction(o) or (is_inst and inspect.iscoroutinefunction(o.__call__))
|
|
422
|
+
prefix = 'async def' if async_ else 'def'
|
|
423
|
+
nm = f'{type(o).__name__}.__call__' if is_inst and not hasattr(o, '__name__') else get_name(o)
|
|
424
|
+
if (sig := _clean_text_sig(o)) and not self.params: sig_str = f"{prefix} {sig}"
|
|
425
|
+
else: sig_str = _fmt_sig(nm, self.params, self._ret_str, self.maxline, prefix=prefix)
|
|
426
|
+
return sig_str + docstr
|
|
427
|
+
|
|
428
|
+
DocmentText.__repr__ = DocmentText.__str__
|
|
429
|
+
|
|
422
430
|
# %% ../nbs/04_docments.ipynb #d44a7fe3
|
|
423
431
|
def sig2str(func, maxline=110):
|
|
424
432
|
"Generate function signature with docments as comments"
|
|
425
433
|
return DocmentText(func, maxline=maxline, docstring=False)
|
|
426
434
|
|
|
427
435
|
# %% ../nbs/04_docments.ipynb #07287425
|
|
428
|
-
def _docstring(sym):
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
436
|
+
def _docstring(sym, dm):
|
|
437
|
+
doc = docstring(sym)
|
|
438
|
+
parts = re.split(r'(?m)^([\w ][\w ]*)\n[-=]{3,}[ \t]*\n', doc)
|
|
439
|
+
res = [parts[0]]
|
|
440
|
+
for title,body in zip(parts[1::2], parts[2::2]):
|
|
441
|
+
headers = [s for s in body.splitlines() if s and not s[0].isspace()]
|
|
442
|
+
if title=='Parameters' and headers:
|
|
443
|
+
params = [s.partition(':') for s in headers]
|
|
444
|
+
if all(sep and _type_str(dm.get(n.strip(), {}).get('anno', empty))==t.strip() for n,sep,t in params): continue
|
|
445
|
+
if title=='Returns' and len(headers)==1 and headers[0].strip()==_type_str(dm.get('return', {}).get('anno', empty)): continue
|
|
446
|
+
res.append(f'{title}\n{"-"*len(title)}\n{body}')
|
|
447
|
+
return '\n\n'.join(s.strip() for s in res if s.strip())
|
|
448
|
+
|
|
449
|
+
# %% ../nbs/04_docments.ipynb #21ebcfcb
|
|
432
450
|
def _unwrap_sym(sym):
|
|
433
451
|
if not hasattr(sym, '__signature__'): sym = getattr(sym, '__wrapped__', sym)
|
|
434
452
|
return (sym.fget or sym.fset or sym) if isinstance(sym, property) else sym
|
|
@@ -436,7 +454,9 @@ def _unwrap_sym(sym):
|
|
|
436
454
|
def can_render(sym):
|
|
437
455
|
"Check if `sym` has a renderable signature"
|
|
438
456
|
sym = _unwrap_sym(sym)
|
|
439
|
-
try:
|
|
457
|
+
try:
|
|
458
|
+
signature(sym, eval_str=True)
|
|
459
|
+
return True
|
|
440
460
|
except (ValueError, TypeError): return False
|
|
441
461
|
|
|
442
462
|
# %% ../nbs/04_docments.ipynb #9de89cb6
|
|
@@ -453,34 +473,21 @@ class ShowDocRenderer:
|
|
|
453
473
|
self.isfunc = inspect.isfunction(sym)
|
|
454
474
|
try: self.sig = signature(sym, eval_str=True)
|
|
455
475
|
except (ValueError,TypeError): self.sig = None
|
|
456
|
-
self.docs = _docstring(sym)
|
|
457
476
|
try: self.dm = DocmentText(sym, maxline=maxline, docstring=False)
|
|
458
477
|
except Exception as e:
|
|
459
478
|
warnings.warn(f'docments unavailable for {self.nm}: {type(e).__name__}: {e}')
|
|
460
479
|
self.dm = f'{self.nm}{self.sig or "(...)"}'
|
|
480
|
+
self.docs = _docstring(sym, self.dm.dm if isinstance(self.dm, DocmentText) else {})
|
|
461
481
|
self.fn = _fullname(sym)
|
|
462
482
|
|
|
463
483
|
__repr__ = basic_repr()
|
|
464
484
|
|
|
465
|
-
# %% ../nbs/04_docments.ipynb #e569d885
|
|
466
|
-
def _f_name(o): return f'<function {o.__name__}>' if isinstance(o, FunctionType) else None
|
|
467
|
-
def _fmt_anno(o): return inspect.formatannotation(o).strip("'").replace(' ','')
|
|
468
|
-
|
|
469
|
-
def _show_param(param):
|
|
470
|
-
"Like `Parameter.__str__` except removes: quotes in annos, spaces, ids in reprs"
|
|
471
|
-
kind,res,anno,default = param.kind,param._name,param._annotation,param._default
|
|
472
|
-
kind = '*' if kind==inspect._VAR_POSITIONAL else '**' if kind==inspect._VAR_KEYWORD else ''
|
|
473
|
-
res = kind+res
|
|
474
|
-
if anno is not inspect._empty: res += f':{_f_name(anno) or _fmt_anno(anno)}'
|
|
475
|
-
if default is not inspect._empty: res += f'={_f_name(default) or repr(default)}'
|
|
476
|
-
return res
|
|
477
|
-
|
|
478
485
|
# %% ../nbs/04_docments.ipynb #59797eb7
|
|
479
486
|
def _ital_first(s:str):
|
|
480
487
|
"Surround first line with * for markdown italics, preserving leading spaces"
|
|
481
488
|
return re.sub(r'^(\s*)(.+)', r'\1*\2*', s, count=1)
|
|
482
489
|
|
|
483
|
-
# %% ../nbs/04_docments.ipynb #
|
|
490
|
+
# %% ../nbs/04_docments.ipynb #c2aa24dd
|
|
484
491
|
class MarkdownRenderer(ShowDocRenderer):
|
|
485
492
|
"Markdown renderer for `show_doc`"
|
|
486
493
|
def _repr_markdown_(self):
|
|
@@ -17,9 +17,9 @@ __all__ = ['langs', 'cell_insert_line', 'cell_str_replace', 'cell_strs_replace',
|
|
|
17
17
|
'first_code_ln', 'dir_tag', 'nb2dict', 'nb2str', 'write_nb', 'find_id', 'cell_edit', 'view_cell',
|
|
18
18
|
'diff_cells', 'validate_cell', 'validate_nb', 'repair_cell', 'repair_nb', 'preferred_out', 'join_out',
|
|
19
19
|
'mk_stream', 'mk_result', 'mk_display', 'mk_error', 'concat_streams', 'preferred_msg_out', 'render_output',
|
|
20
|
-
'render_outputs', 'render_text', 'item2xml', 'cell2xml', 'cells2xml', 'Notebook', '
|
|
21
|
-
'summary_nb', 'Found', 'FoundCells', 'find_cells', 'deep_merge', 'update_cell',
|
|
22
|
-
'does_cell_eval', 'select_cells', 'run_cell', 'msg2out', 'msgs2outs']
|
|
20
|
+
'render_outputs', 'render_text', 'item2xml', 'cell2xml', 'cells2xml', 'Notebook', 'prev_line', 'CellRow',
|
|
21
|
+
'CellRows', 'summary_nb', 'Found', 'FoundCells', 'find_cells', 'deep_merge', 'update_cell',
|
|
22
|
+
'fm_default_eval', 'does_cell_eval', 'select_cells', 'run_cell', 'msg2out', 'msgs2outs']
|
|
23
23
|
|
|
24
24
|
# %% ../nbs/13_nbio.ipynb #954ca1aa
|
|
25
25
|
from .basics import *
|
|
@@ -447,7 +447,7 @@ def validate_cell(cell, idx=None):
|
|
|
447
447
|
ct = cell.get('cell_type')
|
|
448
448
|
if ct not in ('code','markdown','raw'): raise ValueError(f"{where}: unknown cell_type {ct!r}")
|
|
449
449
|
if not _is_text(cell.get('source', '')): raise ValueError(f"{where}: source must be str or list of str")
|
|
450
|
-
if not isinstance(cell.get('metadata'
|
|
450
|
+
if not isinstance(cell.get('metadata'), dict): raise ValueError(f"{where}: metadata must be a dict")
|
|
451
451
|
if ct=='code':
|
|
452
452
|
if not isinstance(cell.get('outputs'), list): raise ValueError(f"{where}: code cell requires an outputs list")
|
|
453
453
|
if 'execution_count' not in cell: raise ValueError(f"{where}: code cell requires execution_count")
|
|
@@ -472,9 +472,9 @@ def repair_cell(cell, idx=None):
|
|
|
472
472
|
if not _is_text(src):
|
|
473
473
|
cell['source'] = ''.join(map(str, listify(src)))
|
|
474
474
|
res.append(f"{where}: coerced source to text")
|
|
475
|
-
if not isinstance(cell.get('metadata'
|
|
475
|
+
if not isinstance(cell.get('metadata'), dict):
|
|
476
476
|
cell['metadata'] = {}
|
|
477
|
-
res.append(f"{where}:
|
|
477
|
+
res.append(f"{where}: set metadata")
|
|
478
478
|
if cell.get('cell_type')=='code':
|
|
479
479
|
if not isinstance(cell.get('outputs'), list):
|
|
480
480
|
cell['outputs'] = []
|
|
@@ -759,6 +759,14 @@ def view_cell(self:Notebook, id, nums=True, incl_out=False, trunc_out=True):
|
|
|
759
759
|
# %% ../nbs/13_nbio.ipynb #4bf3dc2e
|
|
760
760
|
MAXLEN = 180 # Most characters shown per displayed line
|
|
761
761
|
|
|
762
|
+
# %% ../nbs/13_nbio.ipynb #80541be8
|
|
763
|
+
def prev_line(txt, maxlen=MAXLEN, pre='', sep=''):
|
|
764
|
+
"One-line preview `pre`+`sep`+`txt` capped at `maxlen`, blank-line runs shown as `¶`; a cut line carries `txt`'s size before `sep` and ends with the count of characters missing"
|
|
765
|
+
n = len(txt)
|
|
766
|
+
txt = re.sub(r'\n(?:\s*\n)*', '¶', txt)
|
|
767
|
+
if len(pre)+len(sep)+len(txt)<=maxlen: return pre+sep+txt
|
|
768
|
+
return truncstr(f'{pre}[{humanize(n)}]{sep}{txt}', maxlen, suf=lambda n: f'…[{humanize(n)}]')
|
|
769
|
+
|
|
762
770
|
# %% ../nbs/13_nbio.ipynb #804670bc
|
|
763
771
|
class CellRow:
|
|
764
772
|
"Snapshot of one cell, shown as `id:t[directives]:source` (t: c=code m=markdown r=raw); a context row from a find shows `-` in place of its final `:`"
|
|
@@ -766,10 +774,8 @@ class CellRow:
|
|
|
766
774
|
self.id,self.cell_type,self.source,self.maxlen,self.kind = c.id,c.cell_type,c.source,maxlen,kind
|
|
767
775
|
self.meta = copy.deepcopy(dict(c.get('metadata',{})))
|
|
768
776
|
def __repr__(self):
|
|
769
|
-
src = re.sub(r'\n(?:\s*\n)*', '¶', self.source)
|
|
770
|
-
if len(src)>self.maxlen: src = src[:self.maxlen]+'…'
|
|
771
777
|
sep = ':' if self.kind=='match' else '-'
|
|
772
|
-
return f"{self.id}:{self.cell_type[0]}{dir_tag(self.meta)}
|
|
778
|
+
return prev_line(self.source, self.maxlen, f"{self.id}:{self.cell_type[0]}{dir_tag(self.meta)}", sep)
|
|
773
779
|
|
|
774
780
|
class CellRows(list):
|
|
775
781
|
def __repr__(self): return '\n'.join(repr(o) for o in self)
|
|
@@ -296,7 +296,7 @@ def untar_dir(fname, dest, rename=False, overwrite=False, uid=-1, gid=-1):
|
|
|
296
296
|
if uid>-1 or gid>-1:
|
|
297
297
|
os.chown(dest, uid, gid)
|
|
298
298
|
if dest.is_dir():
|
|
299
|
-
for p in dest.rglob('*'): os.chown(p, uid, gid)
|
|
299
|
+
for p in dest.rglob('*'): os.chown(p, uid, gid, follow_symlinks=False)
|
|
300
300
|
return dest
|
|
301
301
|
|
|
302
302
|
# %% ../nbs/03_xtras.ipynb #ab91ee87
|
|
@@ -978,10 +978,15 @@ def partial_format(s:str, **kwargs):
|
|
|
978
978
|
return res,list(fmt.missing),fmt.xtra
|
|
979
979
|
|
|
980
980
|
# %% ../nbs/03_xtras.ipynb #becf7a2d
|
|
981
|
-
def truncstr(s:str, maxlen:int, suf
|
|
982
|
-
"Truncate `s` to length `maxlen`, adding suffix `suf` if truncated"
|
|
981
|
+
def truncstr(s:str, maxlen:int, suf='…', space='', sizevar:str=None)->str:
|
|
982
|
+
"Truncate `s` to length `maxlen`, adding suffix `suf` if truncated; a callable `suf` gets the number of characters cut"
|
|
983
|
+
if len(s)+len(space)<=maxlen: return s+space
|
|
983
984
|
if sizevar: suf = suf.format_map({sizevar: len(s)})
|
|
984
|
-
|
|
985
|
+
if callable(suf):
|
|
986
|
+
f,n = suf,0
|
|
987
|
+
for _ in range(3): n = len(s)-maxlen+len(f(n)) # the count's digits change the suffix length: iterate to a fixed point
|
|
988
|
+
suf = f(n)
|
|
989
|
+
return s[:maxlen-len(suf)]+suf
|
|
985
990
|
|
|
986
991
|
# %% ../nbs/03_xtras.ipynb #a67b3fca
|
|
987
992
|
def trunc_ctr(s:str, mx:int=1000)->str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastcore
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.23
|
|
4
4
|
Summary: Python supercharged for fastai development
|
|
5
5
|
Author-email: Jeremy Howard and Sylvain Gugger <infos@fast.ai>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -54,7 +54,7 @@ Here are some tips on using fastcore:
|
|
|
54
54
|
- **Advanced file operations**: Use the extended `Path` class, which adds methods like `ls()`, `read_json()`, and others to [`pathlib.Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path).
|
|
55
55
|
- **Flexible data structures**: Convert between dictionaries and attribute-access objects using [`dict2obj`](https://fastcore.fast.ai/xtras.html#dict2obj) and [`obj2dict`](https://fastcore.fast.ai/xtras.html#obj2dict) for more intuitive data handling.
|
|
56
56
|
- **Functional programming paradigms**: Use tools like [`compose`](https://fastcore.fast.ai/basics.html#compose), [`maps`](https://fastcore.fast.ai/basics.html#maps), and [`filter_ex`](https://fastcore.fast.ai/basics.html#filter_ex) to write more functional-style Python code.
|
|
57
|
-
- **Documentation**:
|
|
57
|
+
- **Documentation**: Document parameters and return values with [`docments`](https://fastcore.fast.ai/docments.html#docments), using source comments or `Annotated` metadata for generated APIs. [`MarkdownRenderer`](https://fastcore.fast.ai/docments.html#markdownrenderer) shows the effective signature and retains usage sections such as Notes, Raises, and Examples.
|
|
58
58
|
- **Time-aware caching**: Apply the [`timed_cache`](https://fastcore.fast.ai/xtras.html#timed_cache) decorator to add time-based expiration to the standard `lru_cache` functionality.
|
|
59
59
|
- **Simplified CLI creation**: Use `fastcore.script` to easily transform Python functions into command-line interfaces.
|
|
60
60
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|