Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Tool implementations are defined in classes in the `usethis._tool.impl` module.
- 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.
- Mark all methods in your `ToolSpec` subclass with the `@typing.final` decorator. This prevents the methods from being accidentally overridden in the `Tool` subclass.
- 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`.
- Mark all methods in your `Tool` subclass with `@final` as well, to prevent further subclassing from overriding them.
- Mark your `Tool` subclass with `@final` as well, to prevent further subclassing.

#### Register your `Tool` subclass

Expand Down
12 changes: 11 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,6 @@ reportImplicitStringConcatenation = false
reportMissingParameterType = false
reportMissingTypeArgument = false
reportPrivateUsage = false
reportUnannotatedClassAttribute = false
reportUnknownArgumentType = false
reportUnknownMemberType = false
reportUnknownVariableType = false
Expand All @@ -225,11 +224,22 @@ reportUnusedCallResult = false

[[tool.basedpyright.executionEnvironments]]
root = "tests"
reportUnannotatedClassAttribute = false
reportUnknownLambdaType = false
reportUnknownParameterType = false
reportUnreachable = false
reportUnusedFunction = false
reportUnusedParameter = false

[[tool.basedpyright.executionEnvironments]]
# Particularly interested in avoiding the auto-generated schema script
root = "src/usethis/_integrations/ci/bitbucket"
reportUnannotatedClassAttribute = false

[[tool.basedpyright.executionEnvironments]]
# Particularly interested in avoiding the auto-generated schema script
root = "src/usethis/_integrations/pre_commit"
reportUnannotatedClassAttribute = false

[tool.sync-with-uv.repo-to-package]
"https://github.com/astral-sh/uv-pre-commit" = "uv"
2 changes: 1 addition & 1 deletion src/usethis/_core/docstyle.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ def use_docstyle(style: DocStyleEnum) -> None:

RuffTool().set_docstyle(style.value)

if not RuffTool()._are_pydocstyle_rules_selected():
if not RuffTool().are_pydocstyle_rules_selected():
RuffTool().select_rules(["D2", "D3", "D4"])
1 change: 1 addition & 0 deletions src/usethis/_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class UsethisFileManager(Generic[DocumentT], metaclass=ABCMeta):
# https://github.com/python/mypy/issues/5144
# The Any in this expression should be identified with DocumentT
_content_by_path: ClassVar[dict[Path, Any | None]] = {}
path: Path

@property
@abstractmethod
Expand Down
2 changes: 1 addition & 1 deletion src/usethis/_tool/impl/base/codespell.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from usethis._tool.impl.spec.codespell import CodespellToolSpec


@final
class CodespellTool(CodespellToolSpec, Tool):
@final
def print_how_to_use(self) -> None:
how_print(f"Run '{self.how_to_use_cmd()}' to run the {self.name} spellchecker.")
3 changes: 1 addition & 2 deletions src/usethis/_tool/impl/base/coverage_py.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@
from usethis._types.deps import Dependency


@final
class CoveragePyTool(CoveragePyToolSpec, Tool):
@final
def test_deps(self, *, unconditional: bool = False) -> list[Dependency]:
deps = [Dependency(name="coverage", extras=frozenset({"toml"}))]
if unconditional or is_likely_used(PytestToolSpec()):
deps += [Dependency(name="pytest-cov")]
return deps

@final
def print_how_to_use(self) -> None:
backend = get_backend()

Expand Down
6 changes: 1 addition & 5 deletions src/usethis/_tool/impl/base/deptry.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@
from usethis._io import KeyValueFileManager


@final
class DeptryTool(DeptryToolSpec, Tool):
@final
def select_rules(self, rules: Sequence[Rule]) -> bool:
"""Does nothing for deptry - all rules are automatically enabled by default."""
if rules:
info_print(f"All {self.name} rules are always implicitly selected.")
return False

@final
def selected_rules(self) -> list[Rule]:
"""No notion of selection for deptry.

Expand All @@ -33,12 +32,10 @@ def selected_rules(self) -> list[Rule]:
"""
return []

@final
def deselect_rules(self, rules: Sequence[Rule]) -> bool:
"""Does nothing for deptry - all rules are automatically enabled by default."""
return False

@final
def ignored_rules(self) -> list[Rule]:
(file_manager,) = self.get_active_config_file_managers()
keys = self._get_ignore_keys(file_manager)
Expand All @@ -49,7 +46,6 @@ def ignored_rules(self) -> list[Rule]:

return rules

@final
def _get_ignore_keys(self, file_manager: KeyValueFileManager[object]) -> list[str]:
"""Get the keys for the ignored rules in the given file manager."""
if isinstance(file_manager, PyprojectTOMLManager):
Expand Down
3 changes: 1 addition & 2 deletions src/usethis/_tool/impl/base/import_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@
from usethis._tool.rule import Rule


@final
class ImportLinterTool(ImportLinterToolSpec, Tool):
@final
def is_used(self) -> bool:
"""Check if the Import Linter tool is used in the project."""
# We suppress the warning about assumptions regarding the package name.
# See _importlinter_warn_no_packages_found
with usethis_config.set(quiet=True):
return super().is_used()

@final
def print_how_to_use(self) -> None:
if not _is_inp_rule_selected():
# If Ruff is used, we enable the INP rules instead.
Expand Down
2 changes: 1 addition & 1 deletion src/usethis/_tool/impl/base/mkdocs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
from usethis._types.backend import BackendEnum


@final
class MkDocsTool(MkDocsToolSpec, Tool):
@final
def print_how_to_use(self) -> None:
backend = get_backend()
if backend is BackendEnum.uv and is_uv_used():
Expand Down
7 changes: 1 addition & 6 deletions src/usethis/_tool/impl/base/pre_commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,14 @@
from usethis._python.version import PythonVersion


@final
class PreCommitTool(PreCommitToolSpec, Tool):
@final
def is_used(self) -> bool:
return is_pre_commit_used()

@final
def print_how_to_use(self) -> None:
how_print(f"Run '{self.how_to_use_cmd()}' to run the hooks manually.")

@final
def get_bitbucket_steps(
self,
*,
Expand Down Expand Up @@ -66,7 +64,6 @@ def get_bitbucket_steps(
else:
assert_never(backend)

@final
def update_bitbucket_steps(self, *, matrix_python: bool = True) -> None:
"""Add Bitbucket steps associated with pre-commit, and remove outdated ones.

Expand All @@ -78,10 +75,8 @@ def update_bitbucket_steps(self, *, matrix_python: bool = True) -> None:
"""
self._unconditional_update_bitbucket_steps(matrix_python=matrix_python)

@final
def migrate_config_to_pre_commit(self) -> None:
pass

@final
def migrate_config_from_pre_commit(self) -> None:
pass
3 changes: 3 additions & 0 deletions src/usethis/_tool/impl/base/pyproject_fmt.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from __future__ import annotations

from typing import final

from usethis._tool.base import Tool
from usethis._tool.impl.spec.pyproject_fmt import PyprojectFmtToolSpec


@final
class PyprojectFmtTool(PyprojectFmtToolSpec, Tool):
pass
3 changes: 1 addition & 2 deletions src/usethis/_tool/impl/base/pyproject_toml.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,14 @@
]


@final
class PyprojectTOMLTool(PyprojectTOMLToolSpec, Tool):
@final
def print_how_to_use(self) -> None:
how_print("Populate 'pyproject.toml' with the project configuration.")
info_print(
"Learn more at https://packaging.python.org/en/latest/guides/writing-pyproject-toml/"
)

@final
def remove_managed_files(self) -> None:
# https://github.com/usethis-python/usethis-python/issues/416
# We need to step through the tools and see if pyproject.toml is the active
Expand Down
7 changes: 1 addition & 6 deletions src/usethis/_tool/impl/base/pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,21 @@
_PYTEST_PIP_CMD = "pip install pytest"


@final
class PytestTool(PytestToolSpec, Tool):
@final
def test_deps(self, *, unconditional: bool = False) -> list[Dependency]:
deps = [Dependency(name="pytest")]
if unconditional or is_likely_used(CoveragePyToolSpec()):
deps += [Dependency(name="pytest-cov")]
return deps

@final
def print_how_to_use(self) -> None:
how_print(
"Add test files to the '/tests' directory with the format 'test_*.py'."
)
how_print("Add test functions with the format 'test_*()'.")
how_print(f"Run '{self.how_to_use_cmd()}' to run the tests.")

@final
def get_active_config_file_managers(self) -> set[KeyValueFileManager[object]]:
# This is a variant of the "first" method
config_spec = self.config_spec()
Expand Down Expand Up @@ -106,7 +104,6 @@ def get_active_config_file_managers(self) -> set[KeyValueFileManager[object]]:
raise NotImplementedError(msg)
return {preferred_file_manager}

@final
def get_bitbucket_steps(
self, *, matrix_python: bool = True
) -> list[bitbucket_schema.Step]:
Expand Down Expand Up @@ -152,7 +149,6 @@ def get_bitbucket_steps(
steps.append(step)
return steps

@final
def get_managed_bitbucket_step_names(self) -> list[str]:
names = set()
for step in get_steps_in_default():
Expand All @@ -167,7 +163,6 @@ def get_managed_bitbucket_step_names(self) -> list[str]:

return sorted(names)

@final
def update_bitbucket_steps(self, *, matrix_python: bool = True) -> None:
"""Update the pytest-related Bitbucket Pipelines steps.

Expand Down
2 changes: 1 addition & 1 deletion src/usethis/_tool/impl/base/requirements_txt.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
from usethis._types.backend import BackendEnum


@final
class RequirementsTxtTool(RequirementsTxtToolSpec, Tool):
@final
def print_how_to_use(self) -> None:
install_method = self.get_install_method()
backend = get_backend()
Expand Down
Loading
Loading