Skip to content

Commit 211b0d2

Browse files
Introduce covariant DocumentProtocol to enable reportMissingTypeArgument in basedpyright (#1588)
1 parent 0d5939f commit 211b0d2

26 files changed

Lines changed: 141 additions & 111 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,6 @@ ignore = [ "src/usethis/_version.py" ]
212212
reportAny = false
213213
reportExplicitAny = false
214214
reportImplicitStringConcatenation = false
215-
reportMissingTypeArgument = false
216215
reportUnknownArgumentType = false
217216
reportUnknownMemberType = false
218217
reportUnknownVariableType = false
@@ -232,6 +231,7 @@ reportUnusedParameter = false
232231
[[tool.basedpyright.executionEnvironments]]
233232
# Particularly interested in avoiding the auto-generated schema script
234233
root = "src/usethis/_integrations/pre_commit"
234+
reportMissingTypeArgument = false
235235
reportUnannotatedClassAttribute = false
236236

237237
[tool.sync-with-uv.repo-to-package]

src/usethis/_backend/uv/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def get_uv_version() -> str:
1616
except UVSubprocessFailedError:
1717
return FALLBACK_UV_VERSION
1818

19-
json_dict: dict = json.loads(json_str)
19+
json_dict: dict[str, str] = json.loads(json_str)
2020
return json_dict.get("version", FALLBACK_UV_VERSION)
2121

2222

src/usethis/_file/ini/io_.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from usethis._file.types_ import Key
4040

4141

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

4545
@override

src/usethis/_file/manager.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
from abc import ABCMeta, abstractmethod
6-
from typing import TYPE_CHECKING, Any, Generic, TypeVar
6+
from typing import TYPE_CHECKING, Any, Generic, Protocol, TypeVar, cast
77

88
from typing_extensions import override
99

@@ -21,7 +21,13 @@
2121
from usethis._file.types_ import Key
2222

2323

24-
DocumentT = TypeVar("DocumentT")
24+
class Document(Protocol):
25+
"""Protocol for the document type managed by FileManager."""
26+
27+
pass
28+
29+
30+
DocumentT = TypeVar("DocumentT", covariant=True)
2531

2632

2733
class UnexpectedFileOpenError(UsethisError):
@@ -107,7 +113,7 @@ def get(self) -> DocumentT:
107113
else:
108114
return self._content
109115

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

172178
@property
173179
def _content(self) -> DocumentT | None:
174-
return self._content_by_path.get(self.path)
180+
return cast("DocumentT | None", self._content_by_path.get(self.path))
175181

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

199205

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

203211
@abstractmethod

src/usethis/_file/toml/io_.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
from usethis._file.types_ import Key
4646

4747

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

5151
_content_by_path: ClassVar[dict[Path, TOMLDocument | None]] = {}

src/usethis/_file/yaml/io_.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,19 @@
4747
from usethis._file.yaml.typing_ import YAMLLiteral
4848

4949

50-
class YAMLFileManager(KeyValueFileManager, metaclass=ABCMeta):
50+
@dataclass
51+
class YAMLDocument:
52+
"""A dataclass to represent a YAML document in memory.
53+
54+
Attributes:
55+
content: The content of the YAML document as a ruamel.yaml object.
56+
"""
57+
58+
content: YAMLLiteral
59+
roundtripper: ruamel.yaml.YAML
60+
61+
62+
class YAMLFileManager(KeyValueFileManager[YAMLDocument], metaclass=ABCMeta):
5163
"""An abstract class for managing YAML files."""
5264

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

433445

434-
@dataclass
435-
class YAMLDocument:
436-
"""A dataclass to represent a YAML document in memory.
437-
438-
Attributes:
439-
content: The content of the YAML document as a ruamel.yaml object.
440-
"""
441-
442-
content: YAMLLiteral
443-
roundtripper: ruamel.yaml.YAML
444-
445-
446446
@contextmanager
447447
def edit_yaml(
448448
yaml_path: Path,

src/usethis/_tool/base.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
if TYPE_CHECKING:
3232
from collections.abc import Sequence
3333

34-
from usethis._file.manager import KeyValueFileManager
34+
from usethis._file.manager import Document, KeyValueFileManager
3535
from usethis._file.types_ import Key
3636
from usethis._tool.config import ConfigItem
3737
from usethis._tool.rule import Rule
@@ -256,7 +256,7 @@ def _add_config_item(
256256
self,
257257
config_item: ConfigItem,
258258
*,
259-
file_managers: set[KeyValueFileManager[object]],
259+
file_managers: set[KeyValueFileManager[Document]],
260260
) -> bool:
261261
"""Add a specific configuration item using specified file managers.
262262
@@ -416,7 +416,9 @@ def get_install_method(self) -> Literal["devdep", "pre-commit"] | None:
416416
return "pre-commit"
417417
return None
418418

419-
def _get_select_keys(self, file_manager: KeyValueFileManager[object]) -> list[str]:
419+
def _get_select_keys(
420+
self, file_manager: KeyValueFileManager[Document]
421+
) -> list[str]:
420422
"""Get the configuration keys for selected rules.
421423
422424
This is optional - tools that don't support rule selection can leave this
@@ -482,7 +484,9 @@ def select_rules(self, rules: Sequence[Rule]) -> bool:
482484

483485
return True
484486

485-
def _get_ignore_keys(self, file_manager: KeyValueFileManager[object]) -> list[str]:
487+
def _get_ignore_keys(
488+
self, file_manager: KeyValueFileManager[Document]
489+
) -> list[str]:
486490
"""Get the configuration keys for ignored rules.
487491
488492
Args:

src/usethis/_tool/config.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from pydantic import BaseModel, InstanceOf
1010

1111
from usethis._config import usethis_config
12-
from usethis._file.manager import KeyValueFileManager
12+
from usethis._file.manager import Document, KeyValueFileManager
1313
from usethis._file.pyproject_toml.io_ import PyprojectTOMLManager
1414
from usethis._file.types_ import Key
1515
from usethis._init import ensure_pyproject_toml
@@ -43,14 +43,14 @@ class ConfigSpec(BaseModel):
4343
config_items: A list of configuration items that can be managed by the tool.
4444
"""
4545

46-
file_manager_by_relative_path: dict[Path, InstanceOf[KeyValueFileManager]]
46+
file_manager_by_relative_path: dict[Path, InstanceOf[KeyValueFileManager[Document]]]
4747
resolution: ResolutionT
4848
config_items: list[ConfigItem]
4949

5050
@classmethod
5151
def from_flat(
5252
cls,
53-
file_managers: list[KeyValueFileManager[object]],
53+
file_managers: list[KeyValueFileManager[Document]],
5454
resolution: ResolutionT,
5555
config_items: list[ConfigItem],
5656
) -> Self:
@@ -149,7 +149,7 @@ def paths(self) -> set[Path]:
149149
return {(usethis_config.cpd() / path).resolve() for path in self.root}
150150

151151

152-
def ensure_managed_file_exists(file_manager: FileManager[object]) -> None:
152+
def ensure_managed_file_exists(file_manager: FileManager[Document]) -> None:
153153
"""Ensure a file manager's managed file exists."""
154154
if isinstance(file_manager, PyprojectTOMLManager):
155155
ensure_pyproject_toml()

src/usethis/_tool/impl/base/deptry.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
if TYPE_CHECKING:
1717
from collections.abc import Sequence
1818

19-
from usethis._file.manager import KeyValueFileManager
19+
from usethis._file.manager import Document, KeyValueFileManager
2020

2121

2222
@final
@@ -54,7 +54,9 @@ def ignored_rules(self) -> list[Rule]:
5454
return rules
5555

5656
@override
57-
def _get_ignore_keys(self, file_manager: KeyValueFileManager[object]) -> list[str]:
57+
def _get_ignore_keys(
58+
self, file_manager: KeyValueFileManager[Document]
59+
) -> list[str]:
5860
"""Get the keys for the ignored rules in the given file manager."""
5961
if isinstance(file_manager, PyprojectTOMLManager):
6062
return ["tool", "deptry", "ignore"]

src/usethis/_tool/impl/base/pytest.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from usethis._types.deps import Dependency
1717

1818
if TYPE_CHECKING:
19-
from usethis._file.manager import KeyValueFileManager
19+
from usethis._file.manager import Document, KeyValueFileManager
2020

2121

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

3939
@override
40-
def get_active_config_file_managers(self) -> set[KeyValueFileManager[object]]:
40+
def get_active_config_file_managers(
41+
self,
42+
) -> set[KeyValueFileManager[Document]]:
4143
# This is a variant of the "first" method
4244
config_spec = self.config_spec()
4345
if config_spec.resolution != "bespoke":

0 commit comments

Comments
 (0)