Skip to content

Commit c4b28e4

Browse files
Introduce ToolSpec and ToolMeta abstractions to keep tool classes more accessible (#1342)
* Bump uv fallback version * Bump ruff fallback version * Bump pyproject fallback version * Major architectural changes for tool internals * Update contributing info for Tool implementations * Tweak how to use messages * Tweak expectation
1 parent 1c8cc4d commit c4b28e4

33 files changed

Lines changed: 811 additions & 895 deletions

.github/copilot-instructions.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ The following checks run automatically via prek (pre-commit framework). All must
8282

8383
**To run all checks manually:**
8484
```bash
85-
uv run prek run --all-files
85+
uv run prek run -a
8686
```
8787

8888
**Individual validation commands:**
@@ -217,7 +217,7 @@ See CONTRIBUTING.md Architecture section for detailed documentation and examples
217217
```bash
218218
uv run pytest # Tests
219219
uv run pytest --cov # Coverage
220-
uv run prek run --all-files # All pre-commit checks
220+
uv run prek run -a # All pre-commit checks
221221
```
222222

223223
7. **Build documentation** (optional):
@@ -273,7 +273,7 @@ These instructions have been validated by running actual commands and inspecting
273273
| Install git hooks | `uv run prek install` |
274274
| Run tests | `uv run pytest` |
275275
| Run tests with coverage | `uv run pytest --cov` |
276-
| Run all checks | `uv run prek run --all-files` |
276+
| Run all checks | `uv run prek run -a` |
277277
| Format code | `uv run ruff format` |
278278
| Lint code | `uv run ruff check --fix` |
279279
| Check types | `uv run ty check` |

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
- name: Run checks
5151
if: ${{ matrix.checks == null || matrix.checks == 'true' }}
5252
run: |
53-
uv run --frozen prek run --all-files
53+
uv run --frozen prek run -a
5454
uv run --frozen basedpyright
5555
5656
- name: Run pytest

CONTRIBUTING.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,13 @@ Tool implementations are defined in classes in the `usethis._tool.impl` module.
134134
- Name the classes based on the filename of the configuration file, following the pattern of the other classes in the module.
135135
- Register the file in the `files_manager` function in the `usethis._config_file` module.
136136

137-
#### Create the `Tool` subclass
137+
#### Create the `Tool` and `ToolSpec` subclasses
138138

139139
- Create a submodule in `usethis._tool.impl` for your tool, e.g. for a tool named Xyz name it `usethis._tool.impl.xyz`.
140140
- Declare this new submodule in the `.importlinter` configuration to architecturally describe its dependency relationships with other tools' submodules. For example, does your tool integrate with pre-commit? It should be in a higher layer module than the `pre-commit` submodule.
141-
- Define a `usethis._tool.base.Tool` subclass, e.g. for a tool named Xyz, define a class `XyzTool(Tool)`.
142-
- Start by implementing its `name` property method, then work through the other methods. Most method have default implementations, but even in those cases you will need to consider them individually and determine an appropriate implementation. For example, methods which specify the tool's dependencies default to empty dependencies, but you shouldn't rely on this. In the future, it is planned to make it easier for you to distinguish methods which are essential to consider overriding versus those which are usually not necessary to override, see <https://github.com/usethis-python/usethis-python/issues/1316>
141+
- Define a `usethis._tool.base.ToolSpec` subclass, e.g. for a tool named Xyz, define a class `XyzToolSpec(ToolSpec)`.
142+
- Start by implementing its `name` property method, then work through the other methods. Most method have default implementations, but even in those cases you will need to consider them individually and determine an appropriate implementation. For example, methods which specify the tool's dependencies default to empty dependencies, but you shouldn't rely on this.
143+
- Then, define a subclass of the `ToolSpec` subclass you just created, which also subclasses `usethis._tool.base.Tool`, e.g. for a tool named Xyz, define a class `XyzTool(XyzToolSpec, Tool)`. The only method this usually requires a non-default implementation for is `config_spec` to specify which configuration sections should be set up for the tool (and which sections the tool manages). However, you may find it helpful to provide custom implementations for other methods as well, e.g. `print_how_to_use`.
143144
- Include a comment with a URL linking to the tool's source repo for reference.
144145

145146
#### Register your `Tool` subclass

src/usethis/_backend/uv/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from usethis._backend.uv.call import call_uv_subprocess
44
from usethis._backend.uv.errors import UVSubprocessFailedError
55

6-
FALLBACK_UV_VERSION = "0.10.0"
6+
FALLBACK_UV_VERSION = "0.10.8"
77

88

99
def get_uv_version() -> str:

src/usethis/_core/tool.py

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -130,15 +130,13 @@ def use_import_linter(*, remove: bool = False, how: bool = False) -> None:
130130
tool.print_how_to_use()
131131
return
132132

133-
rule_config = tool.get_rule_config()
134-
135133
if not remove:
136134
ensure_dep_declaration_file()
137135

138136
tool.add_dev_deps()
139137
tool.add_configs()
140138
if RuffTool().is_used():
141-
RuffTool().apply_rule_config(rule_config)
139+
RuffTool().apply_rule_config(tool.rule_config)
142140
tool.update_bitbucket_steps()
143141
tool.add_pre_commit_config()
144142

@@ -147,7 +145,7 @@ def use_import_linter(*, remove: bool = False, how: bool = False) -> None:
147145
tool.remove_pre_commit_repo_configs()
148146
tool.remove_bitbucket_steps()
149147
if RuffTool().is_used():
150-
RuffTool().remove_rule_config(rule_config)
148+
RuffTool().remove_rule_config(tool.rule_config)
151149
tool.remove_configs()
152150
tool.remove_dev_deps()
153151
tool.remove_managed_files()
@@ -313,16 +311,14 @@ def use_pytest(*, remove: bool = False, how: bool = False) -> None:
313311
# https://github.com/fpgmaas/deptry/issues/302
314312
add_pytest_dir()
315313

316-
rule_config = tool.get_rule_config()
314+
rule_config = tool.rule_config
317315

318316
for _tool in ALL_TOOLS:
319317
if isinstance(_tool, PytestTool):
320318
continue
321319

322-
config = _tool.get_rule_config()
323-
324-
if config.is_related_to_tests and _tool.is_used():
325-
rule_config |= config.subset_related_to_tests()
320+
if _tool.rule_config.is_related_to_tests and _tool.is_used():
321+
rule_config |= _tool.rule_config.subset_related_to_tests()
326322

327323
if RuffTool().is_used():
328324
RuffTool().apply_rule_config(rule_config)
@@ -334,12 +330,10 @@ def use_pytest(*, remove: bool = False, how: bool = False) -> None:
334330
if CoveragePyTool().is_used():
335331
CoveragePyTool().print_how_to_use()
336332
else:
337-
rule_config = tool.get_rule_config()
338-
339333
PytestTool().remove_bitbucket_steps()
340334

341335
if RuffTool().is_used():
342-
RuffTool().remove_rule_config(rule_config)
336+
RuffTool().remove_rule_config(tool.rule_config)
343337
tool.remove_configs()
344338
tool.remove_test_deps()
345339
remove_pytest_dir() # Last, since this is a manual step
@@ -446,16 +440,15 @@ def use_ruff(
446440
# See docstring. Basically, `usethis docstyle` manages the pydocstyle rules,
447441
# so we want to allow the user to subsequently call `usethis tool ruff` and
448442
# still get non-minimal default rules.
449-
all(tool._is_pydocstyle_rule(rule) for rule in tool.get_selected_rules())
443+
all(tool._is_pydocstyle_rule(rule) for rule in tool.selected_rules())
450444
# Another situation where we add default rules is when there are no rules
451445
# selected yet (and we haven't explicitly been requested to add minimal config).
452-
or not tool.get_selected_rules()
446+
or not tool.selected_rules()
453447
):
454448
rule_config = _get_basic_rule_config()
455449
for _tool in ALL_TOOLS:
456-
tool_rule_config = _tool.get_rule_config()
457-
if not tool_rule_config.empty and _tool.is_used():
458-
rule_config |= tool_rule_config
450+
if not _tool.rule_config.empty and _tool.is_used():
451+
rule_config |= _tool.rule_config
459452
else:
460453
rule_config = RuleConfig()
461454

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pre_commit_raw_cmd = "pre-commit run -a"

0 commit comments

Comments
 (0)