cs-py-doc 20250426__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.
@@ -0,0 +1,101 @@
1
+ Metadata-Version: 2.4
2
+ Name: cs-py-doc
3
+ Version: 20250426
4
+ Summary: Create documentation from python modules and other objects.
5
+ Keywords: python2,python3
6
+ Author-email: Cameron Simpson <cs@cskk.id.au>
7
+ Description-Content-Type: text/markdown
8
+ Classifier: Programming Language :: Python
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
15
+ Requires-Dist: cs.lex>=20250414
16
+ Requires-Dist: cs.logutils>=20250323
17
+ Requires-Dist: cs.pfx>=20250308
18
+ Requires-Dist: cs.py.modules>=20241122
19
+ Project-URL: MonoRepo Commits, https://bitbucket.org/cameron_simpson/css/commits/branch/main
20
+ Project-URL: Monorepo Git Mirror, https://github.com/cameron-simpson/css
21
+ Project-URL: Monorepo Hg/Mercurial Mirror, https://hg.sr.ht/~cameron-simpson/css
22
+ Project-URL: Source, https://github.com/cameron-simpson/css/blob/main/lib/python/cs/py/doc.py
23
+
24
+ Create documentation from python modules and other objects.
25
+
26
+ *Latest release 20250426*:
27
+ * module_doc: new doc_item inner function to format an item, now using a list instead of a heading - more compact and readable.
28
+ * module_doc: restore mangled command usage.
29
+ * module_doc: provide a short summary of every module top level name before the full docs.
30
+
31
+ Short summary:
32
+ * `is_dunder`: Test whether a name is a dunder name (`__`*foo*`__`).
33
+ * `module_doc`: Fetch the docstrings from a module and assemble a MarkDown document.
34
+ * `obj_docstring`: Return a docstring for `obj` which has been passed through `stripped_dedent`.
35
+
36
+ Module contents:
37
+ - <a name="is_dunder"></a>`is_dunder(name)`: Test whether a name is a dunder name (`__`*foo*`__`).
38
+ - <a name="module_doc"></a>`module_doc(module, *, sort_key=<function <lambda> at 0x10a7ec220>, filter_key=<function <lambda> at 0x10a7ec720>, method_names=None)`: Fetch the docstrings from a module and assemble a MarkDown document.
39
+
40
+ Parameters:
41
+ * `module`: the module or module name to inspect
42
+ * `sort_key`: optional key for sorting names in the documentation;
43
+ default: `name`
44
+ * filter_key`: optional test for a key used to select or reject keys
45
+ to appear in the documentation
46
+ * `method_names`: optional list of method names to document;
47
+ the default is to document `__init__`, then CONSTANTS, the
48
+ dunders, then other public names
49
+ - <a name="obj_docstring"></a>`obj_docstring(obj)`: Return a docstring for `obj` which has been passed through `stripped_dedent`.
50
+
51
+ This function uses `obj.__doc__` if it is not `None`,
52
+ otherwise `getcomments(obj)` if that is not `None`,
53
+ otherwise `''`.
54
+ The chosen string is passed through `stripped_dedent` before return.
55
+
56
+ # Release Log
57
+
58
+
59
+
60
+ *Release 20250426*:
61
+ * module_doc: new doc_item inner function to format an item, now using a list instead of a heading - more compact and readable.
62
+ * module_doc: restore mangled command usage.
63
+ * module_doc: provide a short summary of every module top level name before the full docs.
64
+
65
+ *Release 20241007*:
66
+ * module_doc: for subclasses of cs.fsm.FSM embed an SVG state transition diagram.
67
+ * module_doc: put HTML named anchors on the headings.
68
+ * module_doc: use BaseCommand.extract_usage() with BaseCOmmand subclasses.
69
+
70
+ *Release 20240709*:
71
+ module_doc: do not insert a BaseCommand usage into the docs, the BaseCommand.__init_subclass__ will be doing that for us.
72
+
73
+ *Release 20240630.1*:
74
+ module_doc: build the usage message from an instance of the baseCommand, needed since the last cs.cmdutils release.
75
+
76
+ *Release 20240630*:
77
+ module_doc: insert the class usage message for subclasses of BaseCommand.
78
+
79
+ *Release 20240422*:
80
+ module_doc: only list things in __all__ if provided.
81
+
82
+ *Release 20240412*:
83
+ module_doc: classes: MRO: suppress classes which are not immediate superclasses.
84
+
85
+ *Release 20220311*:
86
+ module_doc: class members no longer rendered as headings, too verbose.
87
+
88
+ *Release 20210306*:
89
+ Drop noise leaked into output.
90
+
91
+ *Release 20210123*:
92
+ * module_doc: include properties/descriptors.
93
+ * DISTINFO: this is not Python 2 compatible, drop tag.
94
+
95
+ *Release 20200718*:
96
+ * New is_dunder(name) function to test whether name is a dunder name.
97
+ * module_doc: new method_names parameter to report only specific attributes from a class - default is all public names and most dunder methods - things without docs are not reported.
98
+ * Assorted small changes.
99
+
100
+ *Release 20200521*:
101
+ Initial PyPI release.
@@ -0,0 +1,126 @@
1
+ [project]
2
+ name = "cs-py-doc"
3
+ description = "Create documentation from python modules and other objects."
4
+ authors = [
5
+ { name = "Cameron Simpson", email = "cs@cskk.id.au" },
6
+ ]
7
+ keywords = [
8
+ "python2",
9
+ "python3",
10
+ ]
11
+ dependencies = [
12
+ "cs.lex>=20250414",
13
+ "cs.logutils>=20250323",
14
+ "cs.pfx>=20250308",
15
+ "cs.py.modules>=20241122",
16
+ ]
17
+ classifiers = [
18
+ "Programming Language :: Python",
19
+ "Programming Language :: Python :: 3",
20
+ "Development Status :: 4 - Beta",
21
+ "Intended Audience :: Developers",
22
+ "Operating System :: OS Independent",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
25
+ ]
26
+ version = "20250426"
27
+
28
+ [project.license]
29
+ text = "GNU General Public License v3 or later (GPLv3+)"
30
+
31
+ [project.urls]
32
+ "Monorepo Hg/Mercurial Mirror" = "https://hg.sr.ht/~cameron-simpson/css"
33
+ "Monorepo Git Mirror" = "https://github.com/cameron-simpson/css"
34
+ "MonoRepo Commits" = "https://bitbucket.org/cameron_simpson/css/commits/branch/main"
35
+ Source = "https://github.com/cameron-simpson/css/blob/main/lib/python/cs/py/doc.py"
36
+
37
+ [project.readme]
38
+ text = """
39
+ Create documentation from python modules and other objects.
40
+
41
+ *Latest release 20250426*:
42
+ * module_doc: new doc_item inner function to format an item, now using a list instead of a heading - more compact and readable.
43
+ * module_doc: restore mangled command usage.
44
+ * module_doc: provide a short summary of every module top level name before the full docs.
45
+
46
+ Short summary:
47
+ * `is_dunder`: Test whether a name is a dunder name (`__`*foo*`__`).
48
+ * `module_doc`: Fetch the docstrings from a module and assemble a MarkDown document.
49
+ * `obj_docstring`: Return a docstring for `obj` which has been passed through `stripped_dedent`.
50
+
51
+ Module contents:
52
+ - <a name=\"is_dunder\"></a>`is_dunder(name)`: Test whether a name is a dunder name (`__`*foo*`__`).
53
+ - <a name=\"module_doc\"></a>`module_doc(module, *, sort_key=<function <lambda> at 0x10a7ec220>, filter_key=<function <lambda> at 0x10a7ec720>, method_names=None)`: Fetch the docstrings from a module and assemble a MarkDown document.
54
+
55
+ Parameters:
56
+ * `module`: the module or module name to inspect
57
+ * `sort_key`: optional key for sorting names in the documentation;
58
+ default: `name`
59
+ * filter_key`: optional test for a key used to select or reject keys
60
+ to appear in the documentation
61
+ * `method_names`: optional list of method names to document;
62
+ the default is to document `__init__`, then CONSTANTS, the
63
+ dunders, then other public names
64
+ - <a name=\"obj_docstring\"></a>`obj_docstring(obj)`: Return a docstring for `obj` which has been passed through `stripped_dedent`.
65
+
66
+ This function uses `obj.__doc__` if it is not `None`,
67
+ otherwise `getcomments(obj)` if that is not `None`,
68
+ otherwise `''`.
69
+ The chosen string is passed through `stripped_dedent` before return.
70
+
71
+ # Release Log
72
+
73
+
74
+
75
+ *Release 20250426*:
76
+ * module_doc: new doc_item inner function to format an item, now using a list instead of a heading - more compact and readable.
77
+ * module_doc: restore mangled command usage.
78
+ * module_doc: provide a short summary of every module top level name before the full docs.
79
+
80
+ *Release 20241007*:
81
+ * module_doc: for subclasses of cs.fsm.FSM embed an SVG state transition diagram.
82
+ * module_doc: put HTML named anchors on the headings.
83
+ * module_doc: use BaseCommand.extract_usage() with BaseCOmmand subclasses.
84
+
85
+ *Release 20240709*:
86
+ module_doc: do not insert a BaseCommand usage into the docs, the BaseCommand.__init_subclass__ will be doing that for us.
87
+
88
+ *Release 20240630.1*:
89
+ module_doc: build the usage message from an instance of the baseCommand, needed since the last cs.cmdutils release.
90
+
91
+ *Release 20240630*:
92
+ module_doc: insert the class usage message for subclasses of BaseCommand.
93
+
94
+ *Release 20240422*:
95
+ module_doc: only list things in __all__ if provided.
96
+
97
+ *Release 20240412*:
98
+ module_doc: classes: MRO: suppress classes which are not immediate superclasses.
99
+
100
+ *Release 20220311*:
101
+ module_doc: class members no longer rendered as headings, too verbose.
102
+
103
+ *Release 20210306*:
104
+ Drop noise leaked into output.
105
+
106
+ *Release 20210123*:
107
+ * module_doc: include properties/descriptors.
108
+ * DISTINFO: this is not Python 2 compatible, drop tag.
109
+
110
+ *Release 20200718*:
111
+ * New is_dunder(name) function to test whether name is a dunder name.
112
+ * module_doc: new method_names parameter to report only specific attributes from a class - default is all public names and most dunder methods - things without docs are not reported.
113
+ * Assorted small changes.
114
+
115
+ *Release 20200521*:
116
+ Initial PyPI release."""
117
+ content-type = "text/markdown"
118
+
119
+ [build-system]
120
+ build-backend = "flit_core.buildapi"
121
+ requires = [
122
+ "flit_core >=3.2,<4",
123
+ ]
124
+
125
+ [tool.flit.module]
126
+ name = "cs.py.doc"
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env python3
2
+
3
+ ''' Create documentation from python modules and other objects.
4
+ '''
5
+
6
+ import abc
7
+ import importlib
8
+ from inspect import (
9
+ getcomments,
10
+ getmodule,
11
+ isclass,
12
+ isdatadescriptor,
13
+ isfunction,
14
+ ismethod,
15
+ signature,
16
+ )
17
+ from itertools import chain
18
+
19
+ from cs.fsm import FSM
20
+ from cs.gvutils import gvdataurl, GVDATAURL, gvsvg
21
+ from cs.lex import cutprefix, stripped_dedent, indent
22
+ from cs.logutils import warning
23
+ from cs.pfx import Pfx, pfx_call
24
+ from cs.py.modules import module_attributes
25
+
26
+ __version__ = '20250426'
27
+
28
+ DISTINFO = {
29
+ 'keywords': ["python2", "python3"],
30
+ 'classifiers': [
31
+ "Programming Language :: Python",
32
+ "Programming Language :: Python :: 3",
33
+ ],
34
+ 'install_requires': [
35
+ 'cs.lex',
36
+ 'cs.logutils',
37
+ 'cs.pfx',
38
+ 'cs.py.modules',
39
+ ],
40
+ }
41
+
42
+ def is_dunder(name):
43
+ ''' Test whether a name is a dunder name (`__`*foo*`__`).
44
+ '''
45
+ return len(name) > 4 and name.startswith('__') and name.endswith('__')
46
+
47
+ def module_doc(
48
+ module,
49
+ *,
50
+ sort_key=lambda item: item[0].lower(),
51
+ filter_key=lambda key: key != 'DISTINFO' and not key.startswith('_'),
52
+ method_names=None,
53
+ ):
54
+ ''' Fetch the docstrings from a module and assemble a MarkDown document.
55
+
56
+ Parameters:
57
+ * `module`: the module or module name to inspect
58
+ * `sort_key`: optional key for sorting names in the documentation;
59
+ default: `name`
60
+ * filter_key`: optional test for a key used to select or reject keys
61
+ to appear in the documentation
62
+ * `method_names`: optional list of method names to document;
63
+ the default is to document `__init__`, then CONSTANTS, the
64
+ dunders, then other public names
65
+ '''
66
+ from cs.cmdutils import BaseCommand
67
+ if isinstance(module, str):
68
+ module = pfx_call(importlib.import_module, module)
69
+ full_docs = [obj_docstring(module)]
70
+ ALL = getattr(module, '__all__', None)
71
+
72
+ def doc_item(anchor, header, obj_doc, nl="\n"):
73
+ ## return f'\n\n## <a name="{anchor}"></a>`{header}`\n\n{obj_doc}'
74
+ list_item = f'{nl}- <a name="{anchor}"></a>`{str(header)}`: {stripped_dedent(obj_doc,sub_indent=" ")}'
75
+ return list_item
76
+
77
+ full_docs.append('\n\nShort summary:')
78
+ for Mname, obj in sorted(module_attributes(module), key=sort_key):
79
+ with Pfx(Mname):
80
+ if ALL and Mname not in ALL:
81
+ continue
82
+ if not filter_key(Mname):
83
+ continue
84
+ obj_module = getmodule(obj)
85
+ if obj_module is not module:
86
+ # name imported from another module
87
+ continue
88
+ docstring = getattr(obj, '__doc__', '').strip()
89
+ if not docstring:
90
+ continue
91
+ line1 = " ".join(
92
+ line.strip()
93
+ for line in docstring.split("\n\n")[0].split(". ")[0].split("\n")
94
+ )
95
+ if line1[0].isupper() and not line1.endswith('.'):
96
+ line1 += '.'
97
+ full_docs.append(f'\n* `{Mname}`: {line1}')
98
+
99
+ full_docs.append('\n\nModule contents:')
100
+ for Mname, obj in sorted(module_attributes(module), key=sort_key):
101
+ with Pfx(Mname):
102
+ if ALL and Mname not in ALL:
103
+ continue
104
+ if not filter_key(Mname):
105
+ continue
106
+ obj_module = getmodule(obj)
107
+ if obj_module is not module:
108
+ # name imported from another module
109
+ continue
110
+ assert obj_module
111
+ obj_doc = obj_docstring(obj) if obj_module else ''
112
+ if not callable(obj):
113
+ if obj_doc:
114
+ full_docs.append(doc_item(Mname, f'{Mname} = {obj!r}', obj_doc))
115
+ continue
116
+ if not obj_doc:
117
+ continue
118
+ if isfunction(obj):
119
+ sig = signature(obj)
120
+ full_docs.append(doc_item(Mname, f'{Mname}{sig}', obj_doc))
121
+ elif isclass(obj):
122
+ classname_etc = Mname
123
+ # compute the list of immediate superclass names
124
+ mro_names = []
125
+ mro_set = set(obj.__mro__)
126
+ for superclass in obj.__mro__:
127
+ if superclass not in mro_set:
128
+ continue
129
+ if (superclass is not object and superclass is not obj
130
+ and superclass is not abc.ABC):
131
+ supername = superclass.__name__
132
+ supermod = getmodule(superclass)
133
+ if supermod is not module:
134
+ supername = supermod.__name__ + '.' + supername
135
+ mro_names.append(supername)
136
+ mro_set.difference_update(superclass.__mro__)
137
+ if mro_names:
138
+ classname_etc += '(' + ', '.join(mro_names) + ')'
139
+ if issubclass(obj, FSM) and hasattr(obj, 'FSM_TRANSITIONS'):
140
+ # append an FSM state diagram
141
+ obj_doc += (
142
+ f'\n\nState diagram:\n![{Mname} State Diagram](' + gvdataurl(
143
+ obj.fsm_state_diagram_as_dot(
144
+ graph_name=f'{Mname} State Diagram',
145
+ sep='',
146
+ ),
147
+ fmt='svg',
148
+ dataurl_encoding='base64',
149
+ ) + f' "{Mname} State Diagram")\n'
150
+ )
151
+ if issubclass(obj, BaseCommand):
152
+ # extract the Usage: paragraph if present, append a full usage
153
+ doc_without_usage, usage_text = obj.extract_usage()
154
+ obj_doc += ''.join(
155
+ (
156
+ doc_without_usage,
157
+ "\n\nUsage summary:\n\n",
158
+ indent("Usage: " + usage_text, " "),
159
+ )
160
+ )
161
+ full_docs.append(doc_item(Mname, f'Class `{classname_etc}', obj_doc))
162
+ seen_names = set()
163
+ direct_attrs = dict(obj.__dict__)
164
+ # iterate over specified names or default names in order
165
+ for attr_name in method_names or chain(
166
+ # constructor and initialiser
167
+ (
168
+ '__init__',),
169
+ # "constants"
170
+ sorted(filter(lambda name: name and name[0].isupper(),
171
+ direct_attrs)),
172
+ # dunder methods
173
+ sorted(filter(is_dunder, direct_attrs)),
174
+ # remaining attributes
175
+ sorted(filter(lambda name: name and not name.startswith('_'),
176
+ direct_attrs)),
177
+ ):
178
+ # prevent repeats, as the automatic list is composed of
179
+ # overlapping components
180
+ if attr_name in seen_names:
181
+ continue
182
+ seen_names.add(attr_name)
183
+ if not method_names:
184
+ # prune some boring names
185
+ if attr_name in ('__abstractmethods__', '__doc__',
186
+ '__getnewargs__', '__module__', '__new__',
187
+ '__repr__', '__weakref__'):
188
+ continue
189
+ # prune private names which are not dunder names
190
+ if attr_name.startswith('_') and not is_dunder(attr_name):
191
+ continue
192
+ if attr_name not in direct_attrs:
193
+ ##print(" skip, not in direct_attrs", direct_attrs)
194
+ continue
195
+ attr = getattr(obj, attr_name)
196
+ attr_doc = obj_docstring(attr)
197
+ if not attr_doc:
198
+ continue
199
+ # Class.name is a function, not a method
200
+ if ismethod(attr) or isfunction(attr):
201
+ method_sig = signature(attr)
202
+ full_docs.append(
203
+ f'\n\n*`{Mname}.{attr_name}{method_sig}`*:\n{attr_doc}'
204
+ )
205
+ elif isdatadescriptor(attr):
206
+ full_docs.append(f'\n\n*`{Mname}.{attr_name}`*:\n{attr_doc}')
207
+ elif not callable(attr):
208
+ pass
209
+ elif isinstance(attr, property):
210
+ full_docs.append(f'\n\n*`{Mname}.{attr_name}`*:\n{attr_doc}')
211
+ else:
212
+ full_docs.append(f'\n\n*`{Mname}.{attr_name}`*')
213
+ else:
214
+ warning("UNHANDLED %r, neither function nor class", Mname)
215
+ return ''.join(full_docs)
216
+
217
+ # TODO: use inspect.getdoc() initially
218
+ def obj_docstring(obj):
219
+ ''' Return a docstring for `obj` which has been passed through `stripped_dedent`.
220
+
221
+ This function uses `obj.__doc__` if it is not `None`,
222
+ otherwise `getcomments(obj)` if that is not `None`,
223
+ otherwise `''`.
224
+ The chosen string is passed through `stripped_dedent` before return.
225
+ '''
226
+ docstring = getattr(obj, '__doc__', None)
227
+ if docstring is None:
228
+ docstring = '\n'.join(
229
+ map(
230
+ lambda line: cutprefix(line, '# '), (getcomments(obj)
231
+ or '').rstrip().split('\n')
232
+ )
233
+ )
234
+ return stripped_dedent(docstring)