docstringify 0.1.0__py3-none-any.whl
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.
- docstringify/__init__.py +6 -0
- docstringify/__main__.py +5 -0
- docstringify/cli.py +71 -0
- docstringify/components.py +22 -0
- docstringify/converters/__init__.py +1 -0
- docstringify/converters/base.py +51 -0
- docstringify/converters/numpydoc.py +47 -0
- docstringify/visitor.py +196 -0
- docstringify-0.1.0.dist-info/METADATA +146 -0
- docstringify-0.1.0.dist-info/RECORD +14 -0
- docstringify-0.1.0.dist-info/WHEEL +5 -0
- docstringify-0.1.0.dist-info/entry_points.txt +2 -0
- docstringify-0.1.0.dist-info/licenses/LICENSE +21 -0
- docstringify-0.1.0.dist-info/top_level.txt +1 -0
docstringify/__init__.py
ADDED
docstringify/__main__.py
ADDED
docstringify/cli.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Sequence
|
|
6
|
+
|
|
7
|
+
from . import __version__
|
|
8
|
+
from .converters.numpydoc import NumpydocDocstringConverter
|
|
9
|
+
from .visitor import DocstringVisitor
|
|
10
|
+
|
|
11
|
+
PROG = __package__
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
15
|
+
parser = argparse.ArgumentParser(prog=PROG)
|
|
16
|
+
parser.add_argument(
|
|
17
|
+
'filenames',
|
|
18
|
+
nargs='*',
|
|
19
|
+
help='Filenames to process',
|
|
20
|
+
)
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
'--version', action='version', version=f'%(prog)s {__version__}'
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
'--suggest-changes',
|
|
27
|
+
action='store_true',
|
|
28
|
+
help='Whether to provide docstring templates for items missing docstrings',
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
'--style',
|
|
32
|
+
choices=['numpydoc'],
|
|
33
|
+
default='numpydoc',
|
|
34
|
+
help='The style of docstring to use (only used when --suggest-changes is passed)',
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
'--threshold',
|
|
38
|
+
type=float,
|
|
39
|
+
default=1,
|
|
40
|
+
help='The percentage of docstrings that must be present to pass',
|
|
41
|
+
)
|
|
42
|
+
args = parser.parse_args(argv)
|
|
43
|
+
|
|
44
|
+
if args.style is None or not args.suggest_changes:
|
|
45
|
+
converter = None
|
|
46
|
+
elif args.style == 'numpydoc':
|
|
47
|
+
converter = NumpydocDocstringConverter()
|
|
48
|
+
else:
|
|
49
|
+
raise NotImplementedError(f'{args.style} is not a supported option')
|
|
50
|
+
|
|
51
|
+
docstrings_processed = missing_docstrings = 0
|
|
52
|
+
for file in args.filenames:
|
|
53
|
+
visitor = DocstringVisitor(file, converter=converter)
|
|
54
|
+
visitor.process_file()
|
|
55
|
+
missing_docstrings += visitor.missing_docstrings
|
|
56
|
+
docstrings_processed += visitor.docstrings_inspected
|
|
57
|
+
|
|
58
|
+
if docstrings_processed and (
|
|
59
|
+
missing_percentage := (missing_docstrings / docstrings_processed)
|
|
60
|
+
) > (1 - args.threshold):
|
|
61
|
+
print(f'Missing {missing_percentage:.0%} of docstrings', file=sys.stderr)
|
|
62
|
+
print(
|
|
63
|
+
f'Your settings require {args.threshold:.0%} of docstrings to be present',
|
|
64
|
+
file=sys.stderr,
|
|
65
|
+
)
|
|
66
|
+
return 1
|
|
67
|
+
return 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == '__main__':
|
|
71
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Docstring components."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Final, NamedTuple
|
|
6
|
+
|
|
7
|
+
NO_DEFAULT: Final = object()
|
|
8
|
+
DESCRIPTION_PLACEHOLDER = '__description__'
|
|
9
|
+
PARAMETER_TYPE_PLACEHOLDER = '__type__'
|
|
10
|
+
RETURN_TYPE_PLACEHOLDER = '__return_type__'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Parameter(NamedTuple):
|
|
14
|
+
name: str
|
|
15
|
+
type_: str
|
|
16
|
+
category: str | None
|
|
17
|
+
default: str | NO_DEFAULT
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Function(NamedTuple):
|
|
21
|
+
parameters: tuple[Parameter, ...]
|
|
22
|
+
return_type: str | None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Docstring converters."""
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Base docstring converter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..components import Function, Parameter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DocstringConverter(ABC):
|
|
13
|
+
def __init__(
|
|
14
|
+
self, parameters_section_template: str, returns_section_template: str
|
|
15
|
+
) -> None:
|
|
16
|
+
self.parameters_section_template = parameters_section_template
|
|
17
|
+
self.returns_section_template = returns_section_template
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def to_class_docstring(self, class_name: str) -> str:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def to_function_docstring(self, function: Function) -> str:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def to_module_docstring(self, module_name: str) -> str:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def format_parameter(self, parameter: Parameter) -> str:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
def parameters_section(self, parameters: tuple[Parameter, ...]) -> str:
|
|
36
|
+
if parameters:
|
|
37
|
+
return self.parameters_section_template.format(
|
|
38
|
+
parameters='\n'.join(
|
|
39
|
+
self.format_parameter(parameter) for parameter in parameters
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
return ''
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def format_return(self, return_type: str | None) -> str:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def returns_section(self, return_type: str | None) -> str:
|
|
49
|
+
if return_text := self.format_return(return_type):
|
|
50
|
+
return self.returns_section_template.format(returns=return_text)
|
|
51
|
+
return ''
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Numpydoc-style docstring converter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..components import DESCRIPTION_PLACEHOLDER, NO_DEFAULT, Function, Parameter
|
|
6
|
+
from .base import DocstringConverter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NumpydocDocstringConverter(DocstringConverter):
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
super().__init__(
|
|
12
|
+
parameters_section_template='Parameters\n----------\n{parameters}',
|
|
13
|
+
returns_section_template='Returns\n-------\n{returns}',
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
def to_function_docstring(self, function: Function) -> str:
|
|
17
|
+
# TODO: the visitor needs the triple quotes but the transformer does not
|
|
18
|
+
docstring = ['"""', DESCRIPTION_PLACEHOLDER]
|
|
19
|
+
|
|
20
|
+
if parameters_section := self.parameters_section(function.parameters):
|
|
21
|
+
docstring.extend(['', parameters_section])
|
|
22
|
+
|
|
23
|
+
if returns_section := self.returns_section(function.return_type):
|
|
24
|
+
docstring.extend(['', returns_section])
|
|
25
|
+
|
|
26
|
+
sep = '' if len(docstring) == 2 else '\n'
|
|
27
|
+
|
|
28
|
+
return sep.join([*docstring, '"""'])
|
|
29
|
+
|
|
30
|
+
def format_parameter(self, parameter: Parameter) -> str:
|
|
31
|
+
return (
|
|
32
|
+
f'{parameter.name} : {parameter.type_}'
|
|
33
|
+
f'{f", {parameter.category}" if parameter.category else ""}'
|
|
34
|
+
f'{f", default={parameter.default}" if parameter.default != NO_DEFAULT else ""}'
|
|
35
|
+
f'\n {DESCRIPTION_PLACEHOLDER}'
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def format_return(self, return_type: str | None) -> str:
|
|
39
|
+
if return_type:
|
|
40
|
+
return f'{return_type}\n {DESCRIPTION_PLACEHOLDER}'
|
|
41
|
+
return ''
|
|
42
|
+
|
|
43
|
+
def to_module_docstring(self, module_name: str) -> str:
|
|
44
|
+
return f'"""{DESCRIPTION_PLACEHOLDER}"""'
|
|
45
|
+
|
|
46
|
+
def to_class_docstring(self, class_name: str) -> str:
|
|
47
|
+
return f'"""{DESCRIPTION_PLACEHOLDER}"""'
|
docstringify/visitor.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING, Literal
|
|
7
|
+
|
|
8
|
+
from .components import (
|
|
9
|
+
NO_DEFAULT,
|
|
10
|
+
PARAMETER_TYPE_PLACEHOLDER,
|
|
11
|
+
RETURN_TYPE_PLACEHOLDER,
|
|
12
|
+
Function,
|
|
13
|
+
Parameter,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from .converters.base import DocstringConverter
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DocstringVisitor(ast.NodeVisitor):
|
|
21
|
+
def __init__(
|
|
22
|
+
self, filename: str, converter: DocstringConverter | None = None
|
|
23
|
+
) -> None:
|
|
24
|
+
self.source_file: Path = Path(filename)
|
|
25
|
+
self.source_code: str = self.source_file.read_text()
|
|
26
|
+
|
|
27
|
+
self.docstrings_inspected: int = 0
|
|
28
|
+
self.missing_docstrings: int = 0
|
|
29
|
+
|
|
30
|
+
self.module_name: str = self.source_file.stem
|
|
31
|
+
self.stack: list[str] = []
|
|
32
|
+
|
|
33
|
+
self.provide_hints: bool = converter is not None
|
|
34
|
+
if self.provide_hints:
|
|
35
|
+
self.converter: DocstringVisitor = converter # TODO: consider whether this should be instantiated here instead of outside
|
|
36
|
+
|
|
37
|
+
def _extract_default_values(
|
|
38
|
+
self, default: ast.Constant | None | Literal[NO_DEFAULT], is_keyword_only: bool
|
|
39
|
+
) -> str | Literal[NO_DEFAULT]:
|
|
40
|
+
if (not is_keyword_only and default is not NO_DEFAULT) or (
|
|
41
|
+
is_keyword_only and default
|
|
42
|
+
):
|
|
43
|
+
try:
|
|
44
|
+
default_value = default.value
|
|
45
|
+
except AttributeError:
|
|
46
|
+
default_value = f'`{default.id}`'
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
f'"{default_value}"'
|
|
50
|
+
if isinstance(default_value, str) and not default_value.startswith('`')
|
|
51
|
+
else default_value
|
|
52
|
+
)
|
|
53
|
+
return NO_DEFAULT
|
|
54
|
+
|
|
55
|
+
def extract_arguments(
|
|
56
|
+
self, node: ast.AsyncFunctionDef | ast.FunctionDef
|
|
57
|
+
) -> tuple[Parameter, ...]:
|
|
58
|
+
modifiers = {
|
|
59
|
+
'posonlyargs': 'positional-only',
|
|
60
|
+
'kwonlyargs': 'keyword-only',
|
|
61
|
+
}
|
|
62
|
+
params = []
|
|
63
|
+
|
|
64
|
+
positional_arguments_count = len(node.args.posonlyargs) + len(node.args.args)
|
|
65
|
+
if (
|
|
66
|
+
default_count := len(positional_defaults := node.args.defaults)
|
|
67
|
+
) < positional_arguments_count:
|
|
68
|
+
positional_defaults = [NO_DEFAULT] * (
|
|
69
|
+
positional_arguments_count - default_count
|
|
70
|
+
) + positional_defaults
|
|
71
|
+
|
|
72
|
+
keyword_defaults = node.args.kw_defaults
|
|
73
|
+
|
|
74
|
+
processed_positional_args = 0
|
|
75
|
+
for arg_type, args in ast.iter_fields(node.args):
|
|
76
|
+
if arg_type.endswith('defaults'):
|
|
77
|
+
continue
|
|
78
|
+
modifier = modifiers.get(arg_type)
|
|
79
|
+
if arg_type in ['vararg', 'kwarg']:
|
|
80
|
+
if args:
|
|
81
|
+
params.append(
|
|
82
|
+
Parameter(
|
|
83
|
+
name=f'*{args.arg}'
|
|
84
|
+
if arg_type == 'vararg'
|
|
85
|
+
else f'**{args.arg}',
|
|
86
|
+
type_=getattr(
|
|
87
|
+
args.annotation, 'id', PARAMETER_TYPE_PLACEHOLDER
|
|
88
|
+
),
|
|
89
|
+
category=modifier,
|
|
90
|
+
default=NO_DEFAULT,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
is_keyword_only = arg_type.startswith('kw')
|
|
95
|
+
params.extend(
|
|
96
|
+
[
|
|
97
|
+
Parameter(
|
|
98
|
+
name=arg.arg,
|
|
99
|
+
type_=getattr(
|
|
100
|
+
arg.annotation, 'id', PARAMETER_TYPE_PLACEHOLDER
|
|
101
|
+
),
|
|
102
|
+
category=modifier,
|
|
103
|
+
default=self._extract_default_values(
|
|
104
|
+
default, is_keyword_only
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
for arg, default in zip(
|
|
108
|
+
args,
|
|
109
|
+
keyword_defaults
|
|
110
|
+
if is_keyword_only
|
|
111
|
+
else positional_defaults[processed_positional_args:],
|
|
112
|
+
)
|
|
113
|
+
]
|
|
114
|
+
)
|
|
115
|
+
if not is_keyword_only:
|
|
116
|
+
processed_positional_args += len(args)
|
|
117
|
+
|
|
118
|
+
params = tuple(params)
|
|
119
|
+
if (
|
|
120
|
+
params
|
|
121
|
+
and isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef)
|
|
122
|
+
and params[0].name.startswith(('self', 'cls'))
|
|
123
|
+
):
|
|
124
|
+
return params[1:]
|
|
125
|
+
return params
|
|
126
|
+
|
|
127
|
+
def extract_returns(
|
|
128
|
+
self, node: ast.AsyncFunctionDef | ast.FunctionDef
|
|
129
|
+
) -> str | None:
|
|
130
|
+
if return_node := node.returns:
|
|
131
|
+
if isinstance(return_node, ast.Constant):
|
|
132
|
+
return return_node.value
|
|
133
|
+
if isinstance(return_node, ast.Name):
|
|
134
|
+
return return_node.id
|
|
135
|
+
return ast.get_source_segment(self.source_code, return_node)
|
|
136
|
+
if (
|
|
137
|
+
return_nodes := [
|
|
138
|
+
body_node
|
|
139
|
+
for body_node in node.body
|
|
140
|
+
if isinstance(body_node, ast.Return)
|
|
141
|
+
]
|
|
142
|
+
) and any(
|
|
143
|
+
not isinstance(return_value := body_return_node.value, ast.Constant)
|
|
144
|
+
or return_value.value
|
|
145
|
+
for body_return_node in return_nodes
|
|
146
|
+
):
|
|
147
|
+
return RETURN_TYPE_PLACEHOLDER
|
|
148
|
+
return return_node
|
|
149
|
+
|
|
150
|
+
def report_missing_docstring(self) -> None:
|
|
151
|
+
self.missing_docstrings += 1
|
|
152
|
+
print(f'{".".join(self.stack)} is missing a docstring', file=sys.stderr)
|
|
153
|
+
|
|
154
|
+
def suggest_docstring(
|
|
155
|
+
self, node: ast.AsyncFunctionDef | ast.ClassDef | ast.FunctionDef | ast.Module
|
|
156
|
+
) -> str:
|
|
157
|
+
if isinstance(node, ast.AsyncFunctionDef | ast.FunctionDef):
|
|
158
|
+
return self.converter.to_function_docstring(
|
|
159
|
+
Function(self.extract_arguments(node), self.extract_returns(node))
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if isinstance(node, ast.Module):
|
|
163
|
+
return self.converter.to_module_docstring(self.module_name)
|
|
164
|
+
|
|
165
|
+
# TODO: with the stack, I should be able to let __init__() not have a docstring
|
|
166
|
+
# and suggest those parameters as the docstring for the class
|
|
167
|
+
# (may need to keep the class node in another stack)
|
|
168
|
+
return self.converter.to_class_docstring(node.name)
|
|
169
|
+
|
|
170
|
+
def process_docstring(
|
|
171
|
+
self, node: ast.AsyncFunctionDef | ast.ClassDef | ast.FunctionDef | ast.Module
|
|
172
|
+
) -> None:
|
|
173
|
+
if not ast.get_docstring(node):
|
|
174
|
+
self.report_missing_docstring()
|
|
175
|
+
if self.provide_hints:
|
|
176
|
+
print('Hint:')
|
|
177
|
+
print(self.suggest_docstring(node))
|
|
178
|
+
print()
|
|
179
|
+
|
|
180
|
+
self.docstrings_inspected += 1
|
|
181
|
+
|
|
182
|
+
def visit(self, node: ast.AST) -> None:
|
|
183
|
+
if isinstance(
|
|
184
|
+
node, (ast.Module, ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)
|
|
185
|
+
):
|
|
186
|
+
self.stack.append(
|
|
187
|
+
self.module_name if isinstance(node, ast.Module) else node.name
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
self.process_docstring(node)
|
|
191
|
+
|
|
192
|
+
self.generic_visit(node)
|
|
193
|
+
_ = self.stack.pop()
|
|
194
|
+
|
|
195
|
+
def process_file(self) -> None:
|
|
196
|
+
self.visit(ast.parse(self.source_code))
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: docstringify
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Flag missing docstrings and, optionally, generate them from signatures and type annotations.
|
|
5
|
+
Author-email: Stefanie Molin <docstringify@stefaniemolin.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Stefanie Molin
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Documentation, https://github.com/stefmolin/docstringify
|
|
29
|
+
Project-URL: Homepage, https://github.com/stefmolin/docstringify
|
|
30
|
+
Project-URL: Source, https://github.com/stefmolin/docstringify
|
|
31
|
+
Keywords: ast,docstring generation,numpydoc-style docstring,pre-commit
|
|
32
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
33
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
34
|
+
Classifier: Programming Language :: Python
|
|
35
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Requires-Python: >=3.8
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
License-File: LICENSE
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
47
|
+
Requires-Dist: pytest; extra == "dev"
|
|
48
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
49
|
+
Requires-Dist: pytest-randomly; extra == "dev"
|
|
50
|
+
Dynamic: license-file
|
|
51
|
+
|
|
52
|
+
# docstringify
|
|
53
|
+
Flag missing docstrings and, optionally, generate them from signatures and type annotations.
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
### Pre-commit hook
|
|
58
|
+
|
|
59
|
+
Add the following to your `.pre-commit-config.yaml` file to block commits with missing docstrings:
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
- repo: https://github.com/stefmolin/docstringify
|
|
63
|
+
rev: 0.1.0
|
|
64
|
+
hooks:
|
|
65
|
+
- id: docstringify
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
By default, all docstrings are required. If you want to be more lenient, you can set the threshold, which is the percentage of docstrings that must be present:
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
- repo: https://github.com/stefmolin/docstringify
|
|
72
|
+
rev: 0.1.0
|
|
73
|
+
hooks:
|
|
74
|
+
- id: docstringify
|
|
75
|
+
args: [--threshold=0.75]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
If you would like to see suggested docstring templates (inferred from type annotations for functions and methods), provide the `--suggest-changes` argument. By default, these will be [numpydoc-style docstrings](https://numpydoc.readthedocs.io/en/latest/format.html#); support for Google-style docstrings will come in a future release:
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
- repo: https://github.com/stefmolin/docstringify
|
|
82
|
+
rev: 0.1.0
|
|
83
|
+
hooks:
|
|
84
|
+
- id: docstringify
|
|
85
|
+
args: [--suggest-changes]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Be sure to check out the [pre-commit documentation](https://pre-commit.com/#pre-commit-configyaml---hooks) for additional configuration options.
|
|
89
|
+
|
|
90
|
+
### Command line
|
|
91
|
+
|
|
92
|
+
First, install the `docstringify` package from PyPI:
|
|
93
|
+
|
|
94
|
+
```shell
|
|
95
|
+
$ python -m pip install docstringify
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Then, use the `docstringify` entry point on the file(s) of your choice:
|
|
99
|
+
|
|
100
|
+
```shell
|
|
101
|
+
$ docstringify /path/to/file [/path/to/another/file]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Run `docstringify --help` for more information.
|
|
105
|
+
|
|
106
|
+
### Python
|
|
107
|
+
|
|
108
|
+
First, install the `docstringify` package from PyPI:
|
|
109
|
+
|
|
110
|
+
```shell
|
|
111
|
+
$ python -m pip install docstringify
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Then, use the `DocstringVisitor()` class on individual files to see spots where docstrings are missing:
|
|
115
|
+
|
|
116
|
+
```pycon
|
|
117
|
+
>>> from docstringify.visitor import DocstringVisitor
|
|
118
|
+
>>> visitor = DocstringVisitor('test.py')
|
|
119
|
+
>>> visitor.process_file()
|
|
120
|
+
test is missing a docstring
|
|
121
|
+
test.say_hello is missing a docstring
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If you would like to see suggested docstring templates (inferred from type annotations for functions and methods), provide a converter:
|
|
125
|
+
|
|
126
|
+
```pycon
|
|
127
|
+
>>> from docstringify.converters.numpydoc import NumpydocDocstringConverter
|
|
128
|
+
>>> from docstringify.visitor import DocstringVisitor
|
|
129
|
+
>>> visitor = DocstringVisitor('test.py', converter=NumpydocDocstringConverter())
|
|
130
|
+
>>> visitor.process_file()
|
|
131
|
+
test is missing a docstring
|
|
132
|
+
Hint:
|
|
133
|
+
"""__description__"""
|
|
134
|
+
|
|
135
|
+
test.say_hello is missing a docstring
|
|
136
|
+
Hint:
|
|
137
|
+
"""
|
|
138
|
+
__description__
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
name : str, default="World"
|
|
143
|
+
__description__
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
docstringify/__init__.py,sha256=uXJ3o6quBR5skKRbCl3u8jRzjsmj6tGr1qb-3QdFf5Q,124
|
|
2
|
+
docstringify/__main__.py,sha256=A5Xq9jCZwHnQVVGklF9ueL-ad-hInsceHQNgze3Yq6E,96
|
|
3
|
+
docstringify/cli.py,sha256=QrEkav5pGGyCRx6w3cI4SOj4CcBx6YIRT48RVfOgFNo,2121
|
|
4
|
+
docstringify/components.py,sha256=7Q__cuBTWNAqLDTil_91Q7T-zIzr7YpbX2TcmXrfph0,471
|
|
5
|
+
docstringify/visitor.py,sha256=zY0nSAwVyIP1Qa0jkfCKLrKEKlIA0xQ6VtBB5R96V00,7024
|
|
6
|
+
docstringify/converters/__init__.py,sha256=vZE16UrOdY29rAkIH0HcFfr_FDhApua24CJoYM-xL6I,28
|
|
7
|
+
docstringify/converters/base.py,sha256=1QWFE5ckS_Rbwi9TyRM-rKCA7MCwHgZ4Ok8ugMyvMgA,1507
|
|
8
|
+
docstringify/converters/numpydoc.py,sha256=iv5smFrHFBc0rHvLhgh66PaV2OEI3va_piRZ3_y2Ig8,1773
|
|
9
|
+
docstringify-0.1.0.dist-info/licenses/LICENSE,sha256=E5RHv_qfCZPwnlJ_ZQb-WKOOy07LaJBA2a160OGtT3U,1071
|
|
10
|
+
docstringify-0.1.0.dist-info/METADATA,sha256=fsJmCbMOiorPafl732Vwa3y6czXWSdGHmL997u_jsBI,5142
|
|
11
|
+
docstringify-0.1.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
12
|
+
docstringify-0.1.0.dist-info/entry_points.txt,sha256=dJVPsbclB7stLE8g0d4WbO2NaZQLJXSLEVl6oeROAqI,55
|
|
13
|
+
docstringify-0.1.0.dist-info/top_level.txt,sha256=3RFmaFG0rYs4HkqRljatGQtY4BcNN7mqjK4tSznYdnQ,13
|
|
14
|
+
docstringify-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Stefanie Molin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
docstringify
|