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 pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@ ignore = [ "src/usethis/_version.py" ]
reportAny = false
reportExplicitAny = false
reportImplicitStringConcatenation = false
reportMissingTypeArgument = false
reportUnknownArgumentType = false
reportUnknownMemberType = false
reportUnknownVariableType = false
Expand All @@ -232,6 +231,7 @@ reportUnusedParameter = false
[[tool.basedpyright.executionEnvironments]]
# Particularly interested in avoiding the auto-generated schema script
root = "src/usethis/_integrations/pre_commit"
reportMissingTypeArgument = false
reportUnannotatedClassAttribute = false

[tool.sync-with-uv.repo-to-package]
Expand Down
2 changes: 1 addition & 1 deletion src/usethis/_backend/uv/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def get_uv_version() -> str:
except UVSubprocessFailedError:
return FALLBACK_UV_VERSION

json_dict: dict = json.loads(json_str)
json_dict: dict[str, str] = json.loads(json_str)
return json_dict.get("version", FALLBACK_UV_VERSION)


Expand Down
2 changes: 1 addition & 1 deletion src/usethis/_file/ini/io_.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from usethis._file.types_ import Key


class INIFileManager(KeyValueFileManager, metaclass=ABCMeta):
class INIFileManager(KeyValueFileManager[INIDocument], metaclass=ABCMeta):
_content_by_path: ClassVar[dict[Path, INIDocument | None]] = {}

@override
Expand Down
18 changes: 13 additions & 5 deletions src/usethis/_file/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

from abc import ABCMeta, abstractmethod
from typing import TYPE_CHECKING, Any, Generic, TypeVar
from typing import TYPE_CHECKING, Any, Generic, Protocol, TypeVar, cast

from typing_extensions import override

Expand All @@ -21,7 +21,13 @@
from usethis._file.types_ import Key


DocumentT = TypeVar("DocumentT")
class Document(Protocol):
"""Protocol for the document type managed by FileManager."""

pass


DocumentT = TypeVar("DocumentT", covariant=True)


class UnexpectedFileOpenError(UsethisError):
Expand Down Expand Up @@ -107,7 +113,7 @@ def get(self) -> DocumentT:
else:
return self._content

def commit(self, document: DocumentT) -> None:
def commit(self, document: DocumentT) -> None: # pyright: ignore[reportGeneralTypeIssues] not modifying DocumentT so safe to use covariant type variable here
"""Store the given document in memory for deferred writing."""
self._validate_lock()
self._content = document
Expand Down Expand Up @@ -171,7 +177,7 @@ def _parse_content(self, content: str) -> DocumentT:

@property
def _content(self) -> DocumentT | None:
return self._content_by_path.get(self.path)
return cast("DocumentT | None", self._content_by_path.get(self.path))

@_content.setter
def _content(self, value: DocumentT | None) -> None:
Expand All @@ -197,7 +203,9 @@ def unlock(self) -> None:
self._dirty_by_path.pop(self.path, None)


class KeyValueFileManager(FileManager, Generic[DocumentT], metaclass=ABCMeta):
class KeyValueFileManager(
FileManager[DocumentT], Generic[DocumentT], metaclass=ABCMeta
):
"""A manager for files which store (at least some) values in key-value mappings."""

@abstractmethod
Expand Down
2 changes: 1 addition & 1 deletion src/usethis/_file/toml/io_.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
from usethis._file.types_ import Key


class TOMLFileManager(KeyValueFileManager, metaclass=ABCMeta):
class TOMLFileManager(KeyValueFileManager[TOMLDocument], metaclass=ABCMeta):
"""An abstract class for managing TOML files."""

_content_by_path: ClassVar[dict[Path, TOMLDocument | None]] = {}
Expand Down
26 changes: 13 additions & 13 deletions src/usethis/_file/yaml/io_.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,19 @@
from usethis._file.yaml.typing_ import YAMLLiteral


class YAMLFileManager(KeyValueFileManager, metaclass=ABCMeta):
@dataclass
class YAMLDocument:
"""A dataclass to represent a YAML document in memory.

Attributes:
content: The content of the YAML document as a ruamel.yaml object.
"""

content: YAMLLiteral
roundtripper: ruamel.yaml.YAML


class YAMLFileManager(KeyValueFileManager[YAMLDocument], metaclass=ABCMeta):
"""An abstract class for managing YAML files."""

_content_by_path: ClassVar[dict[Path, YAMLDocument | None]] = {}
Expand Down Expand Up @@ -431,18 +443,6 @@ def _validate_keys(keys: Sequence[Key]) -> list[str]:
return so_far_keys


@dataclass
class YAMLDocument:
"""A dataclass to represent a YAML document in memory.

Attributes:
content: The content of the YAML document as a ruamel.yaml object.
"""

content: YAMLLiteral
roundtripper: ruamel.yaml.YAML


@contextmanager
def edit_yaml(
yaml_path: Path,
Expand Down
12 changes: 8 additions & 4 deletions src/usethis/_tool/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
if TYPE_CHECKING:
from collections.abc import Sequence

from usethis._file.manager import KeyValueFileManager
from usethis._file.manager import Document, KeyValueFileManager
from usethis._file.types_ import Key
from usethis._tool.config import ConfigItem
from usethis._tool.rule import Rule
Expand Down Expand Up @@ -256,7 +256,7 @@ def _add_config_item(
self,
config_item: ConfigItem,
*,
file_managers: set[KeyValueFileManager[object]],
file_managers: set[KeyValueFileManager[Document]],
) -> bool:
"""Add a specific configuration item using specified file managers.

Expand Down Expand Up @@ -416,7 +416,9 @@ def get_install_method(self) -> Literal["devdep", "pre-commit"] | None:
return "pre-commit"
return None

def _get_select_keys(self, file_manager: KeyValueFileManager[object]) -> list[str]:
def _get_select_keys(
self, file_manager: KeyValueFileManager[Document]
) -> list[str]:
"""Get the configuration keys for selected rules.

This is optional - tools that don't support rule selection can leave this
Expand Down Expand Up @@ -482,7 +484,9 @@ def select_rules(self, rules: Sequence[Rule]) -> bool:

return True

def _get_ignore_keys(self, file_manager: KeyValueFileManager[object]) -> list[str]:
def _get_ignore_keys(
self, file_manager: KeyValueFileManager[Document]
) -> list[str]:
"""Get the configuration keys for ignored rules.

Args:
Expand Down
8 changes: 4 additions & 4 deletions src/usethis/_tool/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pydantic import BaseModel, InstanceOf

from usethis._config import usethis_config
from usethis._file.manager import KeyValueFileManager
from usethis._file.manager import Document, KeyValueFileManager
from usethis._file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._file.types_ import Key
from usethis._init import ensure_pyproject_toml
Expand Down Expand Up @@ -43,14 +43,14 @@ class ConfigSpec(BaseModel):
config_items: A list of configuration items that can be managed by the tool.
"""

file_manager_by_relative_path: dict[Path, InstanceOf[KeyValueFileManager]]
file_manager_by_relative_path: dict[Path, InstanceOf[KeyValueFileManager[Document]]]
resolution: ResolutionT
config_items: list[ConfigItem]

@classmethod
def from_flat(
cls,
file_managers: list[KeyValueFileManager[object]],
file_managers: list[KeyValueFileManager[Document]],
resolution: ResolutionT,
config_items: list[ConfigItem],
) -> Self:
Expand Down Expand Up @@ -149,7 +149,7 @@ def paths(self) -> set[Path]:
return {(usethis_config.cpd() / path).resolve() for path in self.root}


def ensure_managed_file_exists(file_manager: FileManager[object]) -> None:
def ensure_managed_file_exists(file_manager: FileManager[Document]) -> None:
"""Ensure a file manager's managed file exists."""
if isinstance(file_manager, PyprojectTOMLManager):
ensure_pyproject_toml()
Expand Down
6 changes: 4 additions & 2 deletions src/usethis/_tool/impl/base/deptry.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
if TYPE_CHECKING:
from collections.abc import Sequence

from usethis._file.manager import KeyValueFileManager
from usethis._file.manager import Document, KeyValueFileManager


@final
Expand Down Expand Up @@ -54,7 +54,9 @@ def ignored_rules(self) -> list[Rule]:
return rules

@override
def _get_ignore_keys(self, file_manager: KeyValueFileManager[object]) -> list[str]:
def _get_ignore_keys(
self, file_manager: KeyValueFileManager[Document]
) -> list[str]:
"""Get the keys for the ignored rules in the given file manager."""
if isinstance(file_manager, PyprojectTOMLManager):
return ["tool", "deptry", "ignore"]
Expand Down
6 changes: 4 additions & 2 deletions src/usethis/_tool/impl/base/pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from usethis._types.deps import Dependency

if TYPE_CHECKING:
from usethis._file.manager import KeyValueFileManager
from usethis._file.manager import Document, KeyValueFileManager


@final
Expand All @@ -37,7 +37,9 @@ def print_how_to_use(self) -> None:
how_print(f"Run '{self.how_to_use_cmd()}' to run the tests.")

@override
def get_active_config_file_managers(self) -> set[KeyValueFileManager[object]]:
def get_active_config_file_managers(
self,
) -> set[KeyValueFileManager[Document]]:
# This is a variant of the "first" method
config_spec = self.config_spec()
if config_spec.resolution != "bespoke":
Expand Down
14 changes: 9 additions & 5 deletions src/usethis/_tool/impl/base/ruff.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
if TYPE_CHECKING:
from collections.abc import Sequence

from usethis._file.manager import KeyValueFileManager
from usethis._file.manager import Document, KeyValueFileManager
from usethis._tool.rule import RuleConfig


Expand Down Expand Up @@ -300,7 +300,9 @@ def is_pydocstyle_rule(rule: Rule) -> bool:
return [d for d in rule if d.isalpha()] == ["D"]

@override
def _get_select_keys(self, file_manager: KeyValueFileManager[object]) -> list[str]:
def _get_select_keys(
self, file_manager: KeyValueFileManager[Document]
) -> list[str]:
"""Get the keys for the selected rules in the given file manager."""
if isinstance(file_manager, PyprojectTOMLManager):
return ["tool", "ruff", "lint", "select"]
Expand All @@ -310,7 +312,9 @@ def _get_select_keys(self, file_manager: KeyValueFileManager[object]) -> list[st
return super()._get_select_keys(file_manager)

@override
def _get_ignore_keys(self, file_manager: KeyValueFileManager[object]) -> list[str]:
def _get_ignore_keys(
self, file_manager: KeyValueFileManager[Document]
) -> list[str]:
"""Get the keys for the ignored rules in the given file manager."""
if isinstance(file_manager, PyprojectTOMLManager):
return ["tool", "ruff", "lint", "ignore"]
Expand All @@ -320,7 +324,7 @@ def _get_ignore_keys(self, file_manager: KeyValueFileManager[object]) -> list[st
return super()._get_ignore_keys(file_manager)

def _get_per_file_ignore_keys(
self, file_manager: KeyValueFileManager[object], *, glob: str
self, file_manager: KeyValueFileManager[Document], *, glob: str
) -> list[str]:
"""Get the keys for the per-file ignored rules in the given file manager."""
if isinstance(file_manager, PyprojectTOMLManager):
Expand All @@ -335,7 +339,7 @@ def _get_per_file_ignore_keys(
raise NotImplementedError(msg)

def _get_docstyle_keys(
self, file_manager: KeyValueFileManager[object]
self, file_manager: KeyValueFileManager[Document]
) -> list[str]:
"""Get the keys for the docstyle rules in the given file manager."""
if isinstance(file_manager, PyprojectTOMLManager):
Expand Down
4 changes: 2 additions & 2 deletions src/usethis/_tool/impl/spec/codespell.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from usethis._types.deps import Dependency

if TYPE_CHECKING:
from usethis._file.manager import KeyValueFileManager
from usethis._file.manager import Document, KeyValueFileManager


class CodespellToolSpec(ToolSpec):
Expand All @@ -41,7 +41,7 @@ def meta(self) -> ToolMeta:

@override
@final
def preferred_file_manager(self) -> KeyValueFileManager[object]:
def preferred_file_manager(self) -> KeyValueFileManager[Document]:
if (usethis_config.cpd() / "pyproject.toml").exists():
return PyprojectTOMLManager()
return DotCodespellRCManager()
Expand Down
4 changes: 2 additions & 2 deletions src/usethis/_tool/impl/spec/coverage_py.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from usethis._tool.config import ConfigEntry, ConfigItem, ConfigSpec

if TYPE_CHECKING:
from usethis._file.manager import KeyValueFileManager
from usethis._file.manager import Document, KeyValueFileManager


class CoveragePyToolSpec(ToolSpec):
Expand All @@ -36,7 +36,7 @@ def meta(self) -> ToolMeta:

@override
@final
def preferred_file_manager(self) -> KeyValueFileManager[object]:
def preferred_file_manager(self) -> KeyValueFileManager[Document]:
if (usethis_config.cpd() / "pyproject.toml").exists():
return PyprojectTOMLManager()
return DotCoverageRCManager()
Expand Down
6 changes: 3 additions & 3 deletions src/usethis/_tool/impl/spec/import_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from usethis._types.deps import Dependency

if TYPE_CHECKING:
from usethis._file.manager import KeyValueFileManager
from usethis._file.manager import Document, KeyValueFileManager
from usethis._tool.config import ResolutionT

IMPORT_LINTER_CONTRACT_MIN_MODULE_COUNT = 3
Expand Down Expand Up @@ -65,7 +65,7 @@ def dev_deps(self, *, unconditional: bool = False) -> list[Dependency]:

@override
@final
def preferred_file_manager(self) -> KeyValueFileManager[object]:
def preferred_file_manager(self) -> KeyValueFileManager[Document]:
if (usethis_config.cpd() / "pyproject.toml").exists():
return PyprojectTOMLManager()
return DotImportLinterManager()
Expand Down Expand Up @@ -304,7 +304,7 @@ def _get_resolution(self) -> ResolutionT:
@final
def _get_file_manager_by_relative_path(
self,
) -> dict[Path, KeyValueFileManager[object]]:
) -> dict[Path, KeyValueFileManager[Document]]:
return {
Path("setup.cfg"): SetupCFGManager(),
Path(".importlinter"): DotImportLinterManager(),
Expand Down
4 changes: 2 additions & 2 deletions src/usethis/_tool/impl/spec/mkdocs.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from usethis._types.deps import Dependency

if TYPE_CHECKING:
from usethis._file.manager import KeyValueFileManager
from usethis._file.manager import Document, KeyValueFileManager


class MkDocsToolSpec(ToolSpec):
Expand All @@ -40,7 +40,7 @@ def doc_deps(self, *, unconditional: bool = False) -> list[Dependency]:

@override
@final
def preferred_file_manager(self) -> KeyValueFileManager[object]:
def preferred_file_manager(self) -> KeyValueFileManager[Document]:
"""If there is no currently active config file, this is the preferred one."""
# Should set the mkdocs.yml file manager as the preferred one
return MkDocsYMLManager()
Expand Down
4 changes: 2 additions & 2 deletions src/usethis/_tool/impl/spec/pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from usethis._tool.rule import RuleConfig

if TYPE_CHECKING:
from usethis._file.manager import KeyValueFileManager
from usethis._file.manager import Document, KeyValueFileManager


class PytestToolSpec(ToolSpec):
Expand All @@ -45,7 +45,7 @@ def raw_cmd(self) -> str:

@override
@final
def preferred_file_manager(self) -> KeyValueFileManager[object]:
def preferred_file_manager(self) -> KeyValueFileManager[Document]:
if (usethis_config.cpd() / "pyproject.toml").exists():
return PyprojectTOMLManager()
return PytestINIManager()
Expand Down
Loading
Loading