diff --git a/pyproject.toml b/pyproject.toml index 6fe0b8bf..6ab1922f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -224,7 +224,6 @@ reportUnannotatedClassAttribute = false reportUnknownArgumentType = false reportUnknownLambdaType = false reportUnknownMemberType = false -reportUnknownParameterType = false reportUnknownVariableType = false reportUnnecessaryIsInstance = false reportUnusedCallResult = false @@ -232,6 +231,7 @@ reportUnusedParameter = false [[tool.basedpyright.executionEnvironments]] root = "tests" +reportUnknownParameterType = false reportUnreachable = false reportUnusedFunction = false diff --git a/src/usethis/_file/yaml/update.py b/src/usethis/_file/yaml/update.py index 389f416a..c44314e7 100644 --- a/src/usethis/_file/yaml/update.py +++ b/src/usethis/_file/yaml/update.py @@ -1,7 +1,7 @@ from __future__ import annotations from difflib import SequenceMatcher -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, TypeVar from ruamel.yaml.comments import ( CommentedMap, @@ -12,6 +12,8 @@ from usethis._file.yaml.typing_ import YAMLLiteral +_T = TypeVar("_T") + def update_ruamel_yaml_map( cmap: YAMLLiteral | dict[str, Any], @@ -64,7 +66,7 @@ def update_ruamel_yaml_map( cmap[key] = cmap_copy[key] -def lcs_list_update(original: list, new: list) -> None: +def lcs_list_update(original: list[_T], new: list[_T]) -> None: """Update in-place using a longest common subsequence solver. This makes `original` identical to `new`, but respects subtypes of list such as diff --git a/src/usethis/_io.py b/src/usethis/_io.py index 9ebd2dda..ddb855aa 100644 --- a/src/usethis/_io.py +++ b/src/usethis/_io.py @@ -177,7 +177,7 @@ def unlock(self) -> None: self._content_by_path.pop(self.path, None) -Key: TypeAlias = str | re.Pattern +Key: TypeAlias = str | re.Pattern[str] class KeyValueFileManager(UsethisFileManager, Generic[DocumentT]): diff --git a/src/usethis/_pipeweld/containers.py b/src/usethis/_pipeweld/containers.py index 21261190..0182dc07 100644 --- a/src/usethis/_pipeweld/containers.py +++ b/src/usethis/_pipeweld/containers.py @@ -15,10 +15,10 @@ class Series(RootModel[list["Series | Parallel | DepGroup | str"]]): def __hash__(self): return hash((_HASH_SALT, tuple(self.root))) - def __getitem__(self, item): + def __getitem__(self, item: int) -> Series | Parallel | DepGroup | str: return self.root[item] - def __setitem__(self, item, value): + def __setitem__(self, item: int, value: Series | Parallel | DepGroup | str) -> None: self.root[item] = value def __eq__(self, other: Any): diff --git a/src/usethis/_pipeweld/func.py b/src/usethis/_pipeweld/func.py index e30d6967..dbb8a194 100644 --- a/src/usethis/_pipeweld/func.py +++ b/src/usethis/_pipeweld/func.py @@ -250,10 +250,11 @@ def _insert_before_postrequisites( idx=-1, predecessor=predecessor, ) - if len(container) == 1 and container[0] == _union( - successor_component, self.step - ): - component[idx + 1] = _union(successor_component, self.step) + union = _union(successor_component, self.step) + if len(container) == 1 and container[0] == union: + if union is None: + raise NotImplementedError + component[idx + 1] = union return instructions elif isinstance(successor_component, Parallel | DepGroup | str): diff --git a/src/usethis/_tool/base.py b/src/usethis/_tool/base.py index 279e1952..8f21d1da 100644 --- a/src/usethis/_tool/base.py +++ b/src/usethis/_tool/base.py @@ -235,7 +235,7 @@ def migrate_config_from_pre_commit(self) -> None: if pre_commit_config.inform_how_to_use_on_migrate: self.print_how_to_use() - def get_active_config_file_managers(self) -> set[KeyValueFileManager]: + def get_active_config_file_managers(self) -> set[KeyValueFileManager[object]]: """Get file managers for all active configuration files. Active configuration files are just those that we expect to use based on our @@ -257,8 +257,8 @@ def _get_active_config_file_managers_from_resolution( self, resolution: ResolutionT, *, - file_manager_by_relative_path: dict[Path, KeyValueFileManager], - ) -> set[KeyValueFileManager]: + file_manager_by_relative_path: dict[Path, KeyValueFileManager[object]], + ) -> set[KeyValueFileManager[object]]: if resolution == "first": # N.B. keep this roughly in sync with the bespoke logic for pytest # since that logic is based on this logic. @@ -334,7 +334,10 @@ def add_configs(self) -> None: already_added = True def _add_config_item( - self, config_item: ConfigItem, *, file_managers: set[KeyValueFileManager] + self, + config_item: ConfigItem, + *, + file_managers: set[KeyValueFileManager[object]], ) -> bool: """Add a specific configuration item using specified file managers. @@ -603,7 +606,7 @@ def _unconditional_update_bitbucket_steps( ): remove_bitbucket_step_from_default(step) - def _get_select_keys(self, file_manager: KeyValueFileManager) -> list[str]: + def _get_select_keys(self, file_manager: KeyValueFileManager[object]) -> list[str]: """Get the configuration keys for selected rules. This is optional - tools that don't support rule selection can leave this @@ -655,7 +658,7 @@ def select_rules(self, rules: list[Rule]) -> bool: return True - def _get_ignore_keys(self, file_manager: KeyValueFileManager) -> list[str]: + def _get_ignore_keys(self, file_manager: KeyValueFileManager[object]) -> list[str]: """Get the configuration keys for ignored rules. Args: diff --git a/src/usethis/_tool/config.py b/src/usethis/_tool/config.py index 768942e1..2a6e63fe 100644 --- a/src/usethis/_tool/config.py +++ b/src/usethis/_tool/config.py @@ -47,7 +47,7 @@ class ConfigSpec(BaseModel): @classmethod def from_flat( cls, - file_managers: list[KeyValueFileManager], + file_managers: list[KeyValueFileManager[object]], resolution: ResolutionT, config_items: list[ConfigItem], ) -> Self: @@ -146,7 +146,7 @@ def paths(self) -> set[Path]: return {(usethis_config.cpd() / path).resolve() for path in self.root} -def ensure_managed_file_exists(file_manager: UsethisFileManager) -> None: +def ensure_managed_file_exists(file_manager: UsethisFileManager[object]) -> None: """Ensure a file manager's managed file exists.""" if isinstance(file_manager, PyprojectTOMLManager): ensure_pyproject_toml() diff --git a/src/usethis/_tool/impl/base/deptry.py b/src/usethis/_tool/impl/base/deptry.py index 1bc7ead6..fbb38553 100644 --- a/src/usethis/_tool/impl/base/deptry.py +++ b/src/usethis/_tool/impl/base/deptry.py @@ -69,7 +69,7 @@ def ignored_rules(self) -> list[Rule]: return rules - def _get_ignore_keys(self, file_manager: KeyValueFileManager) -> list[str]: + 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): return ["tool", "deptry", "ignore"] diff --git a/src/usethis/_tool/impl/base/pytest.py b/src/usethis/_tool/impl/base/pytest.py index c6b82416..df21d73e 100644 --- a/src/usethis/_tool/impl/base/pytest.py +++ b/src/usethis/_tool/impl/base/pytest.py @@ -134,7 +134,7 @@ def print_how_to_use(self) -> None: how_print("Add test functions with the format 'test_*()'.") how_print(f"Run '{self.how_to_use_cmd()}' to run the tests.") - def get_active_config_file_managers(self) -> set[KeyValueFileManager]: + def get_active_config_file_managers(self) -> set[KeyValueFileManager[object]]: # This is a variant of the "first" method config_spec = self.config_spec() if config_spec.resolution != "bespoke": diff --git a/src/usethis/_tool/impl/base/ruff.py b/src/usethis/_tool/impl/base/ruff.py index 630203ce..467046e3 100644 --- a/src/usethis/_tool/impl/base/ruff.py +++ b/src/usethis/_tool/impl/base/ruff.py @@ -445,7 +445,7 @@ def _are_pydocstyle_rules_selected(self) -> bool: def _is_pydocstyle_rule(rule: Rule) -> bool: return [d for d in rule if d.isalpha()] == ["D"] - def _get_select_keys(self, file_manager: KeyValueFileManager) -> list[str]: + def _get_select_keys(self, file_manager: KeyValueFileManager[object]) -> list[str]: """Get the keys for the selected rules in the given file manager.""" if isinstance(file_manager, PyprojectTOMLManager): return ["tool", "ruff", "lint", "select"] @@ -454,7 +454,7 @@ def _get_select_keys(self, file_manager: KeyValueFileManager) -> list[str]: else: return super()._get_select_keys(file_manager) - def _get_ignore_keys(self, file_manager: KeyValueFileManager) -> list[str]: + 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): return ["tool", "ruff", "lint", "ignore"] @@ -464,7 +464,7 @@ def _get_ignore_keys(self, file_manager: KeyValueFileManager) -> list[str]: return super()._get_ignore_keys(file_manager) def _get_per_file_ignore_keys( - self, file_manager: KeyValueFileManager, *, glob: str + self, file_manager: KeyValueFileManager[object], *, glob: str ) -> list[str]: """Get the keys for the per-file ignored rules in the given file manager.""" if isinstance(file_manager, PyprojectTOMLManager): @@ -478,7 +478,9 @@ def _get_per_file_ignore_keys( ) raise NotImplementedError(msg) - def _get_docstyle_keys(self, file_manager: KeyValueFileManager) -> list[str]: + def _get_docstyle_keys( + self, file_manager: KeyValueFileManager[object] + ) -> list[str]: """Get the keys for the docstyle rules in the given file manager.""" if isinstance(file_manager, PyprojectTOMLManager): return ["tool", "ruff", "lint", "pydocstyle", "convention"] diff --git a/src/usethis/_tool/impl/spec/codespell.py b/src/usethis/_tool/impl/spec/codespell.py index 40ee0f50..58f61a78 100644 --- a/src/usethis/_tool/impl/spec/codespell.py +++ b/src/usethis/_tool/impl/spec/codespell.py @@ -32,7 +32,7 @@ def meta(self) -> ToolMeta: managed_files=[Path(".codespellrc")], ) - def preferred_file_manager(self) -> KeyValueFileManager: + def preferred_file_manager(self) -> KeyValueFileManager[object]: if (usethis_config.cpd() / "pyproject.toml").exists(): return PyprojectTOMLManager() return DotCodespellRCManager() diff --git a/src/usethis/_tool/impl/spec/coverage_py.py b/src/usethis/_tool/impl/spec/coverage_py.py index cf578300..96a64c68 100644 --- a/src/usethis/_tool/impl/spec/coverage_py.py +++ b/src/usethis/_tool/impl/spec/coverage_py.py @@ -21,7 +21,7 @@ def meta(self) -> ToolMeta: managed_files=[Path(".coveragerc"), Path(".coveragerc.toml")], ) - def preferred_file_manager(self) -> KeyValueFileManager: + def preferred_file_manager(self) -> KeyValueFileManager[object]: if (usethis_config.cpd() / "pyproject.toml").exists(): return PyprojectTOMLManager() return DotCoverageRCManager() diff --git a/src/usethis/_tool/impl/spec/import_linter.py b/src/usethis/_tool/impl/spec/import_linter.py index 1d53663e..d304d84c 100644 --- a/src/usethis/_tool/impl/spec/import_linter.py +++ b/src/usethis/_tool/impl/spec/import_linter.py @@ -50,7 +50,7 @@ def raw_cmd(self) -> str: def dev_deps(self, *, unconditional: bool = False) -> list[Dependency]: return [Dependency(name="import-linter")] - def preferred_file_manager(self) -> KeyValueFileManager: + def preferred_file_manager(self) -> KeyValueFileManager[object]: if (usethis_config.cpd() / "pyproject.toml").exists(): return PyprojectTOMLManager() return DotImportLinterManager() @@ -94,7 +94,9 @@ def _get_layered_architecture_by_module_by_root_package( def _get_resolution(self) -> ResolutionT: return "first" - def _get_file_manager_by_relative_path(self) -> dict[Path, KeyValueFileManager]: + def _get_file_manager_by_relative_path( + self, + ) -> dict[Path, KeyValueFileManager[object]]: return { Path("setup.cfg"): SetupCFGManager(), Path(".importlinter"): DotImportLinterManager(), diff --git a/src/usethis/_tool/impl/spec/mkdocs.py b/src/usethis/_tool/impl/spec/mkdocs.py index 1c928a86..21692e9f 100644 --- a/src/usethis/_tool/impl/spec/mkdocs.py +++ b/src/usethis/_tool/impl/spec/mkdocs.py @@ -28,7 +28,7 @@ def doc_deps(self, *, unconditional: bool = False) -> list[Dependency]: return deps - def preferred_file_manager(self) -> KeyValueFileManager: + def preferred_file_manager(self) -> KeyValueFileManager[object]: """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() diff --git a/src/usethis/_tool/impl/spec/pytest.py b/src/usethis/_tool/impl/spec/pytest.py index d0c7c1aa..8daae95e 100644 --- a/src/usethis/_tool/impl/spec/pytest.py +++ b/src/usethis/_tool/impl/spec/pytest.py @@ -30,7 +30,7 @@ def meta(self) -> ToolMeta: def raw_cmd(self) -> str: return "pytest" - def preferred_file_manager(self) -> KeyValueFileManager: + def preferred_file_manager(self) -> KeyValueFileManager[object]: if (usethis_config.cpd() / "pyproject.toml").exists(): return PyprojectTOMLManager() return PytestINIManager() diff --git a/src/usethis/_tool/impl/spec/ruff.py b/src/usethis/_tool/impl/spec/ruff.py index 4ed60990..f642e49c 100644 --- a/src/usethis/_tool/impl/spec/ruff.py +++ b/src/usethis/_tool/impl/spec/ruff.py @@ -25,7 +25,7 @@ def meta(self) -> ToolMeta: def dev_deps(self, *, unconditional: bool = False) -> list[Dependency]: return [Dependency(name="ruff")] - def preferred_file_manager(self) -> KeyValueFileManager: + def preferred_file_manager(self) -> KeyValueFileManager[object]: if (usethis_config.cpd() / "pyproject.toml").exists(): return PyprojectTOMLManager() return RuffTOMLManager() diff --git a/src/usethis/_tool/spec.py b/src/usethis/_tool/spec.py index 0ae5a4a3..045376d7 100644 --- a/src/usethis/_tool/spec.py +++ b/src/usethis/_tool/spec.py @@ -71,7 +71,7 @@ def rule_config(self) -> RuleConfig: """ return self.meta.rule_config - def preferred_file_manager(self) -> KeyValueFileManager: + def preferred_file_manager(self) -> KeyValueFileManager[object]: """If there is no currently active config file, this is the preferred one. This can vary dynamically, since often we will prefer to respect an existing