pyprojkit 0.4.3__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyprojkit
3
- Version: 0.4.3
3
+ Version: 0.6.0
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>
@@ -183,7 +183,7 @@ Tool configurations are grouped by category under `ToolsConfig`. Every category
183
183
  - `publish`: build/publish output directory
184
184
  - `skill`: a `pyprojkit-python` [agent skill](https://agentskills.io) generated from the configuration
185
185
 
186
- `create_kitchen_sink_tools()` returns a `ToolsConfig` with everything enabled (except sphinx, which needs project-specific settings). Customize it with `dataclasses.replace`:
186
+ `create_recommended_tools()` returns a `ToolsConfig` with everything enabled (except sphinx, which needs project-specific settings). Customize it with `dataclasses.replace`:
187
187
 
188
188
  ```python
189
189
  from dataclasses import replace
@@ -195,7 +195,7 @@ from pyprojkit import (
195
195
  PythonVersions,
196
196
  RequiresPython,
197
197
  SphinxConfig,
198
- create_kitchen_sink_tools,
198
+ create_recommended_tools,
199
199
  )
200
200
 
201
201
  config = ProjectConfig(
@@ -203,7 +203,7 @@ config = ProjectConfig(
203
203
  requires_python=RequiresPython((3, 12)),
204
204
  pinned_python=PythonVersions(3, (12, 14)),
205
205
  tools=replace(
206
- create_kitchen_sink_tools(),
206
+ create_recommended_tools(),
207
207
  doc=DocConfig(sphinx=SphinxConfig(copy_env_var="MY_PACKAGE_DOCS_DIR")),
208
208
  analysis=None,
209
209
  ),
@@ -214,7 +214,7 @@ To tweak a managed value, replace it in the tool's config:
214
214
 
215
215
  ```python
216
216
  tools=replace(
217
- create_kitchen_sink_tools(),
217
+ create_recommended_tools(),
218
218
  test=TestConfig(pytest=PytestConfig(addopts="-x")),
219
219
  )
220
220
  ```
@@ -334,11 +334,11 @@ There is no profile registry: a shared configuration is just a function or globa
334
334
  # in your shared module, e.g. mycompany_config.py
335
335
  from dataclasses import replace
336
336
 
337
- from pyprojkit import AnalysisConfig, ToolsConfig, create_kitchen_sink_tools
337
+ from pyprojkit import AnalysisConfig, ToolsConfig, create_recommended_tools
338
338
 
339
339
 
340
340
  def create_mycompany_tools() -> ToolsConfig:
341
- return replace(create_kitchen_sink_tools(), analysis=AnalysisConfig())
341
+ return replace(create_recommended_tools(), analysis=AnalysisConfig())
342
342
  ```
343
343
 
344
344
  ```python
@@ -140,7 +140,7 @@ Tool configurations are grouped by category under `ToolsConfig`. Every category
140
140
  - `publish`: build/publish output directory
141
141
  - `skill`: a `pyprojkit-python` [agent skill](https://agentskills.io) generated from the configuration
142
142
 
143
- `create_kitchen_sink_tools()` returns a `ToolsConfig` with everything enabled (except sphinx, which needs project-specific settings). Customize it with `dataclasses.replace`:
143
+ `create_recommended_tools()` returns a `ToolsConfig` with everything enabled (except sphinx, which needs project-specific settings). Customize it with `dataclasses.replace`:
144
144
 
145
145
  ```python
146
146
  from dataclasses import replace
@@ -152,7 +152,7 @@ from pyprojkit import (
152
152
  PythonVersions,
153
153
  RequiresPython,
154
154
  SphinxConfig,
155
- create_kitchen_sink_tools,
155
+ create_recommended_tools,
156
156
  )
157
157
 
158
158
  config = ProjectConfig(
@@ -160,7 +160,7 @@ config = ProjectConfig(
160
160
  requires_python=RequiresPython((3, 12)),
161
161
  pinned_python=PythonVersions(3, (12, 14)),
162
162
  tools=replace(
163
- create_kitchen_sink_tools(),
163
+ create_recommended_tools(),
164
164
  doc=DocConfig(sphinx=SphinxConfig(copy_env_var="MY_PACKAGE_DOCS_DIR")),
165
165
  analysis=None,
166
166
  ),
@@ -171,7 +171,7 @@ To tweak a managed value, replace it in the tool's config:
171
171
 
172
172
  ```python
173
173
  tools=replace(
174
- create_kitchen_sink_tools(),
174
+ create_recommended_tools(),
175
175
  test=TestConfig(pytest=PytestConfig(addopts="-x")),
176
176
  )
177
177
  ```
@@ -291,11 +291,11 @@ There is no profile registry: a shared configuration is just a function or globa
291
291
  # in your shared module, e.g. mycompany_config.py
292
292
  from dataclasses import replace
293
293
 
294
- from pyprojkit import AnalysisConfig, ToolsConfig, create_kitchen_sink_tools
294
+ from pyprojkit import AnalysisConfig, ToolsConfig, create_recommended_tools
295
295
 
296
296
 
297
297
  def create_mycompany_tools() -> ToolsConfig:
298
- return replace(create_kitchen_sink_tools(), analysis=AnalysisConfig())
298
+ return replace(create_recommended_tools(), analysis=AnalysisConfig())
299
299
  ```
300
300
 
301
301
  ```python
@@ -29,7 +29,7 @@ license-files = ["LICENSE"]
29
29
  name = "pyprojkit"
30
30
  readme = "README.md"
31
31
  requires-python = ">=3.12,<4.0" # pyprojkit-managed
32
- version = "0.4.3"
32
+ version = "0.6.0"
33
33
 
34
34
  [project.optional-dependencies]
35
35
  all = [
@@ -107,6 +107,7 @@ profile = "black" # pyprojkit-managed
107
107
  quiet = true # pyprojkit-managed
108
108
 
109
109
  [tool.mypy]
110
+ check_untyped_defs = true # pyprojkit-managed
110
111
  ignore_missing_imports = true # pyprojkit-managed
111
112
  python_version = "3.12" # pyprojkit-managed
112
113
 
@@ -114,6 +115,9 @@ python_version = "3.12" # pyprojkit-managed
114
115
  default_venv_backend = "uv" # pyprojkit-managed
115
116
  envdir = "__cache__/nox" # pyprojkit-managed
116
117
 
118
+ [tool.pyright]
119
+ pythonVersion = "3.12" # pyprojkit-managed
120
+
117
121
  [tool.pytest.ini_options]
118
122
  addopts = "--import-mode=importlib -s -v -rA" # pyprojkit-managed
119
123
  cache_dir = "__cache__/pytest" # pyprojkit-managed
@@ -19,6 +19,7 @@ __submodules__ = [
19
19
  # <AUTOGEN_INIT>
20
20
  from .config import (
21
21
  ConfigError,
22
+ BaseConfig,
22
23
  BaseToolConfig,
23
24
  BaseFormatterConfig,
24
25
  AutoflakeConfig,
@@ -32,6 +33,7 @@ from .config import (
32
33
  DoitConfig,
33
34
  NoxConfig,
34
35
  MypyConfig,
36
+ PyrightConfig,
35
37
  ProjectConfig,
36
38
  ToolsConfig,
37
39
  FormattingConfig,
@@ -42,7 +44,7 @@ from .config import (
42
44
  AnalysisConfig,
43
45
  PublishConfig,
44
46
  SkillConfig,
45
- create_kitchen_sink_tools,
47
+ create_recommended_tools,
46
48
  )
47
49
  from .versions import (
48
50
  DEFAULT_PATCH_VERSIONS,
@@ -71,6 +73,7 @@ from .sessions import (
71
73
  __all__ = [
72
74
  "sync",
73
75
  "ConfigError",
76
+ "BaseConfig",
74
77
  "BaseToolConfig",
75
78
  "BaseFormatterConfig",
76
79
  "AutoflakeConfig",
@@ -84,6 +87,7 @@ __all__ = [
84
87
  "DoitConfig",
85
88
  "NoxConfig",
86
89
  "MypyConfig",
90
+ "PyrightConfig",
87
91
  "ProjectConfig",
88
92
  "ToolsConfig",
89
93
  "FormattingConfig",
@@ -94,7 +98,7 @@ __all__ = [
94
98
  "AnalysisConfig",
95
99
  "PublishConfig",
96
100
  "SkillConfig",
97
- "create_kitchen_sink_tools",
101
+ "create_recommended_tools",
98
102
  "DEFAULT_PATCH_VERSIONS",
99
103
  "PythonVersions",
100
104
  "PythonVersionSet",
@@ -13,6 +13,7 @@ __submodules__ = [
13
13
  # <AUTOGEN_INIT>
14
14
  from .base import (
15
15
  ConfigError,
16
+ BaseConfig,
16
17
  BaseToolConfig,
17
18
  BaseFormatterConfig,
18
19
  )
@@ -28,6 +29,7 @@ from .tools import (
28
29
  DoitConfig,
29
30
  NoxConfig,
30
31
  MypyConfig,
32
+ PyrightConfig,
31
33
  )
32
34
  from .project import (
33
35
  ProjectConfig,
@@ -42,11 +44,12 @@ from .project import (
42
44
  SkillConfig,
43
45
  )
44
46
  from .profiles import (
45
- create_kitchen_sink_tools,
47
+ create_recommended_tools,
46
48
  )
47
49
 
48
50
  __all__ = [
49
51
  "ConfigError",
52
+ "BaseConfig",
50
53
  "BaseToolConfig",
51
54
  "BaseFormatterConfig",
52
55
  "AutoflakeConfig",
@@ -60,6 +63,7 @@ __all__ = [
60
63
  "DoitConfig",
61
64
  "NoxConfig",
62
65
  "MypyConfig",
66
+ "PyrightConfig",
63
67
  "ProjectConfig",
64
68
  "ToolsConfig",
65
69
  "FormattingConfig",
@@ -70,6 +74,6 @@ __all__ = [
70
74
  "AnalysisConfig",
71
75
  "PublishConfig",
72
76
  "SkillConfig",
73
- "create_kitchen_sink_tools",
77
+ "create_recommended_tools",
74
78
  ]
75
79
  # </AUTOGEN_INIT>
@@ -6,13 +6,14 @@ from __future__ import annotations
6
6
 
7
7
  from abc import ABC, abstractmethod
8
8
  from dataclasses import dataclass, fields
9
- from typing import TYPE_CHECKING, Any, ClassVar
9
+ from typing import TYPE_CHECKING, Any, ClassVar, Self
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from .project import ProjectConfig
13
13
 
14
14
  __all__ = [
15
15
  "ConfigError",
16
+ "BaseConfig",
16
17
  "BaseToolConfig",
17
18
  "BaseFormatterConfig",
18
19
  ]
@@ -24,16 +25,35 @@ class ConfigError(Exception):
24
25
  """
25
26
 
26
27
 
28
+ class BaseConfig(ABC):
29
+ """
30
+ Base for all tool configurations: both tool categories (e.g. `TestConfig`) and
31
+ individual tools' `pyproject.toml` tables (e.g. `PytestConfig`).
32
+
33
+ A default-constructed config is empty: every setting is unset and every optional
34
+ sub-config is disabled. Opinionated settings are provided by `create_recommended()`.
35
+ """
36
+
37
+ @classmethod
38
+ @abstractmethod
39
+ def create_recommended(cls) -> Self:
40
+ """
41
+ Get a new instance with opinionated settings, as used by
42
+ `create_recommended_tools()`.
43
+ """
44
+ ...
45
+
46
+
27
47
  @dataclass(kw_only=True)
28
- class BaseToolConfig(ABC):
48
+ class BaseToolConfig(BaseConfig):
29
49
  """
30
50
  Configuration for a tool which reads its settings from a table in `pyproject.toml`,
31
51
  e.g. `[tool.black]`.
32
52
 
33
53
  Dataclass fields map to table entries; a field whose name differs from its TOML key
34
54
  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()`.
55
+ (the default for every field) are omitted. Values derived from project-wide config
56
+ (e.g. black's `target-version`) are contributed by `extra_toml()`.
37
57
  """
38
58
 
39
59
  table_path: ClassVar[str]
@@ -0,0 +1,52 @@
1
+ """
2
+ Built-in tool configurations.
3
+
4
+ Configurations are plain `ToolsConfig` values: define your own factory function or
5
+ global in a module shared across projects, enabling just the tools you want:
6
+
7
+ ```python
8
+ from pyprojkit import (
9
+ AnalysisConfig,
10
+ BlackConfig,
11
+ FormattingConfig,
12
+ IsortConfig,
13
+ PyrightConfig,
14
+ PytestConfig,
15
+ TestConfig,
16
+ ToolsConfig,
17
+ )
18
+
19
+
20
+ def create_mycompany_tools() -> ToolsConfig:
21
+ return ToolsConfig(
22
+ formatting=FormattingConfig(
23
+ formatters=(
24
+ IsortConfig(profile="black"),
25
+ BlackConfig(),
26
+ )
27
+ ),
28
+ test=TestConfig(pytest=PytestConfig(testpaths="tests")),
29
+ analysis=AnalysisConfig(pyright=PyrightConfig()),
30
+ )
31
+ ```
32
+
33
+ Alternatively, start from `create_recommended_tools()` and override parts of it with
34
+ `dataclasses.replace()`.
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ from .project import ToolsConfig
40
+
41
+ __all__ = [
42
+ "create_recommended_tools",
43
+ ]
44
+
45
+
46
+ def create_recommended_tools() -> ToolsConfig:
47
+ """
48
+ Get a new instance of the recommended configuration: every tool which doesn't need
49
+ project-specific settings, with opinionated settings; equivalent to
50
+ `ToolsConfig.create_recommended()`.
51
+ """
52
+ return ToolsConfig.create_recommended()
@@ -6,18 +6,24 @@ from __future__ import annotations
6
6
 
7
7
  from dataclasses import dataclass, field
8
8
  from pathlib import Path
9
- from typing import Literal, Sequence
9
+ from typing import Literal, Self, Sequence
10
10
 
11
11
  from .. import _paths
12
12
  from ..versions import PythonVersions, PythonVersionSet, RequiresPython
13
- from .base import BaseFormatterConfig
13
+ from .base import BaseConfig, BaseFormatterConfig
14
14
  from .tools import (
15
+ AutoflakeConfig,
16
+ BlackConfig,
15
17
  CoverageReportConfig,
16
18
  CoverageRunConfig,
19
+ DocfmtConfig,
17
20
  DoitConfig,
21
+ IsortConfig,
18
22
  MypyConfig,
19
23
  NoxConfig,
24
+ PyrightConfig,
20
25
  PytestConfig,
26
+ TomlSortConfig,
21
27
  )
22
28
 
23
29
  __all__ = [
@@ -35,19 +41,31 @@ __all__ = [
35
41
 
36
42
 
37
43
  @dataclass(kw_only=True)
38
- class FormattingConfig:
44
+ class FormattingConfig(BaseConfig):
39
45
  """
40
46
  Formatting configuration: which formatters run (in order) and their settings.
41
47
  """
42
48
 
43
- formatters: tuple[BaseFormatterConfig, ...]
49
+ formatters: tuple[BaseFormatterConfig, ...] = ()
44
50
  """
45
51
  Formatters in run order.
46
52
  """
47
53
 
54
+ @classmethod
55
+ def create_recommended(cls) -> Self:
56
+ return cls(
57
+ formatters=(
58
+ AutoflakeConfig.create_recommended(),
59
+ IsortConfig.create_recommended(),
60
+ DocfmtConfig.create_recommended(),
61
+ BlackConfig.create_recommended(),
62
+ TomlSortConfig.create_recommended(),
63
+ )
64
+ )
65
+
48
66
 
49
67
  @dataclass(kw_only=True)
50
- class TestConfig:
68
+ class TestConfig(BaseConfig):
51
69
  """
52
70
  Testing configuration (pytest + coverage).
53
71
  """
@@ -56,27 +74,35 @@ class TestConfig:
56
74
  coverage_run: CoverageRunConfig = field(default_factory=CoverageRunConfig)
57
75
  coverage_report: CoverageReportConfig = field(default_factory=CoverageReportConfig)
58
76
 
77
+ @classmethod
78
+ def create_recommended(cls) -> Self:
79
+ return cls(
80
+ pytest=PytestConfig.create_recommended(),
81
+ coverage_run=CoverageRunConfig.create_recommended(),
82
+ coverage_report=CoverageReportConfig.create_recommended(),
83
+ )
84
+
59
85
 
60
86
  @dataclass(kw_only=True)
61
- class MkinitConfig:
87
+ class MkinitConfig(BaseConfig):
62
88
  """
63
89
  Configuration for generating `__init__.py` files using mkinit.
64
90
  """
65
91
 
66
- args: tuple[str, ...] = (
67
- "--recursive",
68
- "--nomods",
69
- "--relative",
70
- "-i",
71
- "--source-order",
72
- )
92
+ args: tuple[str, ...] = ()
73
93
  """
74
94
  Arguments passed to mkinit.
75
95
  """
76
96
 
97
+ @classmethod
98
+ def create_recommended(cls) -> Self:
99
+ return cls(
100
+ args=("--recursive", "--nomods", "--relative", "-i", "--source-order")
101
+ )
102
+
77
103
 
78
104
  @dataclass(kw_only=True)
79
- class SphinxConfig:
105
+ class SphinxConfig(BaseConfig):
80
106
  """
81
107
  Configuration for building documentation using sphinx.
82
108
  """
@@ -92,49 +118,72 @@ class SphinxConfig:
92
118
  built documentation when the doc task is run with `--copy`.
93
119
  """
94
120
 
121
+ @classmethod
122
+ def create_recommended(cls) -> Self:
123
+ return cls()
124
+
95
125
 
96
126
  @dataclass(kw_only=True)
97
- class DocConfig:
127
+ class DocConfig(BaseConfig):
98
128
  """
99
129
  Documentation tool configurations.
100
130
  """
101
131
 
102
- mkinit: MkinitConfig | None = field(default_factory=MkinitConfig)
132
+ mkinit: MkinitConfig | None = None
103
133
  """
104
- Enables the `init` task; enabled by default.
134
+ Enables the `init` task.
105
135
  """
106
136
 
107
137
  sphinx: SphinxConfig | None = None
108
138
  """
109
- Enables the `doc` task; opt-in.
139
+ Enables the `doc` task.
110
140
  """
111
141
 
142
+ @classmethod
143
+ def create_recommended(cls) -> Self:
144
+ """
145
+ Enables mkinit; sphinx is left disabled since it needs project-specific
146
+ settings.
147
+ """
148
+ return cls(mkinit=MkinitConfig.create_recommended())
149
+
112
150
 
113
151
  @dataclass(kw_only=True)
114
- class AnalysisConfig:
152
+ class AnalysisConfig(BaseConfig):
115
153
  """
116
154
  Static analysis configuration.
117
155
  """
118
156
 
119
- mypy: MypyConfig | None = field(default_factory=MypyConfig)
120
- pyright: bool = True
157
+ mypy: MypyConfig | None = None
158
+ pyright: PyrightConfig | None = None
159
+
160
+ @classmethod
161
+ def create_recommended(cls) -> Self:
162
+ return cls(
163
+ mypy=MypyConfig.create_recommended(),
164
+ pyright=PyrightConfig.create_recommended(),
165
+ )
121
166
 
122
167
 
123
168
  @dataclass(kw_only=True)
124
- class PublishConfig:
169
+ class PublishConfig(BaseConfig):
125
170
  """
126
171
  Configuration for building and publishing via uv.
127
172
  """
128
173
 
129
- out_dir: str = str(_paths.UV_PATH)
174
+ out_dir: str | None = None
130
175
  """
131
- Directory for build artifacts; cleaned before each build so stale artifacts are
132
- never published.
176
+ Directory for build artifacts, defaulting to uv's `dist`; cleaned before each build
177
+ so stale artifacts are never published.
133
178
  """
134
179
 
180
+ @classmethod
181
+ def create_recommended(cls) -> Self:
182
+ return cls(out_dir=str(_paths.UV_PATH))
183
+
135
184
 
136
185
  @dataclass(kw_only=True)
137
- class SkillConfig:
186
+ class SkillConfig(BaseConfig):
138
187
  """
139
188
  Agent skill integration: a `pyprojkit-python` skill in the
140
189
  [Agent Skills](https://agentskills.io) format, generated from the project's
@@ -148,16 +197,20 @@ class SkillConfig:
148
197
  `<skills_dir>/pyprojkit-python/SKILL.md`.
149
198
  """
150
199
 
200
+ @classmethod
201
+ def create_recommended(cls) -> Self:
202
+ return cls()
203
+
151
204
 
152
205
  @dataclass(kw_only=True)
153
- class ToolsConfig:
206
+ class ToolsConfig(BaseConfig):
154
207
  """
155
208
  Development tool configurations, grouped by broad tool category.
156
209
 
157
210
  Every category is opt-in: `None` (the default) means the tool is neither managed nor
158
- given a task. For a pre-canned set of everything, see `create_kitchen_sink_tools()`;
159
- to share a configuration across projects, define your own factory function or
160
- global.
211
+ given a task. For a pre-canned set of everything, see `create_recommended()` (also
212
+ available as `create_recommended_tools()`); to share a configuration across
213
+ projects, define your own factory function or global.
161
214
  """
162
215
 
163
216
  workflow_tool: Literal["uv", "poetry"] | None = None
@@ -179,6 +232,26 @@ class ToolsConfig:
179
232
  sync.
180
233
  """
181
234
 
235
+ @classmethod
236
+ def create_recommended(cls) -> Self:
237
+ """
238
+ Enables every tool category, each with its opinionated settings: uv as the
239
+ workflow tool, formatting, testing, doit, nox, mkinit, static analysis,
240
+ publishing and the agent skill. Sphinx is left disabled since it needs
241
+ project-specific settings.
242
+ """
243
+ return cls(
244
+ workflow_tool="uv",
245
+ formatting=FormattingConfig.create_recommended(),
246
+ test=TestConfig.create_recommended(),
247
+ doit=DoitConfig.create_recommended(),
248
+ nox=NoxConfig.create_recommended(),
249
+ doc=DocConfig.create_recommended(),
250
+ analysis=AnalysisConfig.create_recommended(),
251
+ publish=PublishConfig.create_recommended(),
252
+ skill=SkillConfig.create_recommended(),
253
+ )
254
+
182
255
 
183
256
  @dataclass(kw_only=True)
184
257
  class ProjectConfig:
@@ -0,0 +1,247 @@
1
+ """
2
+ Configurations for all supported tools.
3
+
4
+ Formatters are tools which additionally run as part of the `format` task. Every field
5
+ defaults to `None` (unset); each tool's opinionated settings are provided by its
6
+ `create_recommended()`.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+ from typing import TYPE_CHECKING, Any, ClassVar, Self
13
+
14
+ from .. import _paths
15
+ from .base import BaseFormatterConfig, BaseToolConfig
16
+
17
+ if TYPE_CHECKING:
18
+ from .project import ProjectConfig
19
+
20
+ __all__ = [
21
+ "AutoflakeConfig",
22
+ "IsortConfig",
23
+ "BlackConfig",
24
+ "DocfmtConfig",
25
+ "TomlSortConfig",
26
+ "PytestConfig",
27
+ "CoverageRunConfig",
28
+ "CoverageReportConfig",
29
+ "DoitConfig",
30
+ "NoxConfig",
31
+ "MypyConfig",
32
+ "PyrightConfig",
33
+ ]
34
+
35
+
36
+ # --- formatters (run order defined by FormattingConfig) ---
37
+
38
+
39
+ @dataclass(kw_only=True)
40
+ class AutoflakeConfig(BaseFormatterConfig):
41
+ table_path: ClassVar[str] = "tool.autoflake"
42
+
43
+ in_place: bool | None = None
44
+ recursive: bool | None = None
45
+ remove_all_unused_imports: bool | None = field(
46
+ default=None, metadata={"toml": "remove-all-unused-imports"}
47
+ )
48
+ remove_unused_variables: bool | None = field(
49
+ default=None, metadata={"toml": "remove-unused-variables"}
50
+ )
51
+
52
+ @classmethod
53
+ def create_recommended(cls) -> Self:
54
+ return cls(
55
+ in_place=True,
56
+ recursive=True,
57
+ remove_all_unused_imports=True,
58
+ remove_unused_variables=True,
59
+ )
60
+
61
+ def command(self, py_paths: list[str], toml_paths: list[str]) -> list[str]:
62
+ return ["autoflake"] + py_paths
63
+
64
+
65
+ @dataclass(kw_only=True)
66
+ class IsortConfig(BaseFormatterConfig):
67
+ table_path: ClassVar[str] = "tool.isort"
68
+
69
+ profile: str | None = None
70
+ quiet: bool | None = None
71
+
72
+ @classmethod
73
+ def create_recommended(cls) -> Self:
74
+ return cls(profile="black", quiet=True)
75
+
76
+ def command(self, py_paths: list[str], toml_paths: list[str]) -> list[str]:
77
+ return ["isort"] + py_paths
78
+
79
+
80
+ @dataclass(kw_only=True)
81
+ class BlackConfig(BaseFormatterConfig):
82
+ table_path: ClassVar[str] = "tool.black"
83
+
84
+ quiet: bool | None = None
85
+
86
+ @classmethod
87
+ def create_recommended(cls) -> Self:
88
+ return cls(quiet=True)
89
+
90
+ def extra_toml(self, project: ProjectConfig) -> dict[str, Any]:
91
+ versions = project.pinned_versions
92
+ if versions is None:
93
+ return {}
94
+ return {"target-version": [f"py{maj}{min}" for maj, min in versions.versions]}
95
+
96
+ def command(self, py_paths: list[str], toml_paths: list[str]) -> list[str]:
97
+ return ["black"] + py_paths
98
+
99
+
100
+ @dataclass(kw_only=True)
101
+ class DocfmtConfig(BaseFormatterConfig):
102
+ table_path: ClassVar[str] = "tool.docfmt"
103
+
104
+ # docfmt returns 0 when it writes changes in-place; 1 means "would change"
105
+ # in check mode and 2 is an error, so only 0 is expected here
106
+ expect_rc: ClassVar[frozenset[int]] = frozenset({0})
107
+
108
+ # line length is left unset: docfmt picks it up from `[tool.black]`
109
+ in_place: bool | None = field(default=None, metadata={"toml": "in-place"})
110
+ recursive: bool | None = None
111
+ summary_on_own_line: bool | None = field(
112
+ default=None, metadata={"toml": "summary-on-own-line"}
113
+ )
114
+ force_reflow: bool | None = field(default=None, metadata={"toml": "force-reflow"})
115
+
116
+ @classmethod
117
+ def create_recommended(cls) -> Self:
118
+ return cls(
119
+ in_place=True, recursive=True, summary_on_own_line=True, force_reflow=True
120
+ )
121
+
122
+ def command(self, py_paths: list[str], toml_paths: list[str]) -> list[str]:
123
+ return ["docfmt"] + py_paths
124
+
125
+
126
+ @dataclass(kw_only=True)
127
+ class TomlSortConfig(BaseFormatterConfig):
128
+ table_path: ClassVar[str] = "tool.tomlsort"
129
+
130
+ sort_first: tuple[str, ...] | None = None
131
+ sort_table_keys: bool | None = None
132
+
133
+ # the toml-sort CLI and library defaults disagree; when unset, the sync engine's
134
+ # normalization falls back to the CLI default so it agrees with the format task
135
+ spaces_before_inline_comment: int | None = None
136
+
137
+ @classmethod
138
+ def create_recommended(cls) -> Self:
139
+ return cls(
140
+ sort_first=("project", "dependency-groups", "build-system"),
141
+ sort_table_keys=True,
142
+ spaces_before_inline_comment=2,
143
+ )
144
+
145
+ def command(self, py_paths: list[str], toml_paths: list[str]) -> list[str]:
146
+ return ["toml-sort", "-i"] + toml_paths
147
+
148
+
149
+ # --- non-formatter tools ---
150
+
151
+
152
+ @dataclass(kw_only=True)
153
+ class PytestConfig(BaseToolConfig):
154
+ table_path: ClassVar[str] = "tool.pytest.ini_options"
155
+
156
+ addopts: str | None = None
157
+ cache_dir: str | None = None
158
+ testpaths: str | None = None
159
+
160
+ @classmethod
161
+ def create_recommended(cls) -> Self:
162
+ return cls(
163
+ addopts="--import-mode=importlib -s -v -rA",
164
+ cache_dir=str(_paths.PYTEST_CACHE_PATH),
165
+ testpaths="test",
166
+ )
167
+
168
+
169
+ @dataclass(kw_only=True)
170
+ class CoverageRunConfig(BaseToolConfig):
171
+ table_path: ClassVar[str] = "tool.coverage.run"
172
+
173
+ data_file: str | None = None
174
+
175
+ @classmethod
176
+ def create_recommended(cls) -> Self:
177
+ return cls(data_file=str(_paths.COVERAGE_DATA_PATH))
178
+
179
+
180
+ @dataclass(kw_only=True)
181
+ class CoverageReportConfig(BaseToolConfig):
182
+ table_path: ClassVar[str] = "tool.coverage.report"
183
+
184
+ exclude_lines: tuple[str, ...] | None = None
185
+
186
+ @classmethod
187
+ def create_recommended(cls) -> Self:
188
+ return cls(exclude_lines=("if TYPE_CHECKING:", "\\.\\.\\.$"))
189
+
190
+
191
+ @dataclass(kw_only=True)
192
+ class DoitConfig(BaseToolConfig):
193
+ table_path: ClassVar[str] = "tool.doit"
194
+
195
+ dep_file: str | None = None
196
+ verbosity: int | None = None
197
+
198
+ @classmethod
199
+ def create_recommended(cls) -> Self:
200
+ return cls(dep_file=str(_paths.DOIT_DB_PATH), verbosity=2)
201
+
202
+
203
+ @dataclass(kw_only=True)
204
+ class NoxConfig(BaseToolConfig):
205
+ table_path: ClassVar[str] = "tool.nox"
206
+
207
+ default_venv_backend: str | None = None
208
+ envdir: str | None = None
209
+
210
+ @classmethod
211
+ def create_recommended(cls) -> Self:
212
+ return cls(default_venv_backend="uv", envdir=str(_paths.NOX_ENVDIR_PATH))
213
+
214
+
215
+ @dataclass(kw_only=True)
216
+ class MypyConfig(BaseToolConfig):
217
+ table_path: ClassVar[str] = "tool.mypy"
218
+
219
+ ignore_missing_imports: bool | None = None
220
+ check_untyped_defs: bool | None = None
221
+
222
+ @classmethod
223
+ def create_recommended(cls) -> Self:
224
+ return cls(ignore_missing_imports=True, check_untyped_defs=True)
225
+
226
+ def extra_toml(self, project: ProjectConfig) -> dict[str, Any]:
227
+ versions = project.pinned_versions
228
+ if versions is None:
229
+ return {}
230
+ maj, min = versions.versions[0]
231
+ return {"python_version": f"{maj}.{min}"}
232
+
233
+
234
+ @dataclass(kw_only=True)
235
+ class PyrightConfig(BaseToolConfig):
236
+ table_path: ClassVar[str] = "tool.pyright"
237
+
238
+ @classmethod
239
+ def create_recommended(cls) -> Self:
240
+ return cls()
241
+
242
+ def extra_toml(self, project: ProjectConfig) -> dict[str, Any]:
243
+ versions = project.pinned_versions
244
+ if versions is None:
245
+ return {}
246
+ maj, min = versions.versions[0]
247
+ return {"pythonVersion": f"{maj}.{min}"}
@@ -45,7 +45,7 @@ class NoxFactory:
45
45
  ):
46
46
  self._config = config or load_config(root)
47
47
  self._registered = set()
48
- if nox_config := self._config.tools.nox:
48
+ if (nox_config := self._config.tools.nox) and nox_config.envdir is not None:
49
49
  nox.options.envdir = nox_config.envdir
50
50
 
51
51
  def create_test_session(self, *, group: str = "dev") -> Callable[[Session], None]:
@@ -45,7 +45,7 @@ from tomlkit import TOMLDocument
45
45
  from tomlkit.items import AoT, Null, Table
46
46
 
47
47
  from ._run import run
48
- from .config.base import ConfigError
48
+ from .config.base import BaseToolConfig, ConfigError
49
49
  from .config.project import ProjectConfig, SkillConfig
50
50
  from .config.tools import TomlSortConfig
51
51
  from .skill import SKILL_NAME, render_skill
@@ -70,25 +70,21 @@ def compute_managed_fields(config: ProjectConfig) -> dict[str, dict[str, Any]]:
70
70
  """
71
71
  Compute all managed fields, keyed by dotted table path then key.
72
72
  """
73
- tables: dict[str, dict[str, Any]] = {}
73
+ tool_configs: list[BaseToolConfig] = []
74
74
  tools = config.tools
75
75
 
76
76
  if tools.formatting:
77
- for formatter in tools.formatting.formatters:
78
- tables[formatter.table_path] = formatter.to_toml(config)
77
+ tool_configs += tools.formatting.formatters
79
78
 
80
79
  if test := tools.test:
81
- for test_tool in (test.pytest, test.coverage_run, test.coverage_report):
82
- tables[test_tool.table_path] = test_tool.to_toml(config)
80
+ tool_configs += [test.pytest, test.coverage_run, test.coverage_report]
83
81
 
84
- for runner in (tools.doit, tools.nox):
85
- if runner:
86
- tables[runner.table_path] = runner.to_toml(config)
82
+ tool_configs += [t for t in (tools.doit, tools.nox) if t]
87
83
 
88
- if (analysis := tools.analysis) and analysis.mypy:
89
- tables[analysis.mypy.table_path] = analysis.mypy.to_toml(config)
84
+ if analysis := tools.analysis:
85
+ tool_configs += [t for t in (analysis.mypy, analysis.pyright) if t]
90
86
 
91
- return tables
87
+ return {t.table_path: t.to_toml(config) for t in tool_configs}
92
88
 
93
89
 
94
90
  def render(config: ProjectConfig, text: str) -> str:
@@ -347,7 +347,7 @@ class TaskFactory:
347
347
  Create `publish` task which builds and publishes the package via uv.
348
348
  """
349
349
  publish = self._require(self._config.tools.publish, "tools.publish")
350
- out_dir = Path(publish.out_dir)
350
+ out_dir = Path(publish.out_dir or "dist")
351
351
 
352
352
  def _publish():
353
353
  artifacts = [str(p) for p in sorted(out_dir.iterdir())]
@@ -1,69 +0,0 @@
1
- """
2
- Built-in tool configurations.
3
-
4
- Configurations are plain `ToolsConfig` values: define your own factory function or
5
- global in a module shared across projects, optionally building on
6
- `create_kitchen_sink_tools()`:
7
-
8
- ```python
9
- from dataclasses import replace
10
-
11
- from pyprojkit import AnalysisConfig, ToolsConfig, create_kitchen_sink_tools
12
-
13
-
14
- def create_mycompany_tools() -> ToolsConfig:
15
- return replace(create_kitchen_sink_tools(), analysis=AnalysisConfig())
16
- ```
17
- """
18
-
19
- from __future__ import annotations
20
-
21
- from .project import (
22
- AnalysisConfig,
23
- DocConfig,
24
- FormattingConfig,
25
- PublishConfig,
26
- SkillConfig,
27
- TestConfig,
28
- ToolsConfig,
29
- )
30
- from .tools import (
31
- AutoflakeConfig,
32
- BlackConfig,
33
- DocfmtConfig,
34
- DoitConfig,
35
- IsortConfig,
36
- NoxConfig,
37
- TomlSortConfig,
38
- )
39
-
40
- __all__ = [
41
- "create_kitchen_sink_tools",
42
- ]
43
-
44
-
45
- def create_kitchen_sink_tools() -> ToolsConfig:
46
- """
47
- Get a new instance of the configuration which enables every tool: uv as the workflow
48
- tool, formatting, testing, doit, nox, mkinit, static analysis, publishing and the
49
- agent skill. Sphinx is left disabled since it needs project-specific settings.
50
- """
51
- return ToolsConfig(
52
- workflow_tool="uv",
53
- formatting=FormattingConfig(
54
- formatters=(
55
- AutoflakeConfig(),
56
- IsortConfig(),
57
- DocfmtConfig(),
58
- BlackConfig(),
59
- TomlSortConfig(),
60
- )
61
- ),
62
- test=TestConfig(),
63
- doit=DoitConfig(),
64
- nox=NoxConfig(),
65
- doc=DocConfig(),
66
- analysis=AnalysisConfig(),
67
- publish=PublishConfig(),
68
- skill=SkillConfig(),
69
- )
@@ -1,175 +0,0 @@
1
- """
2
- Configurations for all supported tools.
3
-
4
- Formatters are tools which additionally run as part of the `format` task.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- from dataclasses import dataclass, field
10
- from typing import TYPE_CHECKING, Any, ClassVar
11
-
12
- from .. import _paths
13
- from .base import BaseFormatterConfig, BaseToolConfig
14
-
15
- if TYPE_CHECKING:
16
- from .project import ProjectConfig
17
-
18
- __all__ = [
19
- "AutoflakeConfig",
20
- "IsortConfig",
21
- "BlackConfig",
22
- "DocfmtConfig",
23
- "TomlSortConfig",
24
- "PytestConfig",
25
- "CoverageRunConfig",
26
- "CoverageReportConfig",
27
- "DoitConfig",
28
- "NoxConfig",
29
- "MypyConfig",
30
- ]
31
-
32
-
33
- # --- formatters (run order defined by FormattingConfig) ---
34
-
35
-
36
- @dataclass(kw_only=True)
37
- class AutoflakeConfig(BaseFormatterConfig):
38
- table_path: ClassVar[str] = "tool.autoflake"
39
-
40
- in_place: bool | None = True
41
- recursive: bool | None = True
42
- remove_all_unused_imports: bool | None = field(
43
- default=True, metadata={"toml": "remove-all-unused-imports"}
44
- )
45
- remove_unused_variables: bool | None = field(
46
- default=True, metadata={"toml": "remove-unused-variables"}
47
- )
48
-
49
- def command(self, py_paths: list[str], toml_paths: list[str]) -> list[str]:
50
- return ["autoflake"] + py_paths
51
-
52
-
53
- @dataclass(kw_only=True)
54
- class IsortConfig(BaseFormatterConfig):
55
- table_path: ClassVar[str] = "tool.isort"
56
-
57
- profile: str | None = "black"
58
- quiet: bool | None = True
59
-
60
- def command(self, py_paths: list[str], toml_paths: list[str]) -> list[str]:
61
- return ["isort"] + py_paths
62
-
63
-
64
- @dataclass(kw_only=True)
65
- class BlackConfig(BaseFormatterConfig):
66
- table_path: ClassVar[str] = "tool.black"
67
-
68
- quiet: bool | None = True
69
-
70
- def extra_toml(self, project: ProjectConfig) -> dict[str, Any]:
71
- versions = project.pinned_versions
72
- if versions is None:
73
- return {}
74
- return {"target-version": [f"py{maj}{min}" for maj, min in versions.versions]}
75
-
76
- def command(self, py_paths: list[str], toml_paths: list[str]) -> list[str]:
77
- return ["black"] + py_paths
78
-
79
-
80
- @dataclass(kw_only=True)
81
- class DocfmtConfig(BaseFormatterConfig):
82
- table_path: ClassVar[str] = "tool.docfmt"
83
-
84
- # docfmt returns 0 when it writes changes in-place; 1 means "would change"
85
- # in check mode and 2 is an error, so only 0 is expected here
86
- expect_rc: ClassVar[frozenset[int]] = frozenset({0})
87
-
88
- # line length is left unset: docfmt picks it up from `[tool.black]`
89
- in_place: bool | None = field(default=True, metadata={"toml": "in-place"})
90
- recursive: bool | None = True
91
- summary_on_own_line: bool | None = field(
92
- default=True, metadata={"toml": "summary-on-own-line"}
93
- )
94
- force_reflow: bool | None = field(default=True, metadata={"toml": "force-reflow"})
95
-
96
- def command(self, py_paths: list[str], toml_paths: list[str]) -> list[str]:
97
- return ["docfmt"] + py_paths
98
-
99
-
100
- @dataclass(kw_only=True)
101
- class TomlSortConfig(BaseFormatterConfig):
102
- table_path: ClassVar[str] = "tool.tomlsort"
103
-
104
- sort_first: tuple[str, ...] | None = (
105
- "project",
106
- "dependency-groups",
107
- "build-system",
108
- )
109
- sort_table_keys: bool | None = True
110
-
111
- # explicit since the toml-sort CLI and library defaults disagree; must be
112
- # pinned so the sync engine's normalization and the format task agree
113
- spaces_before_inline_comment: int | None = 2
114
-
115
- def command(self, py_paths: list[str], toml_paths: list[str]) -> list[str]:
116
- return ["toml-sort", "-i"] + toml_paths
117
-
118
-
119
- # --- non-formatter tools ---
120
-
121
-
122
- @dataclass(kw_only=True)
123
- class PytestConfig(BaseToolConfig):
124
- table_path: ClassVar[str] = "tool.pytest.ini_options"
125
-
126
- addopts: str | None = "--import-mode=importlib -s -v -rA"
127
- cache_dir: str | None = str(_paths.PYTEST_CACHE_PATH)
128
- testpaths: str | None = "test"
129
-
130
-
131
- @dataclass(kw_only=True)
132
- class CoverageRunConfig(BaseToolConfig):
133
- table_path: ClassVar[str] = "tool.coverage.run"
134
-
135
- data_file: str | None = str(_paths.COVERAGE_DATA_PATH)
136
-
137
-
138
- @dataclass(kw_only=True)
139
- class CoverageReportConfig(BaseToolConfig):
140
- table_path: ClassVar[str] = "tool.coverage.report"
141
-
142
- exclude_lines: tuple[str, ...] | None = (
143
- "if TYPE_CHECKING:",
144
- "\\.\\.\\.$",
145
- )
146
-
147
-
148
- @dataclass(kw_only=True)
149
- class DoitConfig(BaseToolConfig):
150
- table_path: ClassVar[str] = "tool.doit"
151
-
152
- dep_file: str | None = str(_paths.DOIT_DB_PATH)
153
- verbosity: int | None = 2
154
-
155
-
156
- @dataclass(kw_only=True)
157
- class NoxConfig(BaseToolConfig):
158
- table_path: ClassVar[str] = "tool.nox"
159
-
160
- default_venv_backend: str | None = "uv"
161
- envdir: str | None = str(_paths.NOX_ENVDIR_PATH)
162
-
163
-
164
- @dataclass(kw_only=True)
165
- class MypyConfig(BaseToolConfig):
166
- table_path: ClassVar[str] = "tool.mypy"
167
-
168
- ignore_missing_imports: bool | None = True
169
-
170
- def extra_toml(self, project: ProjectConfig) -> dict[str, Any]:
171
- versions = project.pinned_versions
172
- if versions is None:
173
- return {}
174
- maj, min = versions.versions[0]
175
- return {"python_version": f"{maj}.{min}"}
File without changes