format-docstring 0.2.4__tar.gz → 0.2.6__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.
- {format_docstring-0.2.4 → format_docstring-0.2.6}/.gitignore +3 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/CHANGELOG.md +21 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/PKG-INFO +1 -1
- {format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring/docstring_rewriter.py +9 -2
- {format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring/line_wrap_numpy.py +103 -25
- {format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring.egg-info/PKG-INFO +1 -1
- {format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring.egg-info/SOURCES.txt +3 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/pyproject.toml +1 -1
- format_docstring-0.2.6/tests/test_data/end_to_end/numpy/rST_cross_reference.txt +72 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/signature_sync_yields.txt +49 -1
- format_docstring-0.2.6/tests/test_data/end_to_end/numpy/variadic_signature_without_colon.txt +58 -0
- format_docstring-0.2.6/tests/test_data/line_wrap/numpy/variadic_signature_without_colon.txt +38 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_docstring_rewriter.py +52 -4
- {format_docstring-0.2.4 → format_docstring-0.2.6}/.github/workflows/python-package.yml +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/.github/workflows/python-publish.yml +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/.pre-commit-config.yaml +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/.pre-commit-hooks.yaml +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/AGENTS.md +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/LICENSE +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/README.md +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring/__init__.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring/base_fixer.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring/config.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring/line_wrap_google.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring/line_wrap_utils.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring/main_jupyter.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring/main_py.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring.egg-info/dependency_links.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring.egg-info/entry_points.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring.egg-info/requires.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring.egg-info/top_level.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/muff.toml +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/requirements.dev +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/setup.cfg +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/__init__.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/helpers.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_base_fixer.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_config.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/README.md +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/arg_name_is_default.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/colon_spacing_fix.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/contents_that_are_not_wrapped.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/default_value_standardization.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/empty_lines_are_respected.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/examples_section.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/existing_linebreaks_should_not_be_respected.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/fix_rst_backticks.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/four_level_nested_classes.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/indent_four_levels_16_spaces_width_10.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/indent_misaligned_all.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/indent_two_levels_8_spaces.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/line_length_2.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/mismatched_underlines.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/mismatched_underlines_one_dash.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/mismatched_underlines_two_dashes.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/module_level_docstring.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/new_lines_before_and_after.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/no_format_docstring_comment.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/param_signature_without_type.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/parameters_returns_raises_wrapping.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/returns_signature_and_description.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/section_headings_with_colons.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/section_title_fixed.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/sections_notes_examples.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/signature_dont_sync_raises.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/signature_line_is_not_wrapped.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/signature_sync_class_docstrings.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/signature_sync_parameters.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/signature_sync_returns.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/single_line_docstring.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/texts_are_rewrapped.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/very_long_unbreakable_word.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/integration_test/numpy/after.ipynb +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/integration_test/numpy/after.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/integration_test/numpy/after_50.ipynb +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/integration_test/numpy/after_50.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/integration_test/numpy/before.ipynb +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/integration_test/numpy/before.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/jupyter/before.ipynb +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/jupyter/verbose_before.ipynb +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/README.md +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/colon_spacing_fix.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/contents_that_are_not_wrapped.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/default_value_standardization.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/empty_lines_are_respected.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/examples_section.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/existing_linebreaks_should_not_be_respected.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/fix_rst_backticks.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/indent_four_levels_16_spaces_width_10.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/indent_two_levels_8_spaces.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/line_length_2.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/mismatched_underlines.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/mismatched_underlines_one_dash.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/mismatched_underlines_two_dashes.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/module_level_docstring.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/param_signature_without_type.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/parameters_returns_raises_wrapping.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/returns_signature_and_description.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/section_headings_with_colons.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/section_title_fixed.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/sections_notes_examples.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/signature_line_is_not_wrapped.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/texts_are_rewrapped.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/very_long_unbreakable_word.txt +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/playground.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_line_wrap_google.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_line_wrap_numpy.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_line_wrap_utils.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_main_jupyter.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_main_py.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_playground.py +0 -0
- {format_docstring-0.2.4 → format_docstring-0.2.6}/tox.ini +0 -0
|
@@ -6,6 +6,27 @@ The format is based on
|
|
|
6
6
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
7
7
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [0.2.6] - 2025-11-21
|
|
10
|
+
|
|
11
|
+
- Fixed
|
|
12
|
+
- Multiline annotations now normalize without inserting spaces inside
|
|
13
|
+
`Literal[...]` or other bracketed signatures, even when they span several
|
|
14
|
+
lines.
|
|
15
|
+
- A bug where bare `*args`/`**kwargs` signature lines (typed or untyped)
|
|
16
|
+
would be merged into previous descriptions
|
|
17
|
+
- NumPy signature syncing left the `:class:` / `:meth:` role prefixes behind,
|
|
18
|
+
producing mismatched annotations like ` : MyClass`
|
|
19
|
+
- Full diff
|
|
20
|
+
- https://github.com/jsh9/format-docstring/compare/0.2.5...0.2.6
|
|
21
|
+
|
|
22
|
+
## [0.2.5] - 2025-11-20
|
|
23
|
+
|
|
24
|
+
- Fixed
|
|
25
|
+
- A bug where `Generator[XXX, YYY, ZZZ]` in the return type annotation is not
|
|
26
|
+
parsed correctly (the Yields section should have been XXX)
|
|
27
|
+
- Full diff
|
|
28
|
+
- https://github.com/jsh9/format-docstring/compare/0.2.4...0.2.5
|
|
29
|
+
|
|
9
30
|
## [0.2.4] - 2025-10-27
|
|
10
31
|
|
|
11
32
|
- Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: format-docstring
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: A Python formatter to wrap/adjust docstring lines
|
|
5
5
|
Author-email: jsh9 <25124332+jsh9@users.noreply.github.com>
|
|
6
6
|
Maintainer-email: jsh9 <25124332+jsh9@users.noreply.github.com>
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import ast
|
|
4
4
|
import io
|
|
5
5
|
import operator
|
|
6
|
+
import textwrap
|
|
6
7
|
import tokenize
|
|
7
8
|
from typing import TYPE_CHECKING
|
|
8
9
|
|
|
@@ -83,12 +84,18 @@ def _normalize_signature_segment(segment: str | None) -> str | None:
|
|
|
83
84
|
|
|
84
85
|
normalized: str = segment.strip()
|
|
85
86
|
if '\n' in normalized or '\r' in normalized or '\t' in normalized:
|
|
87
|
+
dedented: str = textwrap.dedent(normalized)
|
|
88
|
+
# Wrap in parentheses so unions split across lines
|
|
89
|
+
# (e.g. ``Literal[...] | None``)
|
|
90
|
+
# remain valid ``eval`` expressions even when indentation is uneven.
|
|
91
|
+
wrapped_for_parse = f'({dedented})'
|
|
92
|
+
|
|
86
93
|
# `ast.unparse(ast.parse(...))` neatly flattens whitespace but it also
|
|
87
94
|
# canonicalises string quotes to single quotes. We still rely on it for
|
|
88
95
|
# whitespace normalization, so capture its output first.
|
|
89
96
|
try:
|
|
90
|
-
canonical = ast.unparse(ast.parse(
|
|
91
|
-
except (SyntaxError, ValueError):
|
|
97
|
+
canonical = ast.unparse(ast.parse(wrapped_for_parse, mode='eval'))
|
|
98
|
+
except (SyntaxError, ValueError, IndentationError):
|
|
92
99
|
return ' '.join(normalized.split())
|
|
93
100
|
|
|
94
101
|
# Remember the exact string literal tokens from the original text. The
|
|
@@ -170,18 +170,22 @@ def wrap_docstring_numpy( # noqa: C901, PLR0915, TODO: https://github.com/jsh9/
|
|
|
170
170
|
# section (indentation < 4). This prevents mis-detecting
|
|
171
171
|
# description lines that happen to contain a colon (e.g., tables,
|
|
172
172
|
# examples, notes) as new parameter signatures.
|
|
173
|
-
if
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
173
|
+
if leading_indent is None or indent_length <= leading_indent:
|
|
174
|
+
if _is_param_signature(line):
|
|
175
|
+
fixed_line = _fix_colon_spacing(line)
|
|
176
|
+
fixed_line = _standardize_default_value(fixed_line)
|
|
177
|
+
fixed_line = _rewrite_parameter_signature(
|
|
178
|
+
fixed_line, metadata_for_section
|
|
179
|
+
)
|
|
180
|
+
fixed_line = _standardize_default_value(fixed_line)
|
|
181
|
+
temp_out.append(fixed_line)
|
|
182
|
+
i += 1
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
if _is_bare_variadic_signature(line):
|
|
186
|
+
temp_out.append(line)
|
|
187
|
+
i += 1
|
|
188
|
+
continue
|
|
185
189
|
|
|
186
190
|
# Description lines (typically indented): wrap if too long
|
|
187
191
|
collect_to_temp_output(temp_out, line)
|
|
@@ -197,6 +201,7 @@ def wrap_docstring_numpy( # noqa: C901, PLR0915, TODO: https://github.com/jsh9/
|
|
|
197
201
|
|
|
198
202
|
# Treat top-level lines as signatures
|
|
199
203
|
if leading_indent is None or indent_length <= leading_indent:
|
|
204
|
+
is_yields_section = section_lower_case.startswith('yield')
|
|
200
205
|
if not return_signature_style_determined:
|
|
201
206
|
return_use_multiple_signatures = (
|
|
202
207
|
_detect_multiple_return_signatures(
|
|
@@ -223,6 +228,12 @@ def wrap_docstring_numpy( # noqa: C901, PLR0915, TODO: https://github.com/jsh9/
|
|
|
223
228
|
# Fallback to last component when docstring expects more
|
|
224
229
|
desired_annotation = return_components[-1]
|
|
225
230
|
|
|
231
|
+
if is_yields_section:
|
|
232
|
+
desired_annotation = (
|
|
233
|
+
_unwrap_generator_annotation(desired_annotation)
|
|
234
|
+
or desired_annotation
|
|
235
|
+
)
|
|
236
|
+
|
|
226
237
|
if desired_annotation is None:
|
|
227
238
|
temp_out.append(line)
|
|
228
239
|
i += 1
|
|
@@ -334,6 +345,13 @@ _PARAM_SIGNATURE_RE = re.compile(
|
|
|
334
345
|
rf'^\s*\*{{0,2}}{START}{CONT}*(?:\s*,\s*\*{{0,2}}{START}{CONT}*)*\s*:\s*.*$'
|
|
335
346
|
)
|
|
336
347
|
|
|
348
|
+
# Matches bare variadic signatures without a colon, e.g. ``**kwargs`` or
|
|
349
|
+
# ``*args, **kwargs``. These should be treated like signatures so description
|
|
350
|
+
# text doesn't get collapsed into the preceding entry.
|
|
351
|
+
_BARE_VARIADIC_SIGNATURE_RE = re.compile(
|
|
352
|
+
rf'^\s*\*{{1,2}}{START}{CONT}*(?:\s*,\s*\*{{1,2}}{START}{CONT}*)*\s*$'
|
|
353
|
+
)
|
|
354
|
+
|
|
337
355
|
|
|
338
356
|
def _is_param_signature(text: str) -> bool:
|
|
339
357
|
r"""
|
|
@@ -364,6 +382,16 @@ def _is_param_signature(text: str) -> bool:
|
|
|
364
382
|
return bool(_PARAM_SIGNATURE_RE.match(text))
|
|
365
383
|
|
|
366
384
|
|
|
385
|
+
def _is_bare_variadic_signature(text: str) -> bool:
|
|
386
|
+
"""
|
|
387
|
+
Return True for variadic parameter lines lacking ``:`` annotations.
|
|
388
|
+
|
|
389
|
+
Handles stripped signatures such as ``**kwargs`` so they are preserved as
|
|
390
|
+
their own logical entries inside ``Parameters`` sections.
|
|
391
|
+
"""
|
|
392
|
+
return bool(_BARE_VARIADIC_SIGNATURE_RE.match(text))
|
|
393
|
+
|
|
394
|
+
|
|
367
395
|
def _fix_colon_spacing(line: str) -> str:
|
|
368
396
|
"""
|
|
369
397
|
Fix spacing around colons in parameter signature lines.
|
|
@@ -618,19 +646,6 @@ def _split_tuple_annotation(annotation: str | None) -> list[str] | None:
|
|
|
618
646
|
except (SyntaxError, ValueError):
|
|
619
647
|
return None
|
|
620
648
|
|
|
621
|
-
def _name_of(node: ast.AST) -> str | None:
|
|
622
|
-
if isinstance(node, ast.Name):
|
|
623
|
-
return node.id
|
|
624
|
-
|
|
625
|
-
if isinstance(node, ast.Attribute):
|
|
626
|
-
base = _name_of(node.value)
|
|
627
|
-
if base is None:
|
|
628
|
-
return None
|
|
629
|
-
|
|
630
|
-
return f'{base}.{node.attr}'
|
|
631
|
-
|
|
632
|
-
return None
|
|
633
|
-
|
|
634
649
|
if isinstance(expr, ast.Subscript):
|
|
635
650
|
base_name = _name_of(expr.value)
|
|
636
651
|
if base_name not in {'tuple', 'Tuple'}:
|
|
@@ -657,6 +672,63 @@ def _split_tuple_annotation(annotation: str | None) -> list[str] | None:
|
|
|
657
672
|
return None
|
|
658
673
|
|
|
659
674
|
|
|
675
|
+
def _name_of(node: ast.AST) -> str | None:
|
|
676
|
+
"""
|
|
677
|
+
Return the dotted name represented by ``node`` if possible.
|
|
678
|
+
"""
|
|
679
|
+
if isinstance(node, ast.Name):
|
|
680
|
+
return node.id
|
|
681
|
+
|
|
682
|
+
if isinstance(node, ast.Attribute):
|
|
683
|
+
base = _name_of(node.value)
|
|
684
|
+
if base is None:
|
|
685
|
+
return None
|
|
686
|
+
|
|
687
|
+
return f'{base}.{node.attr}'
|
|
688
|
+
|
|
689
|
+
return None
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
def _unwrap_generator_annotation(annotation: str | None) -> str | None:
|
|
693
|
+
"""
|
|
694
|
+
Return the first yield type when ``annotation`` is a Generator or
|
|
695
|
+
AsyncGenerator.
|
|
696
|
+
|
|
697
|
+
This is a small helper to keep ``Yields`` sections intuitive; Python
|
|
698
|
+
signatures often annotate generator functions as ``Generator[T, None,
|
|
699
|
+
None]`` but docstrings should spell out the yielded type ``T`` instead of
|
|
700
|
+
the whole container.
|
|
701
|
+
"""
|
|
702
|
+
if annotation is None:
|
|
703
|
+
return None
|
|
704
|
+
|
|
705
|
+
try:
|
|
706
|
+
expr = ast.parse(annotation, mode='eval').body
|
|
707
|
+
except (SyntaxError, ValueError):
|
|
708
|
+
return None
|
|
709
|
+
|
|
710
|
+
if not isinstance(expr, ast.Subscript):
|
|
711
|
+
return None
|
|
712
|
+
|
|
713
|
+
base_name = _name_of(expr.value)
|
|
714
|
+
if base_name is None or base_name.split('.')[-1] not in {
|
|
715
|
+
'Generator',
|
|
716
|
+
'AsyncGenerator',
|
|
717
|
+
}:
|
|
718
|
+
return None
|
|
719
|
+
|
|
720
|
+
slice_node = expr.slice
|
|
721
|
+
if not isinstance(slice_node, ast.Tuple) or not slice_node.elts:
|
|
722
|
+
return None
|
|
723
|
+
|
|
724
|
+
first = slice_node.elts[0]
|
|
725
|
+
segment = ast.get_source_segment(annotation, first)
|
|
726
|
+
if segment is None:
|
|
727
|
+
segment = ast.unparse(first)
|
|
728
|
+
|
|
729
|
+
return segment.strip()
|
|
730
|
+
|
|
731
|
+
|
|
660
732
|
def _detect_multiple_return_signatures(
|
|
661
733
|
lines: list[str],
|
|
662
734
|
start_idx: int,
|
|
@@ -697,6 +769,12 @@ def _rewrite_return_signature(line: str, annotation: str) -> str:
|
|
|
697
769
|
colon_idx = stripped.find(':')
|
|
698
770
|
if colon_idx != -1:
|
|
699
771
|
name = stripped[:colon_idx].rstrip()
|
|
772
|
+
# Only treat the colon as a signature separator if something precedes
|
|
773
|
+
# it. rST cross references such as ``:class:`Foo``` start with a colon,
|
|
774
|
+
# in which case we just want to output the synced annotation.
|
|
775
|
+
if not name:
|
|
776
|
+
return f'{indent}{annotation}'
|
|
777
|
+
|
|
700
778
|
return f'{indent}{name} : {annotation}'
|
|
701
779
|
|
|
702
780
|
return f'{indent}{annotation}'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: format-docstring
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: A Python formatter to wrap/adjust docstring lines
|
|
5
5
|
Author-email: jsh9 <25124332+jsh9@users.noreply.github.com>
|
|
6
6
|
Maintainer-email: jsh9 <25124332+jsh9@users.noreply.github.com>
|
|
@@ -60,6 +60,7 @@ tests/test_data/end_to_end/numpy/new_lines_before_and_after.txt
|
|
|
60
60
|
tests/test_data/end_to_end/numpy/no_format_docstring_comment.txt
|
|
61
61
|
tests/test_data/end_to_end/numpy/param_signature_without_type.txt
|
|
62
62
|
tests/test_data/end_to_end/numpy/parameters_returns_raises_wrapping.txt
|
|
63
|
+
tests/test_data/end_to_end/numpy/rST_cross_reference.txt
|
|
63
64
|
tests/test_data/end_to_end/numpy/returns_signature_and_description.txt
|
|
64
65
|
tests/test_data/end_to_end/numpy/section_headings_with_colons.txt
|
|
65
66
|
tests/test_data/end_to_end/numpy/section_title_fixed.txt
|
|
@@ -72,6 +73,7 @@ tests/test_data/end_to_end/numpy/signature_sync_returns.txt
|
|
|
72
73
|
tests/test_data/end_to_end/numpy/signature_sync_yields.txt
|
|
73
74
|
tests/test_data/end_to_end/numpy/single_line_docstring.txt
|
|
74
75
|
tests/test_data/end_to_end/numpy/texts_are_rewrapped.txt
|
|
76
|
+
tests/test_data/end_to_end/numpy/variadic_signature_without_colon.txt
|
|
75
77
|
tests/test_data/end_to_end/numpy/very_long_unbreakable_word.txt
|
|
76
78
|
tests/test_data/integration_test/numpy/after.ipynb
|
|
77
79
|
tests/test_data/integration_test/numpy/after.py
|
|
@@ -104,4 +106,5 @@ tests/test_data/line_wrap/numpy/section_title_fixed.txt
|
|
|
104
106
|
tests/test_data/line_wrap/numpy/sections_notes_examples.txt
|
|
105
107
|
tests/test_data/line_wrap/numpy/signature_line_is_not_wrapped.txt
|
|
106
108
|
tests/test_data/line_wrap/numpy/texts_are_rewrapped.txt
|
|
109
|
+
tests/test_data/line_wrap/numpy/variadic_signature_without_colon.txt
|
|
107
110
|
tests/test_data/line_wrap/numpy/very_long_unbreakable_word.txt
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
LINE_LENGTH: 79
|
|
2
|
+
|
|
3
|
+
**********
|
|
4
|
+
|
|
5
|
+
def myfunc() -> MyClass:
|
|
6
|
+
"""
|
|
7
|
+
Do something
|
|
8
|
+
|
|
9
|
+
Returns
|
|
10
|
+
-------
|
|
11
|
+
:class:`MyClass`
|
|
12
|
+
An instance of MyClass
|
|
13
|
+
"""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def uses_cross_refs(
|
|
18
|
+
thing: MyClass,
|
|
19
|
+
creator: Callable[[MyClass], MyClass],
|
|
20
|
+
) -> MyClass:
|
|
21
|
+
"""
|
|
22
|
+
Uses parameters and returns that reference rST roles.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
thing : :class:`MyClass`
|
|
27
|
+
Object to transform.
|
|
28
|
+
creator : :meth:`MyClass.build`
|
|
29
|
+
Callback that builds a new instance.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
:meth:`MyClass.build`
|
|
34
|
+
A created instance.
|
|
35
|
+
"""
|
|
36
|
+
return creator(thing)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
**********
|
|
40
|
+
|
|
41
|
+
def myfunc() -> MyClass:
|
|
42
|
+
"""
|
|
43
|
+
Do something
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
MyClass
|
|
48
|
+
An instance of MyClass
|
|
49
|
+
"""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def uses_cross_refs(
|
|
54
|
+
thing: MyClass,
|
|
55
|
+
creator: Callable[[MyClass], MyClass],
|
|
56
|
+
) -> MyClass:
|
|
57
|
+
"""
|
|
58
|
+
Uses parameters and returns that reference rST roles.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
thing : MyClass
|
|
63
|
+
Object to transform.
|
|
64
|
+
creator : Callable[[MyClass], MyClass]
|
|
65
|
+
Callback that builds a new instance.
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
MyClass
|
|
70
|
+
A created instance.
|
|
71
|
+
"""
|
|
72
|
+
return creator(thing)
|
|
@@ -51,6 +51,30 @@ def yield_custom_type_2() -> Iterator[tuple['MyType1', "MyType2"]]:
|
|
|
51
51
|
yield 'value'
|
|
52
52
|
yield 1
|
|
53
53
|
|
|
54
|
+
|
|
55
|
+
def yield_middle_non_none() -> Generator[int, str, None]:
|
|
56
|
+
"""
|
|
57
|
+
Yield with send type.
|
|
58
|
+
|
|
59
|
+
Yields
|
|
60
|
+
------
|
|
61
|
+
str
|
|
62
|
+
Should match first annotation element, not send type.
|
|
63
|
+
"""
|
|
64
|
+
yield 1
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def yield_last_non_none() -> Generator[int, None, str]:
|
|
68
|
+
"""
|
|
69
|
+
Yield with return type.
|
|
70
|
+
|
|
71
|
+
Yields
|
|
72
|
+
------
|
|
73
|
+
str
|
|
74
|
+
Should match first annotation element, not return type.
|
|
75
|
+
"""
|
|
76
|
+
yield 1
|
|
77
|
+
|
|
54
78
|
**********
|
|
55
79
|
from collections.abc import Generator, Iterator
|
|
56
80
|
|
|
@@ -73,7 +97,7 @@ def yield_named() -> Generator[int, None, None]:
|
|
|
73
97
|
|
|
74
98
|
Yields
|
|
75
99
|
------
|
|
76
|
-
result :
|
|
100
|
+
result : int
|
|
77
101
|
Should match annotation.
|
|
78
102
|
"""
|
|
79
103
|
yield 1
|
|
@@ -101,3 +125,27 @@ def yield_custom_type_2() -> Iterator[tuple['MyType1', "MyType2"]]:
|
|
|
101
125
|
"""
|
|
102
126
|
yield 'value'
|
|
103
127
|
yield 1
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def yield_middle_non_none() -> Generator[int, str, None]:
|
|
131
|
+
"""
|
|
132
|
+
Yield with send type.
|
|
133
|
+
|
|
134
|
+
Yields
|
|
135
|
+
------
|
|
136
|
+
int
|
|
137
|
+
Should match first annotation element, not send type.
|
|
138
|
+
"""
|
|
139
|
+
yield 1
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def yield_last_non_none() -> Generator[int, None, str]:
|
|
143
|
+
"""
|
|
144
|
+
Yield with return type.
|
|
145
|
+
|
|
146
|
+
Yields
|
|
147
|
+
------
|
|
148
|
+
int
|
|
149
|
+
Should match first annotation element, not return type.
|
|
150
|
+
"""
|
|
151
|
+
yield 1
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
LINE_LENGTH: 75
|
|
2
|
+
|
|
3
|
+
**********
|
|
4
|
+
def function_with_variadics():
|
|
5
|
+
"""
|
|
6
|
+
Parameters
|
|
7
|
+
----------
|
|
8
|
+
arg1 : str
|
|
9
|
+
Something short
|
|
10
|
+
**kwargs
|
|
11
|
+
Keyword args that should remain on their own entry.
|
|
12
|
+
*custom_args
|
|
13
|
+
Positional extras that should wrap to their own block even without a colon.
|
|
14
|
+
**some_other_strange_name
|
|
15
|
+
Keyword extras that likewise need wrapping to stay with their entry.
|
|
16
|
+
*explicit_typed_args : tuple[int, ...]
|
|
17
|
+
Shows a variadic entry that still provides a type annotation.
|
|
18
|
+
**typed_kwargs : dict[str, int]
|
|
19
|
+
Another variadic keyword argument that uses a type hint.
|
|
20
|
+
"""
|
|
21
|
+
return (
|
|
22
|
+
arg1,
|
|
23
|
+
kwargs,
|
|
24
|
+
custom_args,
|
|
25
|
+
some_other_strange_name,
|
|
26
|
+
explicit_typed_args,
|
|
27
|
+
typed_kwargs,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
**********
|
|
31
|
+
|
|
32
|
+
def function_with_variadics():
|
|
33
|
+
"""
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
arg1 : str
|
|
37
|
+
Something short
|
|
38
|
+
**kwargs
|
|
39
|
+
Keyword args that should remain on their own entry.
|
|
40
|
+
*custom_args
|
|
41
|
+
Positional extras that should wrap to their own block even without
|
|
42
|
+
a colon.
|
|
43
|
+
**some_other_strange_name
|
|
44
|
+
Keyword extras that likewise need wrapping to stay with their
|
|
45
|
+
entry.
|
|
46
|
+
*explicit_typed_args : tuple[int, ...]
|
|
47
|
+
Shows a variadic entry that still provides a type annotation.
|
|
48
|
+
**typed_kwargs : dict[str, int]
|
|
49
|
+
Another variadic keyword argument that uses a type hint.
|
|
50
|
+
"""
|
|
51
|
+
return (
|
|
52
|
+
arg1,
|
|
53
|
+
kwargs,
|
|
54
|
+
custom_args,
|
|
55
|
+
some_other_strange_name,
|
|
56
|
+
explicit_typed_args,
|
|
57
|
+
typed_kwargs,
|
|
58
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
LINE_LENGTH: 55
|
|
2
|
+
|
|
3
|
+
**********
|
|
4
|
+
Parameters
|
|
5
|
+
----------
|
|
6
|
+
arg1 : str
|
|
7
|
+
Something short
|
|
8
|
+
**kwargs
|
|
9
|
+
Keyword args that should remain on their own entry.
|
|
10
|
+
*custom_args
|
|
11
|
+
Positional extras that should wrap to their own block even without a colon.
|
|
12
|
+
**some_other_strange_name
|
|
13
|
+
Keyword extras that likewise need wrapping to stay with their entry.
|
|
14
|
+
*explicit_typed_args : tuple[int, ...]
|
|
15
|
+
Shows a variadic entry that still provides a type annotation.
|
|
16
|
+
**typed_kwargs : dict[str, int]
|
|
17
|
+
Another variadic keyword argument that uses a type hint.
|
|
18
|
+
|
|
19
|
+
**********
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
arg1 : str
|
|
24
|
+
Something short
|
|
25
|
+
**kwargs
|
|
26
|
+
Keyword args that should remain on their own entry.
|
|
27
|
+
*custom_args
|
|
28
|
+
Positional extras that should wrap to their own
|
|
29
|
+
block even without a colon.
|
|
30
|
+
**some_other_strange_name
|
|
31
|
+
Keyword extras that likewise need wrapping to stay
|
|
32
|
+
with their entry.
|
|
33
|
+
*explicit_typed_args : tuple[int, ...]
|
|
34
|
+
Shows a variadic entry that still provides a type
|
|
35
|
+
annotation.
|
|
36
|
+
**typed_kwargs : dict[str, int]
|
|
37
|
+
Another variadic keyword argument that uses a type
|
|
38
|
+
hint.
|
|
@@ -4,7 +4,6 @@ from textwrap import dedent
|
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
7
|
-
import format_docstring.docstring_rewriter
|
|
8
7
|
from format_docstring import docstring_rewriter
|
|
9
8
|
|
|
10
9
|
|
|
@@ -57,6 +56,57 @@ def test_rebuild_literal(literal: str, content: str, expected: str) -> None:
|
|
|
57
56
|
assert docstring_rewriter.rebuild_literal(literal, content) == expected
|
|
58
57
|
|
|
59
58
|
|
|
59
|
+
@pytest.mark.parametrize(
|
|
60
|
+
('segment', 'expected'),
|
|
61
|
+
[
|
|
62
|
+
(
|
|
63
|
+
dedent(
|
|
64
|
+
"""
|
|
65
|
+
Literal[
|
|
66
|
+
'auto', 'default', 'flex', 'scale', 'priority'
|
|
67
|
+
]
|
|
68
|
+
| None
|
|
69
|
+
| NotGiven
|
|
70
|
+
"""
|
|
71
|
+
),
|
|
72
|
+
"Literal['auto', 'default', 'flex', 'scale', 'priority'] | None | NotGiven", # noqa: E501
|
|
73
|
+
),
|
|
74
|
+
(
|
|
75
|
+
dedent(
|
|
76
|
+
"""
|
|
77
|
+
Optional[
|
|
78
|
+
'Widget'
|
|
79
|
+
]
|
|
80
|
+
| None
|
|
81
|
+
"""
|
|
82
|
+
),
|
|
83
|
+
"Optional['Widget'] | None",
|
|
84
|
+
),
|
|
85
|
+
(
|
|
86
|
+
dedent(
|
|
87
|
+
"""
|
|
88
|
+
tuple[
|
|
89
|
+
dict[str, int],
|
|
90
|
+
list[
|
|
91
|
+
tuple[str, float]
|
|
92
|
+
]
|
|
93
|
+
]
|
|
94
|
+
"""
|
|
95
|
+
),
|
|
96
|
+
'tuple[dict[str, int], list[tuple[str, float]]]',
|
|
97
|
+
),
|
|
98
|
+
],
|
|
99
|
+
)
|
|
100
|
+
def test_normalize_signature_segment_multiline_cases(
|
|
101
|
+
segment: str, expected: str
|
|
102
|
+
) -> None:
|
|
103
|
+
"""
|
|
104
|
+
Multiline annotations should normalize without inserting bracket gaps.
|
|
105
|
+
"""
|
|
106
|
+
normalized = docstring_rewriter._normalize_signature_segment(segment) # noqa: SLF001
|
|
107
|
+
assert normalized == expected
|
|
108
|
+
|
|
109
|
+
|
|
60
110
|
@pytest.mark.parametrize(
|
|
61
111
|
('src', 'selector', 'has_doc'),
|
|
62
112
|
[
|
|
@@ -232,9 +282,7 @@ def test_wrap_docstring_numpy_parameters_and_examples() -> None:
|
|
|
232
282
|
""" # noqa: E501
|
|
233
283
|
).strip('\n')
|
|
234
284
|
|
|
235
|
-
wrapped =
|
|
236
|
-
doc, line_length=79
|
|
237
|
-
)
|
|
285
|
+
wrapped = docstring_rewriter.wrap_docstring(doc, line_length=79)
|
|
238
286
|
|
|
239
287
|
temp: str = 'very very very very very very very very very very'
|
|
240
288
|
|
|
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
|
{format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{format_docstring-0.2.4 → format_docstring-0.2.6}/format_docstring.egg-info/entry_points.txt
RENAMED
|
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
|
{format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/README.md
RENAMED
|
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
|
{format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/end_to_end/numpy/line_length_2.txt
RENAMED
|
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
|
{format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/integration_test/numpy/after.ipynb
RENAMED
|
File without changes
|
{format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/integration_test/numpy/after.py
RENAMED
|
File without changes
|
|
File without changes
|
{format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/integration_test/numpy/after_50.py
RENAMED
|
File without changes
|
|
File without changes
|
{format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/integration_test/numpy/before.py
RENAMED
|
File without changes
|
|
File without changes
|
{format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/jupyter/verbose_before.ipynb
RENAMED
|
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
|
{format_docstring-0.2.4 → format_docstring-0.2.6}/tests/test_data/line_wrap/numpy/line_length_2.txt
RENAMED
|
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
|