format-docstring 0.2.0__tar.gz → 0.2.2__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 (108) hide show
  1. {format_docstring-0.2.0 → format_docstring-0.2.2}/.pre-commit-config.yaml +5 -0
  2. {format_docstring-0.2.0 → format_docstring-0.2.2}/AGENTS.md +13 -8
  3. {format_docstring-0.2.0 → format_docstring-0.2.2}/CHANGELOG.md +14 -0
  4. {format_docstring-0.2.0 → format_docstring-0.2.2}/PKG-INFO +1 -1
  5. {format_docstring-0.2.0 → format_docstring-0.2.2}/format_docstring/base_fixer.py +1 -1
  6. {format_docstring-0.2.0 → format_docstring-0.2.2}/format_docstring/docstring_rewriter.py +65 -1
  7. {format_docstring-0.2.0 → format_docstring-0.2.2}/format_docstring/line_wrap_google.py +1 -0
  8. {format_docstring-0.2.0 → format_docstring-0.2.2}/format_docstring/line_wrap_numpy.py +27 -4
  9. {format_docstring-0.2.0 → format_docstring-0.2.2}/format_docstring.egg-info/PKG-INFO +1 -1
  10. {format_docstring-0.2.0 → format_docstring-0.2.2}/format_docstring.egg-info/SOURCES.txt +5 -2
  11. {format_docstring-0.2.0 → format_docstring-0.2.2}/pyproject.toml +1 -1
  12. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/fix_rst_backticks.txt +1 -1
  13. format_docstring-0.2.2/tests/test_data/end_to_end/numpy/signature_dont_sync_raises.txt +48 -0
  14. format_docstring-0.2.2/tests/test_data/end_to_end/numpy/signature_sync_class_docstrings.txt +203 -0
  15. format_docstring-0.2.0/tests/test_data/end_to_end/numpy/parameter_signature_sync.txt → format_docstring-0.2.2/tests/test_data/end_to_end/numpy/signature_sync_parameters.txt +57 -0
  16. format_docstring-0.2.0/tests/test_data/end_to_end/numpy/returns_signature_sync.txt → format_docstring-0.2.2/tests/test_data/end_to_end/numpy/signature_sync_returns.txt +57 -0
  17. format_docstring-0.2.2/tests/test_data/end_to_end/numpy/signature_sync_yields.txt +103 -0
  18. {format_docstring-0.2.0 → format_docstring-0.2.2}/.github/workflows/python-package.yml +0 -0
  19. {format_docstring-0.2.0 → format_docstring-0.2.2}/.github/workflows/python-publish.yml +0 -0
  20. {format_docstring-0.2.0 → format_docstring-0.2.2}/.gitignore +0 -0
  21. {format_docstring-0.2.0 → format_docstring-0.2.2}/.pre-commit-hooks.yaml +0 -0
  22. {format_docstring-0.2.0 → format_docstring-0.2.2}/LICENSE +0 -0
  23. {format_docstring-0.2.0 → format_docstring-0.2.2}/README.md +0 -0
  24. {format_docstring-0.2.0 → format_docstring-0.2.2}/format_docstring/__init__.py +0 -0
  25. {format_docstring-0.2.0 → format_docstring-0.2.2}/format_docstring/config.py +0 -0
  26. {format_docstring-0.2.0 → format_docstring-0.2.2}/format_docstring/line_wrap_utils.py +0 -0
  27. {format_docstring-0.2.0 → format_docstring-0.2.2}/format_docstring/main_jupyter.py +0 -0
  28. {format_docstring-0.2.0 → format_docstring-0.2.2}/format_docstring/main_py.py +0 -0
  29. {format_docstring-0.2.0 → format_docstring-0.2.2}/format_docstring.egg-info/dependency_links.txt +0 -0
  30. {format_docstring-0.2.0 → format_docstring-0.2.2}/format_docstring.egg-info/entry_points.txt +0 -0
  31. {format_docstring-0.2.0 → format_docstring-0.2.2}/format_docstring.egg-info/requires.txt +0 -0
  32. {format_docstring-0.2.0 → format_docstring-0.2.2}/format_docstring.egg-info/top_level.txt +0 -0
  33. {format_docstring-0.2.0 → format_docstring-0.2.2}/muff.toml +0 -0
  34. {format_docstring-0.2.0 → format_docstring-0.2.2}/requirements.dev +0 -0
  35. {format_docstring-0.2.0 → format_docstring-0.2.2}/setup.cfg +0 -0
  36. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/__init__.py +0 -0
  37. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/helpers.py +0 -0
  38. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_base_fixer.py +0 -0
  39. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_config.py +0 -0
  40. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/README.md +0 -0
  41. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/colon_spacing_fix.txt +0 -0
  42. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/contents_that_are_not_wrapped.txt +0 -0
  43. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/default_value_standardization.txt +0 -0
  44. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/empty_lines_are_respected.txt +0 -0
  45. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/examples_section.txt +0 -0
  46. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/existing_linebreaks_should_not_be_respected.txt +0 -0
  47. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/four_level_nested_classes.txt +0 -0
  48. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/indent_four_levels_16_spaces_width_10.txt +0 -0
  49. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/indent_misaligned_all.txt +0 -0
  50. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/indent_two_levels_8_spaces.txt +0 -0
  51. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/line_length_2.txt +0 -0
  52. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/mismatched_underlines.txt +0 -0
  53. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/mismatched_underlines_one_dash.txt +0 -0
  54. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/mismatched_underlines_two_dashes.txt +0 -0
  55. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/module_level_docstring.txt +0 -0
  56. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/new_lines_before_and_after.txt +0 -0
  57. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/no_format_docstring_comment.txt +0 -0
  58. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/param_signature_without_type.txt +0 -0
  59. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/parameters_returns_raises_wrapping.txt +0 -0
  60. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/returns_signature_and_description.txt +0 -0
  61. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/section_headings_with_colons.txt +0 -0
  62. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/section_title_fixed.txt +0 -0
  63. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/sections_notes_examples.txt +0 -0
  64. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/signature_line_is_not_wrapped.txt +0 -0
  65. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/single_line_docstring.txt +0 -0
  66. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/texts_are_rewrapped.txt +0 -0
  67. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/end_to_end/numpy/very_long_unbreakable_word.txt +0 -0
  68. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/integration_test/numpy/after.ipynb +0 -0
  69. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/integration_test/numpy/after.py +0 -0
  70. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/integration_test/numpy/after_50.ipynb +0 -0
  71. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/integration_test/numpy/after_50.py +0 -0
  72. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/integration_test/numpy/before.ipynb +0 -0
  73. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/integration_test/numpy/before.py +0 -0
  74. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/jupyter/before.ipynb +0 -0
  75. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/jupyter/verbose_before.ipynb +0 -0
  76. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/README.md +0 -0
  77. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/colon_spacing_fix.txt +0 -0
  78. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/contents_that_are_not_wrapped.txt +0 -0
  79. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/default_value_standardization.txt +0 -0
  80. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/empty_lines_are_respected.txt +0 -0
  81. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/examples_section.txt +0 -0
  82. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/existing_linebreaks_should_not_be_respected.txt +0 -0
  83. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/fix_rst_backticks.txt +0 -0
  84. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/indent_four_levels_16_spaces_width_10.txt +0 -0
  85. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/indent_two_levels_8_spaces.txt +0 -0
  86. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/line_length_2.txt +0 -0
  87. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/mismatched_underlines.txt +0 -0
  88. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/mismatched_underlines_one_dash.txt +0 -0
  89. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/mismatched_underlines_two_dashes.txt +0 -0
  90. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/module_level_docstring.txt +0 -0
  91. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/param_signature_without_type.txt +0 -0
  92. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/parameters_returns_raises_wrapping.txt +0 -0
  93. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/returns_signature_and_description.txt +0 -0
  94. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/section_headings_with_colons.txt +0 -0
  95. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/section_title_fixed.txt +0 -0
  96. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/sections_notes_examples.txt +0 -0
  97. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/signature_line_is_not_wrapped.txt +0 -0
  98. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/texts_are_rewrapped.txt +0 -0
  99. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/line_wrap/numpy/very_long_unbreakable_word.txt +0 -0
  100. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_data/playground.py +0 -0
  101. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_docstring_rewriter.py +0 -0
  102. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_line_wrap_google.py +0 -0
  103. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_line_wrap_numpy.py +0 -0
  104. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_line_wrap_utils.py +0 -0
  105. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_main_jupyter.py +0 -0
  106. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_main_py.py +0 -0
  107. {format_docstring-0.2.0 → format_docstring-0.2.2}/tests/test_playground.py +0 -0
  108. {format_docstring-0.2.0 → format_docstring-0.2.2}/tox.ini +0 -0
@@ -57,6 +57,11 @@ repos:
57
57
  rev: 0.0.11
58
58
  hooks:
59
59
  - id: markdown-toc-creator
60
+ - repo: https://github.com/jsh9/markdown-heading-numbering
61
+ rev: 0.1.0
62
+ hooks:
63
+ - id: markdown-heading-numbering
64
+ exclude: ^CHANGELOG\.md$
60
65
  - repo: local
61
66
  hooks:
62
67
  - id: format-docstring
@@ -3,7 +3,7 @@
3
3
  This guide briefs coding agents working on `format-docstring`. Use it to get
4
4
  oriented before making changes.
5
5
 
6
- ## Quick Snapshot
6
+ ## 1. Quick Snapshot
7
7
 
8
8
  - Formats NumPy-style docstrings (with experimental Google support) in `.py`
9
9
  files and Jupyter notebooks while preserving surrounding code.
@@ -13,7 +13,7 @@ oriented before making changes.
13
13
  `jupyter-notebook-parser`, and `tomli/tomllib` for configuration loading.
14
14
  - Version is sourced dynamically in `format_docstring/__init__.py`.
15
15
 
16
- ## Repository Layout
16
+ ## 2. Repository Layout
17
17
 
18
18
  - `format_docstring/main_py.py` – Click CLI for Python files; validates input
19
19
  and delegates to `PythonFileFixer`.
@@ -33,7 +33,7 @@ oriented before making changes.
33
33
  - `tests/` – Pytest suite with fixture-driven cases in `tests/test_data/`; see
34
34
  `tests/helpers.py` for fixture loading helpers.
35
35
 
36
- ## Implementation Notes
36
+ ## 3. Implementation Notes
37
37
 
38
38
  - `docstring_rewriter.fix_src` parses with `ast.parse`, collects docstring
39
39
  literals, and rewrites source slices using absolute offsets from
@@ -44,6 +44,8 @@ oriented before making changes.
44
44
  redundant `, optional`, and forward references keep their original quoting.
45
45
  - Return annotations are likewise projected into `Returns`/`Yields` sections,
46
46
  mirroring tuple element splits when the docstring already enumerates them.
47
+ - `Raises` section entries are treated like signature lines in the NumPy
48
+ wrapper so exception names stay untouched while descriptions wrap.
47
49
  - Wrapping honors NumPy section heuristics, rST constructs, code fences,
48
50
  `Examples` prompts, and literal blocks introduced by `::`.
49
51
  - `_normalize_signature_segment` flattens multiline annotations via
@@ -57,7 +59,7 @@ oriented before making changes.
57
59
  - Notebook fixer round-trips JSON via `json.dump(..., indent=1)` and rewrites
58
60
  cells only when content changes, preserving magics with `reconstruct_source`.
59
61
 
60
- ## Configuration
62
+ ## 4. Configuration
61
63
 
62
64
  - User-facing configuration lives under `[tool.format_docstring]` inside
63
65
  `pyproject.toml` and supports `line_length`, `docstring_style`, `exclude`,
@@ -68,7 +70,7 @@ oriented before making changes.
68
70
  - Default exclude pattern is `\.git|\.tox|\.pytest_cache`; tests tweak it as
69
71
  needed.
70
72
 
71
- ## Development Workflow
73
+ ## 5. Development Workflow
72
74
 
73
75
  - Install: `pip install -e .` for the project,
74
76
  `pip install -r requirements.dev` for tooling.
@@ -83,17 +85,20 @@ oriented before making changes.
83
85
  `format-docstring-jupyter --help`.
84
86
  - Pre-commit: `pre-commit run -a`.
85
87
 
86
- ## Testing Notes
88
+ ## 6. Testing Notes
87
89
 
88
90
  - Fixture files under `tests/test_data/line_wrap` and
89
91
  `tests/test_data/end_to_end` use `LINE_LENGTH: <int>` headers followed by
90
92
  `BEFORE`/`AFTER` sections split by `**********`.
93
+ - Regression fixture
94
+ `tests/test_data/end_to_end/numpy/signature_dont_sync_raises.txt` guards
95
+ against mutating exception names in `Raises` blocks.
91
96
  - `tests/test_playground.py` focuses on regression snippets;
92
97
  `tests/test_config.py` exercises config discovery and CLI overrides.
93
98
  - When modifying wrapping rules, update both the helper (`line_wrap_utils.py`)
94
99
  and the corresponding expectation files in `tests/test_data/`.
95
100
 
96
- ## Style Guidance
101
+ ## 7. Style Guidance
97
102
 
98
103
  - Formatting rules mirror `muff.toml` (line length 79, single quotes, NumPy
99
104
  docstring convention). Respect these when adding code.
@@ -101,7 +106,7 @@ oriented before making changes.
101
106
  content, and add regression cases whenever handling around literal sections
102
107
  or tables changes.
103
108
 
104
- ## What is a "signature line"?
109
+ ## 8. What is a "signature line"?
105
110
 
106
111
  They are the lines in the docstring where input and return args are defined,
107
112
  for example, in this docstring:
@@ -6,6 +6,20 @@ 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.2] - 2024-10-20
10
+
11
+ - Added
12
+ - Formatting support for type hints and default values in class docstrings
13
+ - Full diff
14
+ - https://github.com/jsh9/format-docstring/compare/0.2.1...0.2.2
15
+
16
+ ## [0.2.1] - 2025-10-20
17
+
18
+ - Fixed
19
+ - A bug where raised exceptions in docstrings are incorrectly changed
20
+ - Full diff
21
+ - https://github.com/jsh9/format-docstring/compare/0.2.0...0.2.1
22
+
9
23
  ## [0.2.0] - 2025-10-20
10
24
 
11
25
  - Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: format-docstring
3
- Version: 0.2.0
3
+ Version: 0.2.2
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>
@@ -15,7 +15,7 @@ class BaseFixer:
15
15
  ----------
16
16
  path : str
17
17
  Target file or directory to process.
18
- exclude_pattern : str, default='\.git|\.tox|\.pytest_cache'
18
+ exclude_pattern : str, default=r'\.git|\.tox|\.pytest_cache'
19
19
  Regular expression describing paths to skip.
20
20
  verbose : str, default='default'
21
21
  Verbosity mode; ``'diff'`` prints unified diffs for rewritten files.
@@ -221,6 +221,53 @@ def _collect_param_metadata(
221
221
  return metadata
222
222
 
223
223
 
224
+ def _collect_class_metadata(
225
+ node: ast.ClassDef,
226
+ source_code: str,
227
+ ) -> tuple[ParameterMetadata, ParameterMetadata]:
228
+ """
229
+ Build metadata for class docstrings using ``__init__`` and class attrs.
230
+ """
231
+ init_metadata: ParameterMetadata = {}
232
+ attribute_metadata: ParameterMetadata = {}
233
+
234
+ init_method: ast.FunctionDef | None = None
235
+ for stmt in node.body:
236
+ if isinstance(stmt, ast.FunctionDef) and stmt.name == '__init__':
237
+ init_method = stmt
238
+ break
239
+
240
+ if init_method is not None:
241
+ init_metadata = _collect_param_metadata(init_method, source_code)
242
+ # ``self``/``cls`` rarely appear in docstrings; drop to avoid noise.
243
+ init_metadata.pop('self', None)
244
+ init_metadata.pop('cls', None)
245
+
246
+ for stmt in node.body:
247
+ if isinstance(stmt, ast.AnnAssign):
248
+ target = stmt.target
249
+ if not isinstance(target, ast.Name):
250
+ continue
251
+
252
+ annotation = _render_signature_piece(stmt.annotation, source_code)
253
+ default = _render_signature_piece(stmt.value, source_code)
254
+ attribute_metadata[target.id] = (annotation, default)
255
+ continue
256
+
257
+ if isinstance(stmt, ast.Assign):
258
+ if len(stmt.targets) != 1:
259
+ continue
260
+
261
+ assign_target = stmt.targets[0]
262
+ if not isinstance(assign_target, ast.Name):
263
+ continue
264
+
265
+ # Record that this attribute explicitly has no annotation/default.
266
+ attribute_metadata[assign_target.id] = ('', None)
267
+
268
+ return init_metadata, attribute_metadata
269
+
270
+
224
271
  def fix_src(
225
272
  source_code: str,
226
273
  *,
@@ -255,7 +302,7 @@ def fix_src(
255
302
  spans directly in the original text to preserve non-docstring formatting
256
303
  and comments.
257
304
  """
258
- tree: ast.Module = ast.parse(source_code)
305
+ tree: ast.Module = ast.parse(source_code, type_comments=True)
259
306
  line_starts: list[int] = calc_line_starts(source_code)
260
307
 
261
308
  replacements: list[tuple[int, int, str]] = []
@@ -387,10 +434,20 @@ def build_replacement_docstring(
387
434
  )
388
435
 
389
436
  param_metadata: ParameterMetadata | None = None
437
+ attribute_metadata: ParameterMetadata | None = None
390
438
  return_annotation: str | None = None
391
439
  if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
392
440
  param_metadata = _collect_param_metadata(node, source_code)
393
441
  return_annotation = _render_signature_piece(node.returns, source_code)
442
+ elif isinstance(node, ast.ClassDef):
443
+ init_metadata, class_attr_metadata = _collect_class_metadata(
444
+ node, source_code
445
+ )
446
+ if init_metadata:
447
+ param_metadata = init_metadata
448
+
449
+ if class_attr_metadata:
450
+ attribute_metadata = class_attr_metadata
394
451
 
395
452
  wrapped: str = wrap_docstring(
396
453
  doc,
@@ -400,6 +457,7 @@ def build_replacement_docstring(
400
457
  fix_rst_backticks=fix_rst_backticks,
401
458
  function_param_metadata=param_metadata,
402
459
  function_return_annotation=return_annotation,
460
+ class_attribute_metadata=attribute_metadata,
403
461
  )
404
462
 
405
463
  new_literal: str | None = rebuild_literal(original_literal, wrapped)
@@ -521,6 +579,7 @@ def wrap_docstring(
521
579
  fix_rst_backticks: bool = True,
522
580
  function_param_metadata: ParameterMetadata | None = None,
523
581
  function_return_annotation: str | None = None,
582
+ class_attribute_metadata: ParameterMetadata | None = None,
524
583
  ) -> str:
525
584
  """
526
585
  Wrap a docstring to the given line length (stub).
@@ -544,6 +603,9 @@ def wrap_docstring(
544
603
  function_return_annotation : str | None, default=None
545
604
  The function's return annotation text (normalized), used to keep
546
605
  ``Returns``/``Yields`` signature lines synchronized.
606
+ class_attribute_metadata : ParameterMetadata | None, default=None
607
+ Attribute metadata for class docstrings (names mapped to annotations
608
+ and default values) collected from class-level assignments.
547
609
 
548
610
  Returns
549
611
  -------
@@ -565,6 +627,7 @@ def wrap_docstring(
565
627
  fix_rst_backticks=fix_rst_backticks,
566
628
  parameter_metadata=function_param_metadata,
567
629
  return_annotation=function_return_annotation,
630
+ attribute_metadata=class_attribute_metadata,
568
631
  )
569
632
  # Default to NumPy-style for unknown/unspecified styles to be permissive.
570
633
  return wrap_docstring_numpy(
@@ -573,5 +636,6 @@ def wrap_docstring(
573
636
  leading_indent=leading_indent,
574
637
  fix_rst_backticks=fix_rst_backticks,
575
638
  parameter_metadata=function_param_metadata,
639
+ attribute_metadata=class_attribute_metadata,
576
640
  return_annotation=function_return_annotation,
577
641
  )
@@ -8,6 +8,7 @@ def wrap_docstring_google(
8
8
  fix_rst_backticks: bool = True,
9
9
  parameter_metadata: ParameterMetadata | None = None,
10
10
  return_annotation: str | None = None,
11
+ attribute_metadata: ParameterMetadata | None = None,
11
12
  ) -> str:
12
13
  """A placeholder for now.""" # noqa: D401
13
14
  return ''
@@ -19,6 +19,7 @@ def wrap_docstring_numpy(
19
19
  leading_indent: int | None = None,
20
20
  fix_rst_backticks: bool = False,
21
21
  parameter_metadata: ParameterMetadata | None = None,
22
+ attribute_metadata: ParameterMetadata | None = None,
22
23
  return_annotation: str | None = None,
23
24
  ) -> str:
24
25
  """
@@ -66,12 +67,15 @@ def wrap_docstring_numpy(
66
67
  'other parameters:',
67
68
  'other parameter', # tolerate typo
68
69
  'other parameter:',
70
+ }
71
+ SECTION_ATTRIBUTES = {
69
72
  'attributes',
70
73
  'attributes:',
71
74
  'attribute', # tolerate typo
72
75
  'attribute:',
73
76
  }
74
- SECTION_RETURNS = {
77
+ SECTION_SIGNABLE = SECTION_PARAMS | SECTION_ATTRIBUTES
78
+ SECTION_RETURNS_YIELDS = {
75
79
  'returns',
76
80
  'returns:',
77
81
  'return', # tolerate typo
@@ -80,6 +84,8 @@ def wrap_docstring_numpy(
80
84
  'yields:',
81
85
  'yield', # tolerate typo
82
86
  'yield:',
87
+ }
88
+ SECTION_RAISES = {
83
89
  'raises',
84
90
  'raises:',
85
91
  'raise', # tolerate typo
@@ -154,7 +160,11 @@ def wrap_docstring_numpy(
154
160
 
155
161
  # Parameters-like sections
156
162
  section_lower_case: str = current_section.lower()
157
- if section_lower_case in SECTION_PARAMS:
163
+ if section_lower_case in SECTION_SIGNABLE:
164
+ metadata_for_section = parameter_metadata
165
+ if section_lower_case in SECTION_ATTRIBUTES:
166
+ metadata_for_section = attribute_metadata or parameter_metadata
167
+
158
168
  if line.strip() == '':
159
169
  temp_out.append(line)
160
170
  i += 1
@@ -170,7 +180,7 @@ def wrap_docstring_numpy(
170
180
  fixed_line = _fix_colon_spacing(line)
171
181
  fixed_line = _standardize_default_value(fixed_line)
172
182
  fixed_line = _rewrite_parameter_signature(
173
- fixed_line, parameter_metadata
183
+ fixed_line, metadata_for_section
174
184
  )
175
185
  fixed_line = _standardize_default_value(fixed_line)
176
186
  temp_out.append(fixed_line)
@@ -183,7 +193,7 @@ def wrap_docstring_numpy(
183
193
  continue
184
194
 
185
195
  # Returns/Yields sections
186
- if section_lower_case in SECTION_RETURNS:
196
+ if section_lower_case in SECTION_RETURNS_YIELDS:
187
197
  if line.strip() == '':
188
198
  temp_out.append(line)
189
199
  i += 1
@@ -234,6 +244,19 @@ def wrap_docstring_numpy(
234
244
  i += 1
235
245
  continue
236
246
 
247
+ # Raises section
248
+ if section_lower_case in SECTION_RAISES:
249
+ if line.strip() == '':
250
+ temp_out.append(line)
251
+ i += 1
252
+ continue
253
+
254
+ # Treat top-level lines as signatures
255
+ if indent_length <= leading_indent: # type: ignore[operator]
256
+ temp_out.append(line)
257
+ i += 1
258
+ continue
259
+
237
260
  # Examples or any other section
238
261
  collect_to_temp_output(temp_out, line)
239
262
  i += 1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: format-docstring
3
- Version: 0.2.0
3
+ Version: 0.2.2
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>
@@ -58,14 +58,17 @@ tests/test_data/end_to_end/numpy/module_level_docstring.txt
58
58
  tests/test_data/end_to_end/numpy/new_lines_before_and_after.txt
59
59
  tests/test_data/end_to_end/numpy/no_format_docstring_comment.txt
60
60
  tests/test_data/end_to_end/numpy/param_signature_without_type.txt
61
- tests/test_data/end_to_end/numpy/parameter_signature_sync.txt
62
61
  tests/test_data/end_to_end/numpy/parameters_returns_raises_wrapping.txt
63
62
  tests/test_data/end_to_end/numpy/returns_signature_and_description.txt
64
- tests/test_data/end_to_end/numpy/returns_signature_sync.txt
65
63
  tests/test_data/end_to_end/numpy/section_headings_with_colons.txt
66
64
  tests/test_data/end_to_end/numpy/section_title_fixed.txt
67
65
  tests/test_data/end_to_end/numpy/sections_notes_examples.txt
66
+ tests/test_data/end_to_end/numpy/signature_dont_sync_raises.txt
68
67
  tests/test_data/end_to_end/numpy/signature_line_is_not_wrapped.txt
68
+ tests/test_data/end_to_end/numpy/signature_sync_class_docstrings.txt
69
+ tests/test_data/end_to_end/numpy/signature_sync_parameters.txt
70
+ tests/test_data/end_to_end/numpy/signature_sync_returns.txt
71
+ tests/test_data/end_to_end/numpy/signature_sync_yields.txt
69
72
  tests/test_data/end_to_end/numpy/single_line_docstring.txt
70
73
  tests/test_data/end_to_end/numpy/texts_are_rewrapped.txt
71
74
  tests/test_data/end_to_end/numpy/very_long_unbreakable_word.txt
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "format-docstring"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "A Python formatter to wrap/adjust docstring lines"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -162,7 +162,7 @@ class DataProcessor:
162
162
 
163
163
  Attributes
164
164
  ----------
165
- mode : str
165
+ mode : str, default='standard'
166
166
  Processing mode, one of ``'simple'``, ``'standard'``, or
167
167
  ``'advanced'``.
168
168
  pipelines : list
@@ -0,0 +1,48 @@
1
+ LINE_LENGTH: 79
2
+
3
+ **********
4
+ def func1() -> Any:
5
+ """
6
+ Do something.
7
+
8
+ This test case is to make sure the raised exceptions are not modified by
9
+ this formatter.
10
+
11
+ Returns
12
+ -------
13
+ Any
14
+
15
+ Raises
16
+ ------
17
+ ValueError
18
+ When value is wrong. This is a very very very very very very very very very very very very very very very very very very very very very very very very very very very long line.
19
+ IOError
20
+ When IO is wrong. This is a very very very very very very very very very very very very very very very very very very very very very very very very very very very long line.
21
+ """
22
+ pass
23
+
24
+ **********
25
+
26
+ def func1() -> Any:
27
+ """
28
+ Do something.
29
+
30
+ This test case is to make sure the raised exceptions are not modified by
31
+ this formatter.
32
+
33
+ Returns
34
+ -------
35
+ Any
36
+
37
+ Raises
38
+ ------
39
+ ValueError
40
+ When value is wrong. This is a very very very very very very very very
41
+ very very very very very very very very very very very very very very
42
+ very very very very very long line.
43
+ IOError
44
+ When IO is wrong. This is a very very very very very very very very
45
+ very very very very very very very very very very very very very very
46
+ very very very very very long line.
47
+ """
48
+ pass
@@ -0,0 +1,203 @@
1
+ LINE_LENGTH: 79
2
+
3
+ **********
4
+ from typing import Callable, Mapping
5
+
6
+
7
+ class Widget:
8
+ """
9
+ Widget docstring.
10
+
11
+ Parameters
12
+ ----------
13
+ foo : str
14
+ Primary value.
15
+ bar : int
16
+ Secondary value.
17
+ options : tuple
18
+ Additional options payload.
19
+ callback : Callable
20
+ Callback invoked after processing.
21
+ arg_not_in_init_signature :"MyCustomClassThatHasVeryVeryVeryVeryVeryVeryVeryVeryLongName"
22
+ Arg
23
+
24
+ Attributes
25
+ ----------
26
+ qux : dict
27
+ Class attribute.
28
+ count : tuple
29
+ Count attribute.
30
+ settings : Mapping
31
+ Settings payload.
32
+ threshold : float
33
+ Threshold attribute.
34
+ prefix : str | None, default='x'
35
+ Prefix attribute.
36
+ """
37
+
38
+ qux: dict[str, list[int]] = {'alpha': [1, 2]}
39
+ count: tuple[int, ...]
40
+ settings: Mapping[str, tuple[int, ...]]
41
+ threshold: float = 0.75
42
+ prefix = 'x' # type: str | None
43
+
44
+ def __init__(
45
+ self,
46
+ foo: dict[str, list[int]],
47
+ bar: 'PathLike[str]' | None = None,
48
+ *,
49
+ options: tuple[list[int], dict[str, float]] = (
50
+ [1, 2], {'scale': 0.5}
51
+ ),
52
+ callback: Callable[[str, int], bool] | None = None,
53
+ ) -> None:
54
+ """Init."""
55
+ self.foo = foo
56
+ self.bar = bar
57
+ self.options = options
58
+ self.callback = callback
59
+
60
+
61
+ class Both:
62
+ """
63
+ Both class docstring and __init__
64
+ docstring will have a Parameters section,
65
+ and this tool will
66
+ format the type hints and default values in both
67
+ docstrings. Even
68
+ though this is not best
69
+ practice (and some linters will
70
+ report an error), it
71
+ is out of the scope
72
+ of this
73
+ formatter
74
+ to
75
+ fix
76
+ this.
77
+
78
+ Parameters
79
+ ----------
80
+ data:
81
+ The input data
82
+ metadata:
83
+ The metadata
84
+
85
+ Attributes
86
+ ----------
87
+ something:
88
+ Something
89
+ important:
90
+ Important
91
+ """
92
+
93
+ something: tuple[str, ...] = ['a', 'b', "c", "d"]
94
+ important: bool = False
95
+
96
+ def __init__(self, data: np.ndarray, metadata: dict[str, Any]) -> None:
97
+ """
98
+ Initialize the class.
99
+
100
+ Parameters
101
+ ----------
102
+ data: np.ndarray
103
+ The input data
104
+ metadata:
105
+ The metadata
106
+ """
107
+ pass
108
+
109
+ **********
110
+ from typing import Callable, Mapping
111
+
112
+
113
+ class Widget:
114
+ """
115
+ Widget docstring.
116
+
117
+ Parameters
118
+ ----------
119
+ foo : dict[str, list[int]]
120
+ Primary value.
121
+ bar : 'PathLike[str]' | None, default=None
122
+ Secondary value.
123
+ options : tuple[list[int], dict[str, float]], default=([1, 2], {'scale': 0.5})
124
+ Additional options payload.
125
+ callback : Callable[[str, int], bool] | None, default=None
126
+ Callback invoked after processing.
127
+ arg_not_in_init_signature : "MyCustomClassThatHasVeryVeryVeryVeryVeryVeryVeryVeryLongName"
128
+ Arg
129
+
130
+ Attributes
131
+ ----------
132
+ qux : dict[str, list[int]], default={'alpha': [1, 2]}
133
+ Class attribute.
134
+ count : tuple[int, ...]
135
+ Count attribute.
136
+ settings : Mapping[str, tuple[int, ...]]
137
+ Settings payload.
138
+ threshold : float, default=0.75
139
+ Threshold attribute.
140
+ prefix :
141
+ Prefix attribute.
142
+ """
143
+
144
+ qux: dict[str, list[int]] = {'alpha': [1, 2]}
145
+ count: tuple[int, ...]
146
+ settings: Mapping[str, tuple[int, ...]]
147
+ threshold: float = 0.75
148
+ prefix = 'x' # type: str | None
149
+
150
+ def __init__(
151
+ self,
152
+ foo: dict[str, list[int]],
153
+ bar: 'PathLike[str]' | None = None,
154
+ *,
155
+ options: tuple[list[int], dict[str, float]] = (
156
+ [1, 2], {'scale': 0.5}
157
+ ),
158
+ callback: Callable[[str, int], bool] | None = None,
159
+ ) -> None:
160
+ """Init."""
161
+ self.foo = foo
162
+ self.bar = bar
163
+ self.options = options
164
+ self.callback = callback
165
+
166
+
167
+ class Both:
168
+ """
169
+ Both class docstring and __init__ docstring will have a Parameters section,
170
+ and this tool will format the type hints and default values in both
171
+ docstrings. Even though this is not best practice (and some linters will
172
+ report an error), it is out of the scope of this formatter to fix this.
173
+
174
+ Parameters
175
+ ----------
176
+ data : np.ndarray
177
+ The input data
178
+ metadata : dict[str, Any]
179
+ The metadata
180
+
181
+ Attributes
182
+ ----------
183
+ something : tuple[str, ...], default=['a', 'b', "c", "d"]
184
+ Something
185
+ important : bool, default=False
186
+ Important
187
+ """
188
+
189
+ something: tuple[str, ...] = ['a', 'b', "c", "d"]
190
+ important: bool = False
191
+
192
+ def __init__(self, data: np.ndarray, metadata: dict[str, Any]) -> None:
193
+ """
194
+ Initialize the class.
195
+
196
+ Parameters
197
+ ----------
198
+ data : np.ndarray
199
+ The input data
200
+ metadata : dict[str, Any]
201
+ The metadata
202
+ """
203
+ pass
@@ -98,6 +98,37 @@ class Example:
98
98
  """
99
99
  return flag
100
100
 
101
+
102
+ def docstring_has_inconsistent_args_than_signature(
103
+ a: int,
104
+ b: str,
105
+ c: bool,
106
+ d: float,
107
+ ) -> None:
108
+ """
109
+ This
110
+ docstring has
111
+ inconsistent arguments
112
+ than the function signature.
113
+ This tool will only format the "common
114
+ args" (args that are in both the docstring
115
+ and the function signature). Some docstring linters
116
+ will report an error, but it is out of scope for this tool to deal with this issue.
117
+
118
+ Parameters
119
+ ----------
120
+ b :
121
+ B
122
+ d :
123
+ D
124
+ e:
125
+ E
126
+ f:
127
+ F
128
+ """
129
+ pass
130
+
131
+
101
132
  **********
102
133
  from typing import List, Optional
103
134
 
@@ -195,3 +226,29 @@ class Example:
195
226
  Result.
196
227
  """
197
228
  return flag
229
+
230
+
231
+ def docstring_has_inconsistent_args_than_signature(
232
+ a: int,
233
+ b: str,
234
+ c: bool,
235
+ d: float,
236
+ ) -> None:
237
+ """
238
+ This docstring has inconsistent arguments than the function signature. This
239
+ tool will only format the "common args" (args that are in both the
240
+ docstring and the function signature). Some docstring linters will report
241
+ an error, but it is out of scope for this tool to deal with this issue.
242
+
243
+ Parameters
244
+ ----------
245
+ b : str
246
+ B
247
+ d : float
248
+ D
249
+ e :
250
+ E
251
+ f :
252
+ F
253
+ """
254
+ pass
@@ -84,6 +84,35 @@ def custom_return_type_2() -> tuple['MyType1', "MyType2"]:
84
84
  """
85
85
  pass
86
86
 
87
+
88
+ def with_return_var_name_1() -> str:
89
+ """
90
+ Do something
91
+
92
+ Returns
93
+ -------
94
+ result:float
95
+ The result
96
+ """
97
+ pass
98
+
99
+
100
+ def with_return_var_name_2() -> tuple[str, int, int]:
101
+ """
102
+ Do something
103
+
104
+ Returns
105
+ -------
106
+ name:
107
+ The name
108
+ index :bool
109
+ The index
110
+ status: str
111
+ The status
112
+ """
113
+ pass
114
+
115
+
87
116
  **********
88
117
  from typing import List
89
118
 
@@ -167,3 +196,31 @@ def custom_return_type_2() -> tuple['MyType1', "MyType2"]:
167
196
  Integer
168
197
  """
169
198
  pass
199
+
200
+
201
+ def with_return_var_name_1() -> str:
202
+ """
203
+ Do something
204
+
205
+ Returns
206
+ -------
207
+ result : str
208
+ The result
209
+ """
210
+ pass
211
+
212
+
213
+ def with_return_var_name_2() -> tuple[str, int, int]:
214
+ """
215
+ Do something
216
+
217
+ Returns
218
+ -------
219
+ name : str
220
+ The name
221
+ index : int
222
+ The index
223
+ status : int
224
+ The status
225
+ """
226
+ pass
@@ -0,0 +1,103 @@
1
+ LINE_LENGTH: 79
2
+
3
+ **********
4
+ from collections.abc import Generator, Iterator
5
+
6
+
7
+ def yield_numbers() -> Iterator[int]:
8
+ """
9
+ Yield numbers.
10
+
11
+ Yields
12
+ ------
13
+ str
14
+ Should match annotation.
15
+ """
16
+ yield 1
17
+
18
+
19
+ def yield_named() -> Generator[int, None, None]:
20
+ """
21
+ Yield named.
22
+
23
+ Yields
24
+ ------
25
+ result : str
26
+ Should match annotation.
27
+ """
28
+ yield 1
29
+
30
+
31
+ def yield_custom_type_1() -> Iterator['MyType1']:
32
+ """
33
+ Yield custom type.
34
+
35
+ Yields
36
+ ------
37
+ str
38
+ """
39
+ yield 'value'
40
+
41
+
42
+ def yield_custom_type_2() -> Iterator[tuple['MyType1', "MyType2"]]:
43
+ """
44
+ Yield another custom type.
45
+
46
+ Yields
47
+ ------
48
+ str
49
+ Combined values.
50
+ """
51
+ yield 'value'
52
+ yield 1
53
+
54
+ **********
55
+ from collections.abc import Generator, Iterator
56
+
57
+
58
+ def yield_numbers() -> Iterator[int]:
59
+ """
60
+ Yield numbers.
61
+
62
+ Yields
63
+ ------
64
+ Iterator[int]
65
+ Should match annotation.
66
+ """
67
+ yield 1
68
+
69
+
70
+ def yield_named() -> Generator[int, None, None]:
71
+ """
72
+ Yield named.
73
+
74
+ Yields
75
+ ------
76
+ result : Generator[int, None, None]
77
+ Should match annotation.
78
+ """
79
+ yield 1
80
+
81
+
82
+ def yield_custom_type_1() -> Iterator['MyType1']:
83
+ """
84
+ Yield custom type.
85
+
86
+ Yields
87
+ ------
88
+ Iterator['MyType1']
89
+ """
90
+ yield 'value'
91
+
92
+
93
+ def yield_custom_type_2() -> Iterator[tuple['MyType1', "MyType2"]]:
94
+ """
95
+ Yield another custom type.
96
+
97
+ Yields
98
+ ------
99
+ Iterator[tuple['MyType1', "MyType2"]]
100
+ Combined values.
101
+ """
102
+ yield 'value'
103
+ yield 1