pyprojkit 0.2.0__tar.gz → 0.3.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyprojkit
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: Development workflow toolkit for Python projects: pyproject.toml sync, doit tasks, nox sessions
5
5
  Author: mm21
6
6
  Author-email: mm21 <mm21.dev@gmail.com>
@@ -27,7 +27,7 @@ Requires-Dist: pyright>=1.1.329,<2 ; extra == 'analysis'
27
27
  Requires-Dist: mkinit ; extra == 'doc'
28
28
  Requires-Dist: autoflake>=2.3.1,<3 ; extra == 'format'
29
29
  Requires-Dist: black>=25.1.0,<26 ; extra == 'format'
30
- Requires-Dist: docformatter>=1.7.7,<2 ; extra == 'format'
30
+ Requires-Dist: docfmt>=0.2.0,<0.3.0 ; extra == 'format'
31
31
  Requires-Dist: isort>=6.0.1,<7 ; extra == 'format'
32
32
  Requires-Dist: genbadge[tests,coverage]>=1.1.0,<2 ; extra == 'test'
33
33
  Requires-Dist: pytest>=8.4.2,<9 ; extra == 'test'
@@ -160,7 +160,7 @@ python = PythonVersions(3, (12, 14), patch_overrides={(3, 14): "3.14.7"})
160
160
 
161
161
  Tool configurations are grouped by category under `ToolsConfig`, whose defaults constitute the default *profile*:
162
162
 
163
- - `formatting`: ordered chain of formatters; by default autoflake &rarr; isort &rarr; black &rarr; docformatter &rarr; toml-sort
163
+ - `formatting`: ordered chain of formatters; by default autoflake &rarr; isort &rarr; docfmt &rarr; black &rarr; toml-sort
164
164
  - `test`: pytest + coverage settings
165
165
  - `doit`, `nox`: cache locations and backends
166
166
  - `doc`: documentation tools — mkinit (enabled by default) and sphinx (opt-in)
@@ -117,7 +117,7 @@ python = PythonVersions(3, (12, 14), patch_overrides={(3, 14): "3.14.7"})
117
117
 
118
118
  Tool configurations are grouped by category under `ToolsConfig`, whose defaults constitute the default *profile*:
119
119
 
120
- - `formatting`: ordered chain of formatters; by default autoflake &rarr; isort &rarr; black &rarr; docformatter &rarr; toml-sort
120
+ - `formatting`: ordered chain of formatters; by default autoflake &rarr; isort &rarr; docfmt &rarr; black &rarr; toml-sort
121
121
  - `test`: pytest + coverage settings
122
122
  - `doit`, `nox`: cache locations and backends
123
123
  - `doc`: documentation tools — mkinit (enabled by default) and sphinx (opt-in)
@@ -27,7 +27,7 @@ license-files = ["LICENSE"]
27
27
  name = "pyprojkit"
28
28
  readme = "README.md"
29
29
  requires-python = ">=3.12,<3.15" # managed by pyprojkit
30
- version = "0.2.0"
30
+ version = "0.3.1"
31
31
 
32
32
  [project.optional-dependencies]
33
33
  all = [
@@ -43,7 +43,7 @@ doc = [
43
43
  format = [
44
44
  "autoflake>=2.3.1,<3",
45
45
  "black>=25.1.0,<26",
46
- "docformatter>=1.7.7,<2",
46
+ "docfmt>=0.2.0,<0.3.0",
47
47
  "isort>=6.0.1,<7"
48
48
  ]
49
49
  test = [
@@ -90,13 +90,11 @@ exclude_lines = [
90
90
  [tool.coverage.run] # managed by pyprojkit
91
91
  data_file = "__cache__/.coverage"
92
92
 
93
- [tool.docformatter] # managed by pyprojkit
94
- black = true
93
+ [tool.docfmt] # managed by pyprojkit
94
+ force-reflow = true
95
95
  in-place = true
96
- make-summary-multi-line = true
97
- non-strict = true
98
- pre-summary-newline = true
99
96
  recursive = true
97
+ summary-on-own-line = true
100
98
 
101
99
  [tool.doit] # managed by pyprojkit
102
100
  dep_file = "__cache__/.doit.db"
@@ -116,7 +114,7 @@ managed = [
116
114
  "tool.black",
117
115
  "tool.coverage.report",
118
116
  "tool.coverage.run",
119
- "tool.docformatter",
117
+ "tool.docfmt",
120
118
  "tool.doit",
121
119
  "tool.isort",
122
120
  "tool.nox",
@@ -139,5 +137,4 @@ sort_table_keys = true
139
137
  spaces_before_inline_comment = 2
140
138
 
141
139
  [tool.uv.sources]
142
- docformatter = {git = "https://github.com/mm21/docformatter.git"}
143
140
  mkinit = {git = "https://github.com/mm21/mkinit.git"}
@@ -27,7 +27,7 @@ from .config import (
27
27
  AutoflakeConfig,
28
28
  IsortConfig,
29
29
  BlackConfig,
30
- DocformatterConfig,
30
+ DocfmtConfig,
31
31
  TomlSortConfig,
32
32
  PytestConfig,
33
33
  CoverageRunConfig,
@@ -74,7 +74,7 @@ __all__ = [
74
74
  "AutoflakeConfig",
75
75
  "IsortConfig",
76
76
  "BlackConfig",
77
- "DocformatterConfig",
77
+ "DocfmtConfig",
78
78
  "TomlSortConfig",
79
79
  "PytestConfig",
80
80
  "CoverageRunConfig",
@@ -20,7 +20,7 @@ from .tools import (
20
20
  AutoflakeConfig,
21
21
  IsortConfig,
22
22
  BlackConfig,
23
- DocformatterConfig,
23
+ DocfmtConfig,
24
24
  TomlSortConfig,
25
25
  PytestConfig,
26
26
  CoverageRunConfig,
@@ -54,7 +54,7 @@ __all__ = [
54
54
  "AutoflakeConfig",
55
55
  "IsortConfig",
56
56
  "BlackConfig",
57
- "DocformatterConfig",
57
+ "DocfmtConfig",
58
58
  "TomlSortConfig",
59
59
  "PytestConfig",
60
60
  "CoverageRunConfig",
@@ -27,13 +27,13 @@ class ConfigError(Exception):
27
27
  @dataclass(kw_only=True)
28
28
  class BaseToolConfig(ABC):
29
29
  """
30
- Configuration for a tool which reads its settings from a table in
31
- `pyproject.toml`, e.g. `[tool.black]`.
30
+ Configuration for a tool which reads its settings from a table in `pyproject.toml`,
31
+ e.g. `[tool.black]`.
32
32
 
33
- Dataclass fields map to table entries; a field whose name differs from its
34
- TOML key declares the key via `field(metadata={"toml": "some-key"})`.
35
- Fields set to `None` are omitted. Values derived from project-wide config
36
- (e.g. black's `target-version`) are contributed by `extra_toml()`.
33
+ Dataclass fields map to table entries; a field whose name differs from its TOML key
34
+ declares the key via `field(metadata={"toml": "some-key"})`. Fields set to `None`
35
+ are omitted. Values derived from project-wide config (e.g. black's `target-version`)
36
+ are contributed by `extra_toml()`.
37
37
  """
38
38
 
39
39
  table_path: ClassVar[str]
@@ -1,9 +1,9 @@
1
1
  """
2
2
  Profile registries: named factories for pre-canned configurations.
3
3
 
4
- A tools profile encompasses all tool categories; a formatting profile covers
5
- just the formatter chain. Third parties can register their own profiles and
6
- reuse them across projects:
4
+ A tools profile encompasses all tool categories; a formatting profile covers just the
5
+ formatter chain. Third parties can register their own profiles and reuse them across
6
+ projects:
7
7
 
8
8
  ```python
9
9
  from pyprojkit import register_tools_profile
@@ -21,7 +21,7 @@ from .project import FormattingConfig, ToolsConfig
21
21
  from .tools import (
22
22
  AutoflakeConfig,
23
23
  BlackConfig,
24
- DocformatterConfig,
24
+ DocfmtConfig,
25
25
  IsortConfig,
26
26
  TomlSortConfig,
27
27
  )
@@ -39,7 +39,7 @@ def _default_formatting() -> FormattingConfig:
39
39
  formatters=(
40
40
  AutoflakeConfig(),
41
41
  IsortConfig(),
42
- DocformatterConfig(),
42
+ DocfmtConfig(),
43
43
  BlackConfig(),
44
44
  TomlSortConfig(),
45
45
  )
@@ -189,19 +189,23 @@ class ProjectConfig:
189
189
  """
190
190
  Import name of the package, e.g. `"trilium_alchemy"`.
191
191
  """
192
+
192
193
  python: PythonVersions
193
194
  """
194
195
  Supported Python versions.
195
196
  """
197
+
196
198
  packages_dir: str = "src"
197
199
  """
198
200
  Directory containing packages, relative to the project root.
199
201
  """
202
+
200
203
  format_paths: Sequence[str] = ("src", "test", "doc", "examples")
201
204
  """
202
205
  Directories to format (those which exist), in addition to `*.py` and `*.toml` files
203
206
  at the project root.
204
207
  """
208
+
205
209
  tools: ToolsConfig = field(default_factory=ToolsConfig.default)
206
210
  """
207
211
  Tool configurations.
@@ -19,7 +19,7 @@ __all__ = [
19
19
  "AutoflakeConfig",
20
20
  "IsortConfig",
21
21
  "BlackConfig",
22
- "DocformatterConfig",
22
+ "DocfmtConfig",
23
23
  "TomlSortConfig",
24
24
  "PytestConfig",
25
25
  "CoverageRunConfig",
@@ -75,25 +75,23 @@ class BlackConfig(BaseFormatterConfig):
75
75
 
76
76
 
77
77
  @dataclass(kw_only=True)
78
- class DocformatterConfig(BaseFormatterConfig):
79
- table_path: ClassVar[str] = "tool.docformatter"
78
+ class DocfmtConfig(BaseFormatterConfig):
79
+ table_path: ClassVar[str] = "tool.docfmt"
80
80
 
81
- # docformatter returns 3 when files were changed
82
- expect_rc: ClassVar[frozenset[int]] = frozenset({0, 3})
81
+ # docfmt returns 0 when it writes changes in-place; 1 means "would change"
82
+ # in check mode and 2 is an error, so only 0 is expected here
83
+ expect_rc: ClassVar[frozenset[int]] = frozenset({0})
83
84
 
84
- black: bool | None = True
85
+ # line length is left unset: docfmt picks it up from `[tool.black]`
85
86
  in_place: bool | None = field(default=True, metadata={"toml": "in-place"})
86
- make_summary_multi_line: bool | None = field(
87
- default=True, metadata={"toml": "make-summary-multi-line"}
88
- )
89
- non_strict: bool | None = field(default=True, metadata={"toml": "non-strict"})
90
- pre_summary_newline: bool | None = field(
91
- default=True, metadata={"toml": "pre-summary-newline"}
92
- )
93
87
  recursive: bool | None = True
88
+ summary_on_own_line: bool | None = field(
89
+ default=True, metadata={"toml": "summary-on-own-line"}
90
+ )
91
+ force_reflow: bool | None = field(default=True, metadata={"toml": "force-reflow"})
94
92
 
95
93
  def command(self, py_paths: list[str], toml_paths: list[str]) -> list[str]:
96
- return ["docformatter"] + py_paths
94
+ return ["docfmt"] + py_paths
97
95
 
98
96
 
99
97
  @dataclass(kw_only=True)
@@ -5,16 +5,16 @@ Managed content:
5
5
 
6
6
  - `project.requires-python`
7
7
  - Python version classifiers (`Programming Language :: Python :: 3[.X]`);
8
- other classifiers are left untouched
8
+ other classifiers are left untouched, as are comments in the list
9
9
  - One `[tool.X]` table per enabled tool (fully owned — any hand edits or
10
10
  comments inside are overwritten)
11
11
  - `[tool.pyprojkit].managed`: bookkeeping list of owned tables, enabling safe
12
12
  removal of tables for tools dropped from the configuration
13
13
 
14
- Everything else (dependencies, build-system, urls, unmanaged tool tables,
15
- etc.) is preserved. Output is normalized with toml-sort (as a library, using
16
- the same settings as the managed `[tool.tomlsort]` table), so a subsequent
17
- `toml-sort` run in the format task is a no-op.
14
+ Everything else (dependencies, build-system, urls, unmanaged tool tables, etc.) is
15
+ preserved. Output is normalized with toml-sort (as a library, using the same settings as
16
+ the managed `[tool.tomlsort]` table), so a subsequent `toml-sort` run in the format task
17
+ is a no-op.
18
18
  """
19
19
 
20
20
  from __future__ import annotations
@@ -22,11 +22,11 @@ from __future__ import annotations
22
22
  import difflib
23
23
  import re
24
24
  from pathlib import Path
25
- from typing import Any, cast
25
+ from typing import Any
26
26
 
27
27
  import tomlkit
28
28
  from tomlkit import TOMLDocument
29
- from tomlkit.items import Table
29
+ from tomlkit.items import Null, Table
30
30
 
31
31
  from .config.base import ConfigError
32
32
  from .config.project import ProjectConfig
@@ -140,22 +140,74 @@ def _update_classifiers(project: Table, config: ProjectConfig):
140
140
  """
141
141
  Replace python version classifiers with those derived from config, preserving all
142
142
  others; result is sorted, with managed entries marked by an inline comment.
143
+
144
+ Comments in the existing list are preserved: an inline comment stays with its entry,
145
+ and a standalone comment stays with the entry it precedes (or is emitted at the end
146
+ of the list if it precedes none).
143
147
  """
144
- existing = cast(list[str], [str(c) for c in project.get("classifiers", [])])
145
- kept = [c for c in existing if not _CLASSIFIER_RE.match(c)]
146
148
  managed = set(config.python.classifiers)
149
+ existing = project.get("classifiers", [])
150
+ kept = [str(c) for c in existing if not _CLASSIFIER_RE.match(str(c))]
151
+ leading, inline, trailing = _collect_classifier_comments(
152
+ existing, set(kept) | managed
153
+ )
147
154
 
148
155
  array = tomlkit.array()
149
156
  for entry in sorted(set(kept) | managed):
157
+ for comment in leading.get(entry, []):
158
+ array.add_line(comment=comment, indent=" ")
150
159
  array.add_line(
151
160
  entry,
152
161
  indent=" ",
153
- comment=_MANAGED_COMMENT if entry in managed else None,
162
+ comment=_MANAGED_COMMENT if entry in managed else inline.get(entry),
154
163
  )
164
+ for comment in trailing:
165
+ array.add_line(comment=comment, indent=" ")
155
166
  array.add_line(indent="")
156
167
  project["classifiers"] = array
157
168
 
158
169
 
170
+ def _collect_classifier_comments(
171
+ array: Any, surviving: set[str]
172
+ ) -> tuple[dict[str, list[str]], dict[str, str], list[str]]:
173
+ """
174
+ Extract comments from an existing classifiers array, anchored to the entries they
175
+ accompany. Comments attached to entries which don't survive the sync are carried
176
+ forward to the next surviving entry.
177
+ """
178
+ leading: dict[str, list[str]] = {}
179
+ inline: dict[str, str] = {}
180
+ pending: list[str] = []
181
+
182
+ for group in getattr(array, "_value", []):
183
+ comment = _comment_text(group)
184
+ if group.value is None or isinstance(group.value, Null):
185
+ # standalone comment line
186
+ if comment:
187
+ pending.append(comment)
188
+ continue
189
+
190
+ entry = str(group.value)
191
+ if entry in surviving:
192
+ if pending:
193
+ leading.setdefault(entry, []).extend(pending)
194
+ pending = []
195
+ if comment and comment != _MANAGED_COMMENT:
196
+ inline[entry] = comment
197
+ elif comment and comment != _MANAGED_COMMENT:
198
+ # entry is going away; keep its comment as a standalone one
199
+ pending.append(comment)
200
+
201
+ return leading, inline, pending
202
+
203
+
204
+ def _comment_text(group: Any) -> str | None:
205
+ comment = getattr(group, "comment", None)
206
+ if comment is None:
207
+ return None
208
+ return comment.trivia.comment.lstrip("#").strip() or None
209
+
210
+
159
211
  def _get_managed_list(doc: TOMLDocument) -> list[str]:
160
212
  try:
161
213
  return list(doc["tool"]["pyprojkit"]["managed"]) # type: ignore[index]
File without changes