docstringify 0.7.0__tar.gz → 1.0.0__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.
Files changed (41) hide show
  1. {docstringify-0.7.0 → docstringify-1.0.0}/.pre-commit-config.yaml +4 -4
  2. {docstringify-0.7.0 → docstringify-1.0.0}/PKG-INFO +30 -10
  3. {docstringify-0.7.0 → docstringify-1.0.0}/README.md +28 -8
  4. {docstringify-0.7.0 → docstringify-1.0.0}/pyproject.toml +1 -1
  5. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify/__init__.py +1 -1
  6. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify/converters/base.py +13 -6
  7. docstringify-1.0.0/src/docstringify/exceptions.py +25 -0
  8. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify/nodes/function.py +41 -17
  9. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify/traversal/transformer.py +81 -14
  10. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify/traversal/visitor.py +8 -8
  11. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify.egg-info/PKG-INFO +30 -10
  12. docstringify-0.7.0/src/docstringify/exceptions.py +0 -6
  13. {docstringify-0.7.0 → docstringify-1.0.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  14. {docstringify-0.7.0 → docstringify-1.0.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  15. {docstringify-0.7.0 → docstringify-1.0.0}/.github/dependabot.yml +0 -0
  16. {docstringify-0.7.0 → docstringify-1.0.0}/.github/pull_request_template.md +0 -0
  17. {docstringify-0.7.0 → docstringify-1.0.0}/.github/release.yml +0 -0
  18. {docstringify-0.7.0 → docstringify-1.0.0}/.github/workflows/ci.yml +0 -0
  19. {docstringify-0.7.0 → docstringify-1.0.0}/.github/workflows/greetings.yml +0 -0
  20. {docstringify-0.7.0 → docstringify-1.0.0}/.github/workflows/pypi-publish.yml +0 -0
  21. {docstringify-0.7.0 → docstringify-1.0.0}/.gitignore +0 -0
  22. {docstringify-0.7.0 → docstringify-1.0.0}/.pre-commit-hooks.yaml +0 -0
  23. {docstringify-0.7.0 → docstringify-1.0.0}/CODE_OF_CONDUCT.md +0 -0
  24. {docstringify-0.7.0 → docstringify-1.0.0}/CONTRIBUTING.md +0 -0
  25. {docstringify-0.7.0 → docstringify-1.0.0}/LICENSE +0 -0
  26. {docstringify-0.7.0 → docstringify-1.0.0}/setup.cfg +0 -0
  27. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify/__main__.py +0 -0
  28. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify/cli.py +0 -0
  29. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify/components.py +0 -0
  30. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify/converters/__init__.py +0 -0
  31. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify/converters/google.py +0 -0
  32. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify/converters/numpydoc.py +0 -0
  33. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify/converters/stub.py +0 -0
  34. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify/nodes/__init__.py +0 -0
  35. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify/nodes/base.py +0 -0
  36. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify/traversal/__init__.py +0 -0
  37. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify.egg-info/SOURCES.txt +0 -0
  38. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify.egg-info/dependency_links.txt +0 -0
  39. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify.egg-info/entry_points.txt +0 -0
  40. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify.egg-info/requires.txt +0 -0
  41. {docstringify-0.7.0 → docstringify-1.0.0}/src/docstringify.egg-info/top_level.txt +0 -0
@@ -12,20 +12,20 @@ repos:
12
12
  - id: trailing-whitespace
13
13
 
14
14
  - repo: https://github.com/stefmolin/docstringify
15
- rev: 0.6.0
15
+ rev: 0.7.0
16
16
  hooks:
17
17
  - id: docstringify
18
18
  args: [--make-changes-inplace=numpydoc]
19
19
 
20
20
  - repo: https://github.com/astral-sh/ruff-pre-commit
21
- rev: v0.11.9
21
+ rev: v0.11.10
22
22
  hooks:
23
- - id: ruff
23
+ - id: ruff-check
24
24
  args: [--fix, --exit-non-zero-on-fix, --show-fixes]
25
25
  - id: ruff-format
26
26
 
27
27
  - repo: https://github.com/tox-dev/pyproject-fmt
28
- rev: v2.5.1
28
+ rev: v2.6.0
29
29
  hooks:
30
30
  - id: pyproject-fmt
31
31
  args: [--keep-full-version, --no-print-diff]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docstringify
3
- Version: 0.7.0
3
+ Version: 1.0.0
4
4
  Summary: Flag missing docstrings and, optionally, generate them from signatures and type annotations.
5
5
  Author-email: Stefanie Molin <docstringify@stefaniemolin.com>
6
6
  License: MIT License
@@ -29,7 +29,7 @@ Project-URL: Documentation, https://github.com/stefmolin/docstringify
29
29
  Project-URL: Homepage, https://github.com/stefmolin/docstringify
30
30
  Project-URL: Source, https://github.com/stefmolin/docstringify
31
31
  Keywords: ast,docstring generation,google-style docstring,numpydoc-style docstring,pre-commit
32
- Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Development Status :: 4 - Beta
33
33
  Classifier: Programming Language :: Python
34
34
  Classifier: Programming Language :: Python :: 3 :: Only
35
35
  Classifier: Programming Language :: Python :: 3.9
@@ -103,11 +103,11 @@ You can use Docstringify in three modes:
103
103
 
104
104
  ### Pre-commit hook
105
105
 
106
- Add the following to your `.pre-commit-config.yaml` file to block commits with missing docstrings:
106
+ Add the following to your `.pre-commit-config.yaml` file to block commits with missing docstrings before any formatters like `ruff`:
107
107
 
108
108
  ```yaml
109
109
  - repo: https://github.com/stefmolin/docstringify
110
- rev: 0.7.0
110
+ rev: 1.0.0
111
111
  hooks:
112
112
  - id: docstringify
113
113
  ```
@@ -116,17 +116,17 @@ By default, all docstrings are required. If you want to be more lenient, you can
116
116
 
117
117
  ```yaml
118
118
  - repo: https://github.com/stefmolin/docstringify
119
- rev: 0.7.0
119
+ rev: 1.0.0
120
120
  hooks:
121
121
  - id: docstringify
122
122
  args: [--threshold=0.75]
123
123
  ```
124
124
 
125
- If you would like to see suggested docstring templates (inferred from type annotations for functions and methods), provide the `--suggest-changes` argument, along with the docstring style you want to use (options are `google`, `numpydoc`, and `stub`). Here, we ask for [numpydoc-style docstring](https://numpydoc.readthedocs.io/en/latest/format.html#) suggestions:
125
+ If you would like to see suggested docstring templates (inferred from type annotations for functions and methods), provide the `--suggest-changes` argument, along with the docstring style you want to use (options are `google`, `numpydoc`, and `stub`). Here, we ask for stub suggestions (just single lines of `"""__description__"""`):
126
126
 
127
127
  ```yaml
128
128
  - repo: https://github.com/stefmolin/docstringify
129
- rev: 0.7.0
129
+ rev: 1.0.0
130
130
  hooks:
131
131
  - id: docstringify
132
132
  args: [--suggest-changes=numpydoc]
@@ -136,13 +136,21 @@ Use `--make-changes` to create a copy of each file with docstring templates. Her
136
136
 
137
137
  ```yaml
138
138
  - repo: https://github.com/stefmolin/docstringify
139
- rev: 0.7.0
139
+ rev: 1.0.0
140
140
  hooks:
141
141
  - id: docstringify
142
142
  args: [--make-changes=google]
143
143
  ```
144
144
 
145
- If you want the changes to be made in place, change `--make-changes` to `--make-changes-inplace` &ndash; make sure you only operate on files that are in version control with this setting. Note that the resulting format of your file may be a little different (spacing, newlines, *etc.*).
145
+ If you want the changes to be made in place, change `--make-changes` to `--make-changes-inplace` &ndash; make sure you only operate on files that are in version control with this setting. Here, we ask for [numpydoc-style docstring](https://numpydoc.readthedocs.io/en/latest/format.html#) suggestions:
146
+
147
+ ```yaml
148
+ - repo: https://github.com/stefmolin/docstringify
149
+ rev: 1.0.0
150
+ hooks:
151
+ - id: docstringify
152
+ args: [--make-changes-inplace=numpydoc]
153
+ ```
146
154
 
147
155
  Be sure to check out the [pre-commit documentation](https://pre-commit.com/#pre-commit-configyaml---hooks) for additional configuration options.
148
156
 
@@ -216,7 +224,19 @@ test.say_hello is missing a docstring
216
224
  Docstring templates written to /.../test_docstringify.py
217
225
  ```
218
226
 
219
- If you want to overwrite the file with the edits, pass `overwrite=True` to `DocstringTransformer()`.
227
+ If you want to overwrite the file with the edits, pass `overwrite=True` to `DocstringTransformer()`:
228
+
229
+ ```pycon
230
+ >>> from docstringify.converters import GoogleDocstringConverter
231
+ >>> from docstringify.traversal import DocstringTransformer
232
+ >>> transformer = DocstringTransformer(
233
+ ... 'test.py', converter=GoogleDocstringConverter, overwrite=True
234
+ ... )
235
+ >>> transformer.process_file()
236
+ test is missing a docstring
237
+ test.say_hello is missing a docstring
238
+ Docstring templates written to /.../test.py
239
+ ```
220
240
 
221
241
  ## Contributing
222
242
 
@@ -54,11 +54,11 @@ You can use Docstringify in three modes:
54
54
 
55
55
  ### Pre-commit hook
56
56
 
57
- Add the following to your `.pre-commit-config.yaml` file to block commits with missing docstrings:
57
+ Add the following to your `.pre-commit-config.yaml` file to block commits with missing docstrings before any formatters like `ruff`:
58
58
 
59
59
  ```yaml
60
60
  - repo: https://github.com/stefmolin/docstringify
61
- rev: 0.7.0
61
+ rev: 1.0.0
62
62
  hooks:
63
63
  - id: docstringify
64
64
  ```
@@ -67,17 +67,17 @@ By default, all docstrings are required. If you want to be more lenient, you can
67
67
 
68
68
  ```yaml
69
69
  - repo: https://github.com/stefmolin/docstringify
70
- rev: 0.7.0
70
+ rev: 1.0.0
71
71
  hooks:
72
72
  - id: docstringify
73
73
  args: [--threshold=0.75]
74
74
  ```
75
75
 
76
- If you would like to see suggested docstring templates (inferred from type annotations for functions and methods), provide the `--suggest-changes` argument, along with the docstring style you want to use (options are `google`, `numpydoc`, and `stub`). Here, we ask for [numpydoc-style docstring](https://numpydoc.readthedocs.io/en/latest/format.html#) suggestions:
76
+ If you would like to see suggested docstring templates (inferred from type annotations for functions and methods), provide the `--suggest-changes` argument, along with the docstring style you want to use (options are `google`, `numpydoc`, and `stub`). Here, we ask for stub suggestions (just single lines of `"""__description__"""`):
77
77
 
78
78
  ```yaml
79
79
  - repo: https://github.com/stefmolin/docstringify
80
- rev: 0.7.0
80
+ rev: 1.0.0
81
81
  hooks:
82
82
  - id: docstringify
83
83
  args: [--suggest-changes=numpydoc]
@@ -87,13 +87,21 @@ Use `--make-changes` to create a copy of each file with docstring templates. Her
87
87
 
88
88
  ```yaml
89
89
  - repo: https://github.com/stefmolin/docstringify
90
- rev: 0.7.0
90
+ rev: 1.0.0
91
91
  hooks:
92
92
  - id: docstringify
93
93
  args: [--make-changes=google]
94
94
  ```
95
95
 
96
- If you want the changes to be made in place, change `--make-changes` to `--make-changes-inplace` &ndash; make sure you only operate on files that are in version control with this setting. Note that the resulting format of your file may be a little different (spacing, newlines, *etc.*).
96
+ If you want the changes to be made in place, change `--make-changes` to `--make-changes-inplace` &ndash; make sure you only operate on files that are in version control with this setting. Here, we ask for [numpydoc-style docstring](https://numpydoc.readthedocs.io/en/latest/format.html#) suggestions:
97
+
98
+ ```yaml
99
+ - repo: https://github.com/stefmolin/docstringify
100
+ rev: 1.0.0
101
+ hooks:
102
+ - id: docstringify
103
+ args: [--make-changes-inplace=numpydoc]
104
+ ```
97
105
 
98
106
  Be sure to check out the [pre-commit documentation](https://pre-commit.com/#pre-commit-configyaml---hooks) for additional configuration options.
99
107
 
@@ -167,7 +175,19 @@ test.say_hello is missing a docstring
167
175
  Docstring templates written to /.../test_docstringify.py
168
176
  ```
169
177
 
170
- If you want to overwrite the file with the edits, pass `overwrite=True` to `DocstringTransformer()`.
178
+ If you want to overwrite the file with the edits, pass `overwrite=True` to `DocstringTransformer()`:
179
+
180
+ ```pycon
181
+ >>> from docstringify.converters import GoogleDocstringConverter
182
+ >>> from docstringify.traversal import DocstringTransformer
183
+ >>> transformer = DocstringTransformer(
184
+ ... 'test.py', converter=GoogleDocstringConverter, overwrite=True
185
+ ... )
186
+ >>> transformer.process_file()
187
+ test is missing a docstring
188
+ test.say_hello is missing a docstring
189
+ Docstring templates written to /.../test.py
190
+ ```
171
191
 
172
192
  ## Contributing
173
193
 
@@ -23,7 +23,7 @@ authors = [
23
23
  ]
24
24
  requires-python = ">=3.9"
25
25
  classifiers = [
26
- "Development Status :: 3 - Alpha",
26
+ "Development Status :: 4 - Beta",
27
27
  "Programming Language :: Python",
28
28
  "Programming Language :: Python :: 3 :: Only",
29
29
  "Programming Language :: Python :: 3.9",
@@ -3,4 +3,4 @@ Flag missing docstrings and, optionally, generate them from signatures and
3
3
  type annotations.
4
4
  """
5
5
 
6
- __version__ = '0.7.0'
6
+ __version__ = '1.0.0'
@@ -184,7 +184,9 @@ class DocstringConverter(ABC):
184
184
  return self._returns_section_template.format(returns=return_text)
185
185
  return ''
186
186
 
187
- def format_docstring(self, docstring: str | list[str], indent: int) -> str:
187
+ def format_docstring(
188
+ self, docstring: str | list[str], indent: int, quote: bool | None = None
189
+ ) -> str:
188
190
  """
189
191
  Format the docstring with the requested level of indentation and surrounding
190
192
  triple quotes, if requested.
@@ -195,23 +197,28 @@ class DocstringConverter(ABC):
195
197
  The docstring as a string or list of strings that form lines in the docstring.
196
198
  indent : int
197
199
  The number of spaces by which to indent the docstring.
200
+ quote : bool | None, default=None
201
+ Whether to surround the docstring in triple quotes, passing this overrides
202
+ the ``quote`` setting specified upon initialization.
198
203
 
199
204
  Returns
200
205
  -------
201
206
  str
202
207
  The docstring indented to ``indent`` spaces will be returned either as a
203
- quoted docstring if the converter was initialized with ``quote=True``, or an
204
- unquoted docstring otherwise.
208
+ quoted docstring if the converter was initialized with ``quote=True`` or
209
+ ``quote=True`` was passed when calling this method, or an unquoted
210
+ docstring otherwise.
205
211
  """
206
- quote = '"""' if self._quote else ''
212
+ quote_char = '"""' if quote or self._quote else ''
207
213
  prefix = sep = ''
214
+
208
215
  if isinstance(docstring, str):
209
- docstring = [quote, docstring, quote]
216
+ docstring = [quote_char, docstring, quote_char]
210
217
  elif isinstance(docstring, list):
211
218
  if len(docstring) > 1:
212
219
  prefix = ' ' * indent if indent else ''
213
220
  sep = '\n'
214
- docstring = [quote, *docstring, f'{prefix}{quote}']
221
+ docstring = [quote_char, *docstring, quote_char or f'{prefix}']
215
222
  else:
216
223
  raise InvalidDocstringError(type(docstring).__name__)
217
224
 
@@ -0,0 +1,25 @@
1
+ """Docstringify exceptions."""
2
+
3
+
4
+ class EmptyDocstringError(ValueError):
5
+ """
6
+ Raised when trying to write the modified source code back to a file if a docstring
7
+ is somehow empty.
8
+ """
9
+
10
+ def __init__(self) -> None:
11
+ super().__init__('Docstring is empty')
12
+
13
+
14
+ class InvalidDocstringError(TypeError):
15
+ """
16
+ Raised when trying to create a docstring with invalid input.
17
+
18
+ Parameters
19
+ ----------
20
+ docstring_class : str
21
+ The class of the docstring object. This is used for the error message.
22
+ """
23
+
24
+ def __init__(self, docstring_class: str) -> None:
25
+ super().__init__(f'Expected str or list[str] docstring, got {docstring_class}')
@@ -23,11 +23,17 @@ class FunctionDocstringNode(DocstringNode):
23
23
  parent: DocstringNode,
24
24
  ) -> None:
25
25
  super().__init__(node, module_name, source_code, parent)
26
+
26
27
  self.decorators: list[str] = [
27
- self.get_source_segment(decorator) for decorator in node.decorator_list
28
+ decorator
29
+ for decorator_node in node.decorator_list
30
+ if (decorator := self.get_source_segment(decorator_node))
28
31
  ]
29
32
 
30
- self.is_method: bool = self.parent and isinstance(
33
+ self.is_overload: bool = (
34
+ 'overload' in self.decorators or 'typing.overload' in self.decorators
35
+ )
36
+ self.is_method: bool = self.parent is not None and isinstance(
31
37
  self.parent.ast_node, ast.ClassDef
32
38
  )
33
39
  self.is_abstract_method: bool = self.is_method and (
@@ -39,15 +45,26 @@ class FunctionDocstringNode(DocstringNode):
39
45
  self.is_method and 'staticmethod' in self.decorators
40
46
  )
41
47
  self.is_instance_method: bool = (
42
- self.is_method and not self.is_class_method and not self.is_static_method
48
+ self.is_method
49
+ and (not self.is_class_method)
50
+ and (not self.is_static_method)
43
51
  )
44
52
 
45
53
  # don't require docstring for the __init__ method if the class has a docstring
46
- self.docstring_required: bool = not (
47
- self.is_method and self.name == '__init__' and self.parent.docstring
54
+ # or if the function/method is just a typing.overload signature
55
+ self.docstring_required: bool = (
56
+ not (
57
+ self.is_method
58
+ and self.name == '__init__'
59
+ and self.parent is not None
60
+ and self.parent.docstring
61
+ )
62
+ and not self.is_overload
48
63
  )
49
64
 
50
- self.arguments: ast.arguments | None = getattr(node, 'args', None)
65
+ self.arguments: ast.arguments = node.args
66
+
67
+ self.returns: ast.AST | None = node.returns
51
68
  self.return_annotation: str | None = self._extract_return_annotation()
52
69
  self.return_statements: list[ast.Return] = []
53
70
 
@@ -64,7 +81,8 @@ class FunctionDocstringNode(DocstringNode):
64
81
 
65
82
  return (
66
83
  f'"{default_value}"'
67
- if isinstance(default_value, str) and not default_value.startswith('`')
84
+ if isinstance(default_value, str)
85
+ and (not default_value.startswith('`'))
68
86
  else default_value
69
87
  )
70
88
  return NO_DEFAULT
@@ -95,8 +113,9 @@ class FunctionDocstringNode(DocstringNode):
95
113
 
96
114
  def _extract_positional_args(self) -> list[Parameter]:
97
115
  if (default_count := len(positional_defaults := self.arguments.defaults)) < (
98
- positional_arguments_count := len(self.arguments.posonlyargs)
99
- + len(self.arguments.args)
116
+ positional_arguments_count := (
117
+ len(self.arguments.posonlyargs) + len(self.arguments.args)
118
+ )
100
119
  ):
101
120
  positional_defaults = [NO_DEFAULT] * (
102
121
  positional_arguments_count - default_count
@@ -155,23 +174,28 @@ class FunctionDocstringNode(DocstringNode):
155
174
  return params
156
175
 
157
176
  def _extract_return_annotation(self) -> str | None:
158
- if return_annotation_node := self.ast_node.returns:
159
- if isinstance(return_annotation_node, ast.Constant):
160
- return return_annotation_node.value
161
- if isinstance(return_annotation_node, ast.Name):
162
- return return_annotation_node.id
163
- return self.get_source_segment(return_annotation_node)
164
- return None
177
+ if self.returns is None:
178
+ return self.returns
179
+
180
+ if isinstance(self.returns, ast.Constant):
181
+ return self.returns.value
182
+
183
+ if isinstance(self.returns, ast.Name):
184
+ return self.returns.id
185
+
186
+ return self.get_source_segment(self.returns)
165
187
 
166
188
  def extract_returns(self) -> str | None:
167
189
  if self.return_annotation:
168
190
  return self.return_annotation
191
+
169
192
  if any(
170
- not isinstance(return_value := return_node.value, ast.Constant)
193
+ not isinstance((return_value := return_node.value), ast.Constant)
171
194
  or return_value.value
172
195
  for return_node in self.return_statements
173
196
  ):
174
197
  return RETURN_TYPE_PLACEHOLDER
198
+
175
199
  return None
176
200
 
177
201
  def to_function(self) -> Function:
@@ -6,8 +6,10 @@ on the source code.
6
6
  from __future__ import annotations
7
7
 
8
8
  import ast
9
+ from textwrap import indent
9
10
  from typing import TYPE_CHECKING
10
11
 
12
+ from ..exceptions import EmptyDocstringError
11
13
  from .visitor import DocstringVisitor
12
14
 
13
15
  if TYPE_CHECKING:
@@ -60,11 +62,80 @@ class DocstringTransformer(ast.NodeTransformer, DocstringVisitor):
60
62
  + ''.join(self.source_file.suffixes)
61
63
  )
62
64
  )
63
- edited_code = ast.unparse(self.tree)
65
+ edited_code = self._convert_to_source_code()
64
66
  output.write_text(edited_code)
65
67
  print(f'Docstring templates written to {output}')
66
68
 
67
- def handle_missing_docstring(self, docstring_node: DocstringNode) -> DocstringNode:
69
+ def _convert_to_source_code(self) -> str:
70
+ """
71
+ Convert the modified AST back to source code, preserving the original format and
72
+ any comments.
73
+
74
+ Returns
75
+ -------
76
+ str
77
+ The original source code with the docstrings injected.
78
+ """
79
+ source_code = self.source_code.splitlines()
80
+ output_lines = []
81
+ write_line = start_line = 0
82
+
83
+ for missing_docstring in self.missing_docstrings:
84
+ docstring_node = missing_docstring.ast_node.body[0]
85
+ prefix = docstring_node.col_offset
86
+ suffix = ''
87
+
88
+ # write everything before docstring
89
+ if not isinstance(missing_docstring.ast_node, ast.Module):
90
+ # line before a code node
91
+ start_line = missing_docstring.ast_node.body[1].lineno - 1
92
+
93
+ if isinstance(missing_docstring.ast_node, ast.ClassDef):
94
+ start_line = docstring_node.lineno
95
+
96
+ if len(body := missing_docstring.ast_node.body) == 2:
97
+ code_node = body[1]
98
+ line_number = code_node.lineno - 1
99
+ line = source_code[line_number]
100
+
101
+ if line.strip() != (function_body := line[code_node.col_offset :]):
102
+ # if the function body is on the same line as the signature, cut it out
103
+ # in order to inject the docstring in the right spot
104
+ source_code[line_number] = source_code[line_number][
105
+ : code_node.col_offset
106
+ ].rstrip()
107
+
108
+ # add the logic under the docstring
109
+ start_line = missing_docstring.ast_node.body[1].lineno
110
+ suffix = function_body
111
+
112
+ output_lines += source_code[write_line:start_line]
113
+
114
+ # write docstring
115
+ if not (docstring := missing_docstring.docstring):
116
+ raise EmptyDocstringError
117
+
118
+ output_lines.append(
119
+ indent(
120
+ self.docstring_converter.format_docstring(
121
+ docstring.splitlines(),
122
+ indent=0,
123
+ quote=True,
124
+ )
125
+ + (f'\n{suffix}' if suffix else ''),
126
+ ' ' * prefix,
127
+ )
128
+ )
129
+
130
+ # update current location in file
131
+ write_line = start_line
132
+
133
+ # write the rest of the file
134
+ output_lines += source_code[write_line:]
135
+
136
+ return '\n'.join(output_lines) + '\n'
137
+
138
+ def handle_missing_docstring(self, docstring_node: DocstringNode) -> None:
68
139
  """
69
140
  Handle missing docstrings by injecting a suggested docstring template based on
70
141
  the source code into the AST.
@@ -74,19 +145,16 @@ class DocstringTransformer(ast.NodeTransformer, DocstringVisitor):
74
145
  docstring_node : DocstringNode
75
146
  An instance of :class:`.DocstringNode`, which wraps an AST node and adds
76
147
  additional context relevant for Docstringify.
77
-
78
- Returns
79
- -------
80
- DocstringNode
81
- An instance of :class:`.DocstringNode`, which wraps an AST node and adds
82
- additional context relevant for Docstringify. The AST node it contains will
83
- have a new node for the docstring template added to its body.
84
148
  """
149
+ indent = (
150
+ 0
151
+ if isinstance(docstring_node.ast_node, ast.Module)
152
+ else docstring_node.ast_node.col_offset + 4
153
+ )
154
+
85
155
  suggested_docstring = self.docstring_converter.suggest_docstring(
86
156
  docstring_node,
87
- indent=0
88
- if isinstance(docstring_node.ast_node, ast.Module)
89
- else docstring_node.ast_node.col_offset + 4,
157
+ indent=indent,
90
158
  )
91
159
  docstring_ast_node = ast.Expr(ast.Constant(suggested_docstring))
92
160
 
@@ -98,8 +166,7 @@ class DocstringTransformer(ast.NodeTransformer, DocstringVisitor):
98
166
  docstring_node.ast_node.body.insert(0, docstring_ast_node)
99
167
 
100
168
  docstring_node.ast_node = ast.fix_missing_locations(docstring_node.ast_node)
101
-
102
- return docstring_node
169
+ docstring_ast_node.col_offset = indent
103
170
 
104
171
  def process_file(self) -> None:
105
172
  """
@@ -112,8 +112,8 @@ class DocstringVisitor(ast.NodeVisitor):
112
112
 
113
113
  def process_docstring(self, docstring_node: DocstringNode) -> DocstringNode:
114
114
  """
115
- Process a docstring node, appending it to :attr:`.missing_docstrings` if a docstring
116
- is required, but there isn't one.
115
+ Process a docstring node, appending it to :attr:`.missing_docstrings` if a
116
+ docstring is required, but there isn't one.
117
117
 
118
118
  Parameters
119
119
  ----------
@@ -181,12 +181,12 @@ class DocstringVisitor(ast.NodeVisitor):
181
181
  ast.AsyncFunctionDef | ast.ClassDef | ast.FunctionDef | ast.Module
182
182
  The AST node that was visited.
183
183
  """
184
- docstring_node = docstring_class(
185
- node,
186
- self.module_name,
187
- self.source_code,
188
- parent=self.stack[-1] if self.stack else None,
189
- )
184
+ if isinstance(node, ast.Module):
185
+ docstring_node = docstring_class(node, self.module_name, self.source_code)
186
+ else:
187
+ docstring_node = docstring_class(
188
+ node, self.module_name, self.source_code, parent=self.stack[-1]
189
+ )
190
190
 
191
191
  self.stack.append(docstring_node)
192
192
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docstringify
3
- Version: 0.7.0
3
+ Version: 1.0.0
4
4
  Summary: Flag missing docstrings and, optionally, generate them from signatures and type annotations.
5
5
  Author-email: Stefanie Molin <docstringify@stefaniemolin.com>
6
6
  License: MIT License
@@ -29,7 +29,7 @@ Project-URL: Documentation, https://github.com/stefmolin/docstringify
29
29
  Project-URL: Homepage, https://github.com/stefmolin/docstringify
30
30
  Project-URL: Source, https://github.com/stefmolin/docstringify
31
31
  Keywords: ast,docstring generation,google-style docstring,numpydoc-style docstring,pre-commit
32
- Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Development Status :: 4 - Beta
33
33
  Classifier: Programming Language :: Python
34
34
  Classifier: Programming Language :: Python :: 3 :: Only
35
35
  Classifier: Programming Language :: Python :: 3.9
@@ -103,11 +103,11 @@ You can use Docstringify in three modes:
103
103
 
104
104
  ### Pre-commit hook
105
105
 
106
- Add the following to your `.pre-commit-config.yaml` file to block commits with missing docstrings:
106
+ Add the following to your `.pre-commit-config.yaml` file to block commits with missing docstrings before any formatters like `ruff`:
107
107
 
108
108
  ```yaml
109
109
  - repo: https://github.com/stefmolin/docstringify
110
- rev: 0.7.0
110
+ rev: 1.0.0
111
111
  hooks:
112
112
  - id: docstringify
113
113
  ```
@@ -116,17 +116,17 @@ By default, all docstrings are required. If you want to be more lenient, you can
116
116
 
117
117
  ```yaml
118
118
  - repo: https://github.com/stefmolin/docstringify
119
- rev: 0.7.0
119
+ rev: 1.0.0
120
120
  hooks:
121
121
  - id: docstringify
122
122
  args: [--threshold=0.75]
123
123
  ```
124
124
 
125
- If you would like to see suggested docstring templates (inferred from type annotations for functions and methods), provide the `--suggest-changes` argument, along with the docstring style you want to use (options are `google`, `numpydoc`, and `stub`). Here, we ask for [numpydoc-style docstring](https://numpydoc.readthedocs.io/en/latest/format.html#) suggestions:
125
+ If you would like to see suggested docstring templates (inferred from type annotations for functions and methods), provide the `--suggest-changes` argument, along with the docstring style you want to use (options are `google`, `numpydoc`, and `stub`). Here, we ask for stub suggestions (just single lines of `"""__description__"""`):
126
126
 
127
127
  ```yaml
128
128
  - repo: https://github.com/stefmolin/docstringify
129
- rev: 0.7.0
129
+ rev: 1.0.0
130
130
  hooks:
131
131
  - id: docstringify
132
132
  args: [--suggest-changes=numpydoc]
@@ -136,13 +136,21 @@ Use `--make-changes` to create a copy of each file with docstring templates. Her
136
136
 
137
137
  ```yaml
138
138
  - repo: https://github.com/stefmolin/docstringify
139
- rev: 0.7.0
139
+ rev: 1.0.0
140
140
  hooks:
141
141
  - id: docstringify
142
142
  args: [--make-changes=google]
143
143
  ```
144
144
 
145
- If you want the changes to be made in place, change `--make-changes` to `--make-changes-inplace` &ndash; make sure you only operate on files that are in version control with this setting. Note that the resulting format of your file may be a little different (spacing, newlines, *etc.*).
145
+ If you want the changes to be made in place, change `--make-changes` to `--make-changes-inplace` &ndash; make sure you only operate on files that are in version control with this setting. Here, we ask for [numpydoc-style docstring](https://numpydoc.readthedocs.io/en/latest/format.html#) suggestions:
146
+
147
+ ```yaml
148
+ - repo: https://github.com/stefmolin/docstringify
149
+ rev: 1.0.0
150
+ hooks:
151
+ - id: docstringify
152
+ args: [--make-changes-inplace=numpydoc]
153
+ ```
146
154
 
147
155
  Be sure to check out the [pre-commit documentation](https://pre-commit.com/#pre-commit-configyaml---hooks) for additional configuration options.
148
156
 
@@ -216,7 +224,19 @@ test.say_hello is missing a docstring
216
224
  Docstring templates written to /.../test_docstringify.py
217
225
  ```
218
226
 
219
- If you want to overwrite the file with the edits, pass `overwrite=True` to `DocstringTransformer()`.
227
+ If you want to overwrite the file with the edits, pass `overwrite=True` to `DocstringTransformer()`:
228
+
229
+ ```pycon
230
+ >>> from docstringify.converters import GoogleDocstringConverter
231
+ >>> from docstringify.traversal import DocstringTransformer
232
+ >>> transformer = DocstringTransformer(
233
+ ... 'test.py', converter=GoogleDocstringConverter, overwrite=True
234
+ ... )
235
+ >>> transformer.process_file()
236
+ test is missing a docstring
237
+ test.say_hello is missing a docstring
238
+ Docstring templates written to /.../test.py
239
+ ```
220
240
 
221
241
  ## Contributing
222
242
 
@@ -1,6 +0,0 @@
1
- """Docstringify exceptions."""
2
-
3
-
4
- class InvalidDocstringError(ValueError):
5
- def __init__(self, docstring_class: str) -> None:
6
- super().__init__(f'Expected str or list[str] docstring, got {docstring_class}')
File without changes
File without changes
File without changes