format-docstring 0.2.1__tar.gz → 0.2.3__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 (111) hide show
  1. {format_docstring-0.2.1 → format_docstring-0.2.3}/.pre-commit-config.yaml +19 -10
  2. {format_docstring-0.2.1 → format_docstring-0.2.3}/CHANGELOG.md +19 -0
  3. {format_docstring-0.2.1 → format_docstring-0.2.3}/PKG-INFO +11 -11
  4. {format_docstring-0.2.1 → format_docstring-0.2.3}/README.md +5 -5
  5. {format_docstring-0.2.1 → format_docstring-0.2.3}/format_docstring/base_fixer.py +2 -2
  6. {format_docstring-0.2.1 → format_docstring-0.2.3}/format_docstring/config.py +5 -4
  7. {format_docstring-0.2.1 → format_docstring-0.2.3}/format_docstring/docstring_rewriter.py +102 -27
  8. format_docstring-0.2.3/format_docstring/line_wrap_google.py +15 -0
  9. {format_docstring-0.2.1 → format_docstring-0.2.3}/format_docstring/line_wrap_numpy.py +31 -26
  10. {format_docstring-0.2.1 → format_docstring-0.2.3}/format_docstring/line_wrap_utils.py +21 -19
  11. {format_docstring-0.2.1 → format_docstring-0.2.3}/format_docstring/main_jupyter.py +6 -6
  12. {format_docstring-0.2.1 → format_docstring-0.2.3}/format_docstring/main_py.py +5 -3
  13. {format_docstring-0.2.1 → format_docstring-0.2.3}/format_docstring.egg-info/PKG-INFO +11 -11
  14. {format_docstring-0.2.1 → format_docstring-0.2.3}/format_docstring.egg-info/SOURCES.txt +1 -0
  15. format_docstring-0.2.3/muff.toml +116 -0
  16. format_docstring-0.2.3/pyproject.toml +58 -0
  17. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/helpers.py +5 -2
  18. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_base_fixer.py +4 -3
  19. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_config.py +3 -3
  20. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/fix_rst_backticks.txt +1 -1
  21. format_docstring-0.2.3/tests/test_data/end_to_end/numpy/signature_sync_class_docstrings.txt +203 -0
  22. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/signature_sync_parameters.txt +57 -0
  23. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_docstring_rewriter.py +24 -14
  24. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_line_wrap_numpy.py +20 -13
  25. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_line_wrap_utils.py +12 -10
  26. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_main_jupyter.py +4 -4
  27. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_main_py.py +4 -4
  28. {format_docstring-0.2.1 → format_docstring-0.2.3}/tox.ini +0 -7
  29. format_docstring-0.2.1/format_docstring/line_wrap_google.py +0 -13
  30. format_docstring-0.2.1/muff.toml +0 -48
  31. format_docstring-0.2.1/pyproject.toml +0 -60
  32. {format_docstring-0.2.1 → format_docstring-0.2.3}/.github/workflows/python-package.yml +0 -0
  33. {format_docstring-0.2.1 → format_docstring-0.2.3}/.github/workflows/python-publish.yml +0 -0
  34. {format_docstring-0.2.1 → format_docstring-0.2.3}/.gitignore +0 -0
  35. {format_docstring-0.2.1 → format_docstring-0.2.3}/.pre-commit-hooks.yaml +0 -0
  36. {format_docstring-0.2.1 → format_docstring-0.2.3}/AGENTS.md +0 -0
  37. {format_docstring-0.2.1 → format_docstring-0.2.3}/LICENSE +0 -0
  38. {format_docstring-0.2.1 → format_docstring-0.2.3}/format_docstring/__init__.py +0 -0
  39. {format_docstring-0.2.1 → format_docstring-0.2.3}/format_docstring.egg-info/dependency_links.txt +0 -0
  40. {format_docstring-0.2.1 → format_docstring-0.2.3}/format_docstring.egg-info/entry_points.txt +0 -0
  41. {format_docstring-0.2.1 → format_docstring-0.2.3}/format_docstring.egg-info/requires.txt +1 -1
  42. {format_docstring-0.2.1 → format_docstring-0.2.3}/format_docstring.egg-info/top_level.txt +0 -0
  43. {format_docstring-0.2.1 → format_docstring-0.2.3}/requirements.dev +0 -0
  44. {format_docstring-0.2.1 → format_docstring-0.2.3}/setup.cfg +0 -0
  45. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/__init__.py +0 -0
  46. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/README.md +0 -0
  47. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/colon_spacing_fix.txt +0 -0
  48. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/contents_that_are_not_wrapped.txt +0 -0
  49. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/default_value_standardization.txt +0 -0
  50. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/empty_lines_are_respected.txt +0 -0
  51. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/examples_section.txt +0 -0
  52. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/existing_linebreaks_should_not_be_respected.txt +0 -0
  53. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/four_level_nested_classes.txt +0 -0
  54. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/indent_four_levels_16_spaces_width_10.txt +0 -0
  55. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/indent_misaligned_all.txt +0 -0
  56. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/indent_two_levels_8_spaces.txt +0 -0
  57. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/line_length_2.txt +0 -0
  58. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/mismatched_underlines.txt +0 -0
  59. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/mismatched_underlines_one_dash.txt +0 -0
  60. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/mismatched_underlines_two_dashes.txt +0 -0
  61. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/module_level_docstring.txt +0 -0
  62. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/new_lines_before_and_after.txt +0 -0
  63. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/no_format_docstring_comment.txt +0 -0
  64. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/param_signature_without_type.txt +0 -0
  65. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/parameters_returns_raises_wrapping.txt +0 -0
  66. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/returns_signature_and_description.txt +0 -0
  67. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/section_headings_with_colons.txt +0 -0
  68. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/section_title_fixed.txt +0 -0
  69. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/sections_notes_examples.txt +0 -0
  70. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/signature_dont_sync_raises.txt +0 -0
  71. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/signature_line_is_not_wrapped.txt +0 -0
  72. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/signature_sync_returns.txt +0 -0
  73. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/signature_sync_yields.txt +0 -0
  74. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/single_line_docstring.txt +0 -0
  75. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/texts_are_rewrapped.txt +0 -0
  76. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/end_to_end/numpy/very_long_unbreakable_word.txt +0 -0
  77. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/integration_test/numpy/after.ipynb +0 -0
  78. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/integration_test/numpy/after.py +0 -0
  79. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/integration_test/numpy/after_50.ipynb +0 -0
  80. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/integration_test/numpy/after_50.py +0 -0
  81. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/integration_test/numpy/before.ipynb +0 -0
  82. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/integration_test/numpy/before.py +0 -0
  83. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/jupyter/before.ipynb +0 -0
  84. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/jupyter/verbose_before.ipynb +0 -0
  85. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/README.md +0 -0
  86. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/colon_spacing_fix.txt +0 -0
  87. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/contents_that_are_not_wrapped.txt +0 -0
  88. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/default_value_standardization.txt +0 -0
  89. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/empty_lines_are_respected.txt +0 -0
  90. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/examples_section.txt +0 -0
  91. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/existing_linebreaks_should_not_be_respected.txt +0 -0
  92. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/fix_rst_backticks.txt +0 -0
  93. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/indent_four_levels_16_spaces_width_10.txt +0 -0
  94. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/indent_two_levels_8_spaces.txt +0 -0
  95. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/line_length_2.txt +0 -0
  96. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/mismatched_underlines.txt +0 -0
  97. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/mismatched_underlines_one_dash.txt +0 -0
  98. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/mismatched_underlines_two_dashes.txt +0 -0
  99. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/module_level_docstring.txt +0 -0
  100. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/param_signature_without_type.txt +0 -0
  101. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/parameters_returns_raises_wrapping.txt +0 -0
  102. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/returns_signature_and_description.txt +0 -0
  103. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/section_headings_with_colons.txt +0 -0
  104. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/section_title_fixed.txt +0 -0
  105. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/sections_notes_examples.txt +0 -0
  106. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/signature_line_is_not_wrapped.txt +0 -0
  107. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/texts_are_rewrapped.txt +0 -0
  108. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/line_wrap/numpy/very_long_unbreakable_word.txt +0 -0
  109. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_data/playground.py +0 -0
  110. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_line_wrap_google.py +0 -0
  111. {format_docstring-0.2.1 → format_docstring-0.2.3}/tests/test_playground.py +0 -0
@@ -6,10 +6,7 @@ repos:
6
6
  - id: trailing-whitespace
7
7
  - id: end-of-file-fixer
8
8
  exclude: \.json$|\.ipynb$
9
- - id: debug-statements
10
- - id: double-quote-string-fixer
11
9
  - id: requirements-txt-fixer
12
- exclude: \.ipynb$
13
10
  - id: check-added-large-files
14
11
  args: [--maxkb=1000]
15
12
  - id: check-toml
@@ -18,11 +15,6 @@ repos:
18
15
  args: [--pytest-test-first]
19
16
  exclude: ^tests/test_data/|^tests/helpers\.py$
20
17
  - id: check-merge-conflict
21
- - repo: https://github.com/asottile/pyupgrade
22
- rev: v3.20.0
23
- hooks:
24
- - id: pyupgrade
25
- args: [--py310-plus]
26
18
  - repo: https://github.com/jsh9/muff-pre-commit
27
19
  rev: 0.13.2
28
20
  hooks:
@@ -37,11 +29,27 @@ repos:
37
29
  rev: 1.18.0
38
30
  hooks:
39
31
  - id: yamlfix
32
+ - repo: https://github.com/pappasam/toml-sort
33
+ rev: v0.24.2
34
+ hooks:
35
+ - id: toml-sort-fix
36
+ args:
37
+ - --in-place
38
+ - --sort-table-keys
39
+ - --sort-inline-tables
40
+ - --sort-first=name,version,dependencies,requires-python,authors,maintainers
41
+ - --trailing-comma-inline-array
42
+ - --sort-inline-arrays
43
+ - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
44
+ rev: v2.15.0
45
+ hooks:
46
+ - id: pretty-format-ini
47
+ args: [--autofix]
40
48
  - repo: https://github.com/hukkin/mdformat
41
49
  rev: 1.0.0
42
50
  hooks:
43
51
  - id: mdformat
44
- args: [--wrap, '79', --number]
52
+ args: [--wrap=79, --number]
45
53
  additional_dependencies: [mdformat-gfm]
46
54
  - repo: https://github.com/jsh9/format-json
47
55
  rev: 0.1.2
@@ -54,9 +62,10 @@ repos:
54
62
  - --no-sort-keys
55
63
  - --no-eof-newline
56
64
  - repo: https://github.com/jsh9/markdown-toc-creator
57
- rev: 0.0.11
65
+ rev: 0.1.0
58
66
  hooks:
59
67
  - id: markdown-toc-creator
68
+ exclude: ^tests/test_data/|^AGENTS\.md$|^CHANGELOG\.md$
60
69
  - repo: https://github.com/jsh9/markdown-heading-numbering
61
70
  rev: 0.1.0
62
71
  hooks:
@@ -6,6 +6,25 @@ 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.3] - 2025-10-22
10
+
11
+ - Added
12
+ - A lot more linters
13
+ - Formatters for TOML and INI config files
14
+ - Changed
15
+ - A lot of code changes to pass the linter checks
16
+ - Removed
17
+ - Unnecessary pre-commit hooks
18
+ - Full diff
19
+ - https://github.com/jsh9/format-docstring/compare/0.2.2...0.2.3
20
+
21
+ ## [0.2.2] - 2025-10-20
22
+
23
+ - Added
24
+ - Formatting support for type hints and default values in class docstrings
25
+ - Full diff
26
+ - https://github.com/jsh9/format-docstring/compare/0.2.1...0.2.2
27
+
9
28
  ## [0.2.1] - 2025-10-20
10
29
 
11
30
  - Fixed
@@ -1,32 +1,32 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: format-docstring
3
- Version: 0.2.1
3
+ Version: 0.2.3
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>
7
7
  License: MIT
8
8
  Project-URL: Homepage, https://github.com/your/repo
9
- Project-URL: Repository, https://github.com/your/repo.git
10
9
  Project-URL: Issues, https://github.com/your/repo/issues
11
- Keywords: formatter,code-style,docstring,python
10
+ Project-URL: Repository, https://github.com/your/repo.git
11
+ Keywords: code-style,docstring,formatter,python
12
12
  Classifier: Development Status :: 3 - Alpha
13
13
  Classifier: Environment :: Console
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: License :: OSI Approved :: MIT License
16
16
  Classifier: Operating System :: OS Independent
17
- Classifier: Programming Language :: Python
18
- Classifier: Programming Language :: Python :: 3
19
17
  Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3
20
19
  Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python
23
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
24
  Classifier: Topic :: Software Development :: Quality Assurance
25
25
  Requires-Python: >=3.10
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE
28
- Requires-Dist: jupyter-notebook-parser>=0.1.4
29
28
  Requires-Dist: click>=8.0
29
+ Requires-Dist: jupyter-notebook-parser>=0.1.4
30
30
  Requires-Dist: tomli>=1.1.0; python_version < "3.11"
31
31
  Dynamic: license-file
32
32
 
@@ -34,11 +34,11 @@ Dynamic: license-file
34
34
 
35
35
  A Python formatter to automatically format numpy-style docstrings.
36
36
 
37
- ______________________________________________________________________
37
+ <!--TOC-->
38
38
 
39
- **Table of Contents:**
39
+ ______________________________________________________________________
40
40
 
41
- <!--TOC-->
41
+ **Table of Contents**
42
42
 
43
43
  - [1. Overview](#1-overview)
44
44
  - [2. Before vs After Examples](#2-before-vs-after-examples)
@@ -59,10 +59,10 @@ ______________________________________________________________________
59
59
  - [5.3. `pyproject.toml` Configuration](#53-pyprojecttoml-configuration)
60
60
  - [6. Caveat](#6-caveat)
61
61
 
62
- <!--TOC-->
63
-
64
62
  ______________________________________________________________________
65
63
 
64
+ <!--TOC-->
65
+
66
66
  ## 1. Overview
67
67
 
68
68
  `format-docstring` is a tool that automatically formats and wraps docstring
@@ -2,11 +2,11 @@
2
2
 
3
3
  A Python formatter to automatically format numpy-style docstrings.
4
4
 
5
- ______________________________________________________________________
5
+ <!--TOC-->
6
6
 
7
- **Table of Contents:**
7
+ ______________________________________________________________________
8
8
 
9
- <!--TOC-->
9
+ **Table of Contents**
10
10
 
11
11
  - [1. Overview](#1-overview)
12
12
  - [2. Before vs After Examples](#2-before-vs-after-examples)
@@ -27,10 +27,10 @@ ______________________________________________________________________
27
27
  - [5.3. `pyproject.toml` Configuration](#53-pyprojecttoml-configuration)
28
28
  - [6. Caveat](#6-caveat)
29
29
 
30
- <!--TOC-->
31
-
32
30
  ______________________________________________________________________
33
31
 
32
+ <!--TOC-->
33
+
34
34
  ## 1. Overview
35
35
 
36
36
  `format-docstring` is a tool that automatically formats and wraps docstring
@@ -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.
@@ -42,7 +42,7 @@ class BaseFixer:
42
42
  if not should_exclude_file(f, self.exclude_pattern)
43
43
  ]
44
44
 
45
- def _print_diff(self, filename: str, before: str, after: str) -> None:
45
+ def print_diff(self, filename: str, before: str, after: str) -> None:
46
46
  """Print a unified diff when verbose mode is enabled."""
47
47
  if self.verbose != 'diff':
48
48
  return
@@ -4,9 +4,10 @@ from __future__ import annotations
4
4
 
5
5
  import sys
6
6
  from pathlib import Path
7
- from typing import Any
7
+ from typing import TYPE_CHECKING, Any
8
8
 
9
- import click
9
+ if TYPE_CHECKING:
10
+ import click
10
11
 
11
12
  if sys.version_info >= (3, 11):
12
13
  import tomllib
@@ -132,7 +133,7 @@ def load_config_from_file(config_file: Path) -> dict[str, Any]:
132
133
  return {}
133
134
 
134
135
  try:
135
- with open(config_file, 'rb') as fp:
136
+ with Path(config_file).open('rb') as fp:
136
137
  raw_config = tomllib.load(fp)
137
138
 
138
139
  # Extract [tool.format_docstring] section
@@ -144,7 +145,7 @@ def load_config_from_file(config_file: Path) -> dict[str, Any]:
144
145
  return {
145
146
  k.replace('-', '_'): v for k, v in format_docstring_section.items()
146
147
  }
147
- except Exception:
148
+ except Exception: # noqa: BLE001
148
149
  # If there's any error reading/parsing the file, return empty config
149
150
  return {}
150
151
 
@@ -2,14 +2,18 @@ from __future__ import annotations
2
2
 
3
3
  import ast
4
4
  import io
5
+ import operator
5
6
  import tokenize
7
+ from typing import TYPE_CHECKING
6
8
 
7
9
  from format_docstring.line_wrap_google import wrap_docstring_google
8
10
  from format_docstring.line_wrap_numpy import (
9
11
  handle_single_line_docstring,
10
12
  wrap_docstring_numpy,
11
13
  )
12
- from format_docstring.line_wrap_utils import ParameterMetadata
14
+
15
+ if TYPE_CHECKING:
16
+ from format_docstring.line_wrap_utils import ParameterMetadata
13
17
 
14
18
  ModuleClassOrFunc = (
15
19
  ast.Module | ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef
@@ -91,11 +95,13 @@ def _normalize_signature_segment(segment: str | None) -> str | None:
91
95
  # iterator order mirrors the unparse traversal so we can reapply them.
92
96
  original_strings: list[str] = []
93
97
  try:
94
- for tok in tokenize.generate_tokens(
95
- io.StringIO(normalized).readline
96
- ):
97
- if tok.type == tokenize.STRING:
98
- original_strings.append(tok.string)
98
+ original_strings.extend(
99
+ tok.string
100
+ for tok in tokenize.generate_tokens(
101
+ io.StringIO(normalized).readline
102
+ )
103
+ if tok.type == tokenize.STRING
104
+ )
99
105
  except tokenize.TokenError:
100
106
  original_strings = []
101
107
 
@@ -105,6 +111,7 @@ def _normalize_signature_segment(segment: str | None) -> str | None:
105
111
  for tok in tokenize.generate_tokens(
106
112
  io.StringIO(canonical).readline
107
113
  ):
114
+ current_tok: tokenize.TokenInfo = tok
108
115
  if tok.type == tokenize.STRING:
109
116
  replacement = next(string_iter, None)
110
117
  if replacement is not None:
@@ -116,12 +123,12 @@ def _normalize_signature_segment(segment: str | None) -> str | None:
116
123
  if ast.literal_eval(
117
124
  replacement
118
125
  ) == ast.literal_eval(tok.string):
119
- tok = tok._replace(string=replacement)
120
- except Exception:
126
+ current_tok = tok._replace(string=replacement)
127
+ except Exception: # noqa: BLE001
121
128
  pass
122
129
 
123
- rebuilt_tokens.append(tok)
124
- if tok.type == tokenize.ENDMARKER:
130
+ rebuilt_tokens.append(current_tok)
131
+ if current_tok.type == tokenize.ENDMARKER:
125
132
  break
126
133
 
127
134
  # Untokenize the rebuilt stream while trimming the leading/trailing
@@ -221,6 +228,53 @@ def _collect_param_metadata(
221
228
  return metadata
222
229
 
223
230
 
231
+ def _collect_class_metadata(
232
+ node: ast.ClassDef,
233
+ source_code: str,
234
+ ) -> tuple[ParameterMetadata, ParameterMetadata]:
235
+ """
236
+ Build metadata for class docstrings using ``__init__`` and class attrs.
237
+ """
238
+ init_metadata: ParameterMetadata = {}
239
+ attribute_metadata: ParameterMetadata = {}
240
+
241
+ init_method: ast.FunctionDef | None = None
242
+ for stmt in node.body:
243
+ if isinstance(stmt, ast.FunctionDef) and stmt.name == '__init__':
244
+ init_method = stmt
245
+ break
246
+
247
+ if init_method is not None:
248
+ init_metadata = _collect_param_metadata(init_method, source_code)
249
+ # ``self``/``cls`` rarely appear in docstrings; drop to avoid noise.
250
+ init_metadata.pop('self', None)
251
+ init_metadata.pop('cls', None)
252
+
253
+ for stmt in node.body:
254
+ if isinstance(stmt, ast.AnnAssign):
255
+ target = stmt.target
256
+ if not isinstance(target, ast.Name):
257
+ continue
258
+
259
+ annotation = _render_signature_piece(stmt.annotation, source_code)
260
+ default = _render_signature_piece(stmt.value, source_code)
261
+ attribute_metadata[target.id] = (annotation, default)
262
+ continue
263
+
264
+ if isinstance(stmt, ast.Assign):
265
+ if len(stmt.targets) != 1:
266
+ continue
267
+
268
+ assign_target = stmt.targets[0]
269
+ if not isinstance(assign_target, ast.Name):
270
+ continue
271
+
272
+ # Record that this attribute explicitly has no annotation/default.
273
+ attribute_metadata[assign_target.id] = ('', None)
274
+
275
+ return init_metadata, attribute_metadata
276
+
277
+
224
278
  def fix_src(
225
279
  source_code: str,
226
280
  *,
@@ -255,19 +309,20 @@ def fix_src(
255
309
  spans directly in the original text to preserve non-docstring formatting
256
310
  and comments.
257
311
  """
258
- tree: ast.Module = ast.parse(source_code)
312
+ tree: ast.Module = ast.parse(source_code, type_comments=True)
259
313
  line_starts: list[int] = calc_line_starts(source_code)
260
314
 
315
+ # Store (start, end, replacement text) tuples
261
316
  replacements: list[tuple[int, int, str]] = []
262
317
 
263
318
  # Module-level docstring
264
319
  replacement = build_replacement_docstring(
265
320
  tree,
266
- source_code,
267
- line_starts,
268
- line_length,
269
- docstring_style,
270
- fix_rst_backticks,
321
+ source_code=source_code,
322
+ line_starts=line_starts,
323
+ line_length=line_length,
324
+ docstring_style=docstring_style,
325
+ fix_rst_backticks=fix_rst_backticks,
271
326
  )
272
327
  if replacement is not None:
273
328
  replacements.append(replacement)
@@ -279,11 +334,11 @@ def fix_src(
279
334
  ):
280
335
  replacement = build_replacement_docstring(
281
336
  node,
282
- source_code,
283
- line_starts,
284
- line_length,
285
- docstring_style,
286
- fix_rst_backticks,
337
+ source_code=source_code,
338
+ line_starts=line_starts,
339
+ line_length=line_length,
340
+ docstring_style=docstring_style,
341
+ fix_rst_backticks=fix_rst_backticks,
287
342
  )
288
343
  if replacement is not None:
289
344
  replacements.append(replacement)
@@ -292,7 +347,8 @@ def fix_src(
292
347
  if not replacements:
293
348
  return source_code
294
349
 
295
- replacements.sort(key=lambda x: x[0], reverse=True)
350
+ # Sort by starting index descending
351
+ replacements.sort(key=operator.itemgetter(0), reverse=True)
296
352
  new_src = source_code
297
353
  for start, end, text in replacements:
298
354
  new_src = new_src[:start] + text + new_src[end:]
@@ -324,6 +380,7 @@ def calc_line_starts(source_code: str) -> list[int]:
324
380
 
325
381
  def build_replacement_docstring(
326
382
  node: ModuleClassOrFunc,
383
+ *,
327
384
  source_code: str,
328
385
  line_starts: list[int],
329
386
  line_length: int,
@@ -364,7 +421,7 @@ def build_replacement_docstring(
364
421
  return None
365
422
 
366
423
  start: int = calc_abs_pos(line_starts, val.lineno, val.col_offset)
367
- end: int = calc_abs_pos(line_starts, val.end_lineno, val.end_col_offset) # type: ignore[arg-type] # noqa: LN002
424
+ end: int = calc_abs_pos(line_starts, val.end_lineno, val.end_col_offset) # type: ignore[arg-type]
368
425
  original_literal = source_code[start:end]
369
426
 
370
427
  if _has_inline_no_format_comment(source_code, end):
@@ -387,10 +444,20 @@ def build_replacement_docstring(
387
444
  )
388
445
 
389
446
  param_metadata: ParameterMetadata | None = None
447
+ attribute_metadata: ParameterMetadata | None = None
390
448
  return_annotation: str | None = None
391
449
  if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
392
450
  param_metadata = _collect_param_metadata(node, source_code)
393
451
  return_annotation = _render_signature_piece(node.returns, source_code)
452
+ elif isinstance(node, ast.ClassDef):
453
+ init_metadata, class_attr_metadata = _collect_class_metadata(
454
+ node, source_code
455
+ )
456
+ if init_metadata:
457
+ param_metadata = init_metadata
458
+
459
+ if class_attr_metadata:
460
+ attribute_metadata = class_attr_metadata
394
461
 
395
462
  wrapped: str = wrap_docstring(
396
463
  doc,
@@ -400,6 +467,7 @@ def build_replacement_docstring(
400
467
  fix_rst_backticks=fix_rst_backticks,
401
468
  function_param_metadata=param_metadata,
402
469
  function_return_annotation=return_annotation,
470
+ class_attribute_metadata=attribute_metadata,
403
471
  )
404
472
 
405
473
  new_literal: str | None = rebuild_literal(original_literal, wrapped)
@@ -496,10 +564,10 @@ def rebuild_literal(original_literal: str, content: str) -> str | None:
496
564
  prefix = original_literal[:i]
497
565
 
498
566
  delim = ''
499
- if original_literal[i : i + 3] in ('"""', "'''"):
567
+ if original_literal[i : i + 3] in {'"""', "'''"}:
500
568
  delim = original_literal[i : i + 3]
501
569
  i += 3
502
- elif i < n and original_literal[i] in ('"', "'"):
570
+ elif i < n and original_literal[i] in {'"', "'"}:
503
571
  delim = original_literal[i]
504
572
  i += 1
505
573
  else:
@@ -518,9 +586,11 @@ def wrap_docstring(
518
586
  line_length: int = 79,
519
587
  docstring_style: str = 'numpy',
520
588
  leading_indent: int = 0,
589
+ *,
521
590
  fix_rst_backticks: bool = True,
522
591
  function_param_metadata: ParameterMetadata | None = None,
523
592
  function_return_annotation: str | None = None,
593
+ class_attribute_metadata: ParameterMetadata | None = None,
524
594
  ) -> str:
525
595
  """
526
596
  Wrap a docstring to the given line length (stub).
@@ -544,6 +614,9 @@ def wrap_docstring(
544
614
  function_return_annotation : str | None, default=None
545
615
  The function's return annotation text (normalized), used to keep
546
616
  ``Returns``/``Yields`` signature lines synchronized.
617
+ class_attribute_metadata : ParameterMetadata | None, default=None
618
+ Attribute metadata for class docstrings (names mapped to annotations
619
+ and default values) collected from class-level assignments.
547
620
 
548
621
  Returns
549
622
  -------
@@ -560,18 +633,20 @@ def wrap_docstring(
560
633
  if style == 'google':
561
634
  return wrap_docstring_google(
562
635
  docstring,
563
- line_length,
636
+ line_length=line_length,
564
637
  leading_indent=leading_indent,
565
638
  fix_rst_backticks=fix_rst_backticks,
566
639
  parameter_metadata=function_param_metadata,
567
640
  return_annotation=function_return_annotation,
641
+ attribute_metadata=class_attribute_metadata,
568
642
  )
569
643
  # Default to NumPy-style for unknown/unspecified styles to be permissive.
570
644
  return wrap_docstring_numpy(
571
645
  docstring,
572
- line_length,
646
+ line_length=line_length,
573
647
  leading_indent=leading_indent,
574
648
  fix_rst_backticks=fix_rst_backticks,
575
649
  parameter_metadata=function_param_metadata,
650
+ attribute_metadata=class_attribute_metadata,
576
651
  return_annotation=function_return_annotation,
577
652
  )
@@ -0,0 +1,15 @@
1
+ from format_docstring.line_wrap_utils import ParameterMetadata
2
+
3
+
4
+ def wrap_docstring_google(
5
+ docstring: str, # noqa: ARG001
6
+ *,
7
+ line_length: int, # noqa: ARG001
8
+ leading_indent: int | None = None, # noqa: ARG001
9
+ fix_rst_backticks: bool = True, # noqa: ARG001
10
+ parameter_metadata: ParameterMetadata | None = None, # noqa: ARG001
11
+ return_annotation: str | None = None, # noqa: ARG001
12
+ attribute_metadata: ParameterMetadata | None = None, # noqa: ARG001
13
+ ) -> str:
14
+ """A placeholder for now.""" # noqa: D401
15
+ return ''