Skip to content

Commit 6edd7ec

Browse files
Reduce the use of typing.Any (#1443)
* Reduce the use of `typing.Any` Toward #1321 * Remove list casting from `lcs_list_update` * Pass basedpyright
1 parent 19e5eff commit 6edd7ec

16 files changed

Lines changed: 81 additions & 58 deletions

File tree

src/usethis/_file/ini/io_.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,11 @@ def __getitem__(self, item: Sequence[Key]) -> object:
147147
raise KeyError(msg)
148148

149149
def set_value(
150-
self, *, keys: Sequence[Key], value: Any, exists_ok: bool = False
150+
self,
151+
*,
152+
keys: Sequence[Key],
153+
value: object,
154+
exists_ok: bool = False,
151155
) -> None:
152156
"""Set a value in the INI file.
153157
@@ -156,19 +160,24 @@ def set_value(
156160
root = self.get()
157161

158162
if len(keys) == 0:
163+
value = TypeAdapter(dict[str, dict[str, str | list[str]]]).validate_python(
164+
value
165+
)
159166
self._set_value_in_root(root=root, value=value, exists_ok=exists_ok)
160167
elif len(keys) == 1:
161168
(section_key,) = keys
169+
value = TypeAdapter(dict[str, str | list[str]]).validate_python(value)
162170
self._set_value_in_section(
163171
root=root, section_key=keys[0], value=value, exists_ok=exists_ok
164172
)
165173
elif len(keys) == 2:
166174
(section_key, option_key) = keys
175+
cast_value = TypeAdapter(str | list[str]).validate_python(value)
167176
self._set_value_in_option(
168177
root=root,
169178
section_key=section_key,
170179
option_key=option_key,
171-
value=value,
180+
value=cast_value,
172181
exists_ok=exists_ok,
173182
)
174183
else:
@@ -271,7 +280,7 @@ def _set_value_in_option(
271280
root: INIDocument,
272281
section_key: Key,
273282
option_key: Key,
274-
value: str,
283+
value: str | list[str],
275284
exists_ok: bool,
276285
) -> None:
277286
if not isinstance(section_key, str) or not isinstance(option_key, str):
@@ -408,7 +417,7 @@ def _delete_strkeys(self, strkeys: Sequence[str]) -> None:
408417

409418
self.commit(root)
410419

411-
def extend_list(self, *, keys: Sequence[Key], values: list[str]) -> None:
420+
def extend_list(self, *, keys: Sequence[Key], values: Sequence[object]) -> None:
412421
"""Extend a list in the INI file.
413422
414423
An empty list of keys corresponds to the root of the document.
@@ -429,6 +438,7 @@ def extend_list(self, *, keys: Sequence[Key], values: list[str]) -> None:
429438
raise InvalidINITypeError(msg)
430439
elif len(keys) == 2:
431440
section_key, option_key = keys
441+
values = TypeAdapter(list[str]).validate_python(values)
432442
self._extend_list_in_option(
433443
root=root, section_key=section_key, option_key=option_key, values=values
434444
)
@@ -443,7 +453,7 @@ def extend_list(self, *, keys: Sequence[Key], values: list[str]) -> None:
443453

444454
@staticmethod
445455
def _extend_list_in_option(
446-
*, root: INIDocument, section_key: Key, option_key: Key, values: list[str]
456+
*, root: INIDocument, section_key: Key, option_key: Key, values: Sequence[str]
447457
) -> None:
448458
for value in values:
449459
INIFileManager._validated_append(
@@ -452,7 +462,7 @@ def _extend_list_in_option(
452462

453463
@staticmethod
454464
def _remove_from_list_in_option(
455-
*, root: INIDocument, section_key: Key, option_key: Key, values: list[str]
465+
*, root: INIDocument, section_key: Key, option_key: Key, values: Sequence[str]
456466
) -> None:
457467
if section_key not in root:
458468
return
@@ -485,7 +495,9 @@ def _remove_from_list_in_option(
485495
elif len(new_values) > 1:
486496
root[section_key][option_key].set_values(new_values)
487497

488-
def remove_from_list(self, *, keys: Sequence[Key], values: list[str]) -> None:
498+
def remove_from_list(
499+
self, *, keys: Sequence[Key], values: Sequence[object]
500+
) -> None:
489501
"""Remove values from a list in the INI file.
490502
491503
An empty list of keys corresponds to the root of the document.
@@ -506,6 +518,7 @@ def remove_from_list(self, *, keys: Sequence[Key], values: list[str]) -> None:
506518
raise InvalidINITypeError(msg)
507519
elif len(keys) == 2:
508520
section_key, option_key = keys
521+
values = TypeAdapter(list[str]).validate_python(values)
509522
self._remove_from_list_in_option(
510523
root=root, section_key=section_key, option_key=option_key, values=values
511524
)

src/usethis/_file/merge.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from __future__ import annotations
22

33
from collections.abc import MutableMapping
4-
from typing import Any
4+
from typing import Any, TypeVar
55

6+
T = TypeVar("T", bound=MutableMapping[Any, Any])
67

7-
def _deep_merge(
8-
target: MutableMapping[Any, Any], source: MutableMapping[Any, Any]
9-
) -> MutableMapping[Any, Any]:
8+
9+
def _deep_merge(target: T, source: MutableMapping[Any, Any]) -> T:
1010
"""Recursively merge source into target in place, returning target.
1111
1212
For keys present in both mappings, if both values are mappings the merge is

src/usethis/_file/toml/io_.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def set_value(
193193
# The configuration is already present, but we're allowed to overwrite it.
194194
parent[keys[-1]] = value
195195

196-
self.commit(toml_document) # type: ignore[reportAssignmentType]
196+
self.commit(toml_document)
197197

198198
def __delitem__(self, keys: Sequence[Key]) -> None:
199199
"""Delete a value in the TOML file.
@@ -262,7 +262,7 @@ def __delitem__(self, keys: Sequence[Key]) -> None:
262262

263263
self.commit(toml_document)
264264

265-
def extend_list(self, *, keys: Sequence[Key], values: list[Any]) -> None:
265+
def extend_list(self, *, keys: Sequence[Key], values: Sequence[Any]) -> None:
266266
if not keys:
267267
msg = "At least one ID key must be provided."
268268
raise ValueError(msg)
@@ -311,7 +311,7 @@ def extend_list(self, *, keys: Sequence[Key], values: list[Any]) -> None:
311311
)
312312
raise TOMLValueInvalidError(msg) from None
313313
assert isinstance(d, list)
314-
p_parent[keys[-1]] = d + values
314+
p_parent[keys[-1]] = d + list(values)
315315

316316
self.commit(toml_document)
317317

@@ -382,7 +382,7 @@ def _set_value_in_existing(
382382
contents = value
383383
for key in reversed(keys):
384384
contents = {key: contents}
385-
toml_document = _deep_merge(toml_document, contents) # type: ignore[reportAssignmentType]
385+
toml_document = _deep_merge(toml_document, contents)
386386
assert isinstance(toml_document, TOMLDocument)
387387
else:
388388
# Note that this alternative logic is just to avoid a bug:
@@ -397,13 +397,22 @@ def _set_value_in_existing(
397397
# https://github.com/usethis-python/usethis-python/issues/558
398398

399399
placeholder = {keys[0]: {keys[1]: {}}}
400-
toml_document = _deep_merge(toml_document, placeholder) # type: ignore[reportArgumentType]
400+
toml_document = _deep_merge(toml_document, placeholder)
401401

402402
contents = value
403403
for key in reversed(unshared_keys[1:]):
404404
contents = {key: contents}
405405

406-
shared_container[keys[1]] = contents # pyright: ignore[reportArgumentType]
406+
# Check it's not a pattern-based key, which isn't supported in TOML files.
407+
key = keys[1]
408+
if isinstance(key, re.Pattern):
409+
msg = (
410+
f"Regex-based keys are not currently supported in TOML files: "
411+
f"{print_keys(keys)}"
412+
)
413+
raise NotImplementedError(msg)
414+
415+
shared_container[key] = contents
407416
else:
408417
shared_container[_get_unified_key(unshared_keys)] = value
409418

src/usethis/_file/yaml/io_.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ def __delitem__(self, keys: Sequence[Key]) -> None:
279279
)
280280
self.commit(self._content)
281281

282-
def extend_list(self, *, keys: Sequence[Key], values: list[Any]) -> None:
282+
def extend_list(self, *, keys: Sequence[Key], values: Sequence[Any]) -> None:
283283
"""Extend a list in the configuration file."""
284284
if not keys:
285285
msg = "At least one ID key must be provided."
@@ -317,7 +317,7 @@ def extend_list(self, *, keys: Sequence[Key], values: list[Any]) -> None:
317317
TypeAdapter(list).validate_python(d)
318318
assert isinstance(p_parent, dict)
319319
assert isinstance(d, list)
320-
p_parent[keys[-1]] = d + values
320+
p_parent[keys[-1]] = d + list(values)
321321

322322
assert self._content is not None # We have called .get() already.
323323
update_ruamel_yaml_map(
@@ -327,7 +327,7 @@ def extend_list(self, *, keys: Sequence[Key], values: list[Any]) -> None:
327327
)
328328
self.commit(self._content)
329329

330-
def remove_from_list(self, *, keys: Sequence[Key], values: list[Any]) -> None:
330+
def remove_from_list(self, *, keys: Sequence[Key], values: Sequence[Any]) -> None:
331331
"""Remove values from a list in the configuration file."""
332332
if not keys:
333333
msg = "At least one ID key must be provided."
@@ -386,7 +386,7 @@ def _set_value_in_existing(
386386
contents = value
387387
for key in reversed(keys):
388388
contents = {key: contents}
389-
content = _deep_merge(content, contents) # type: ignore[reportAssignmentType]
389+
content = _deep_merge(content, contents)
390390

391391

392392
def _validate_keys(keys: Sequence[Key]) -> list[str]:

src/usethis/_file/yaml/update.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88
)
99

1010
if TYPE_CHECKING:
11+
from collections.abc import Sequence
1112
from typing import Any
1213

1314
from usethis._file.yaml.typing_ import YAMLLiteral
1415

15-
_T = TypeVar("_T")
16+
_T = TypeVar("_T", bound=object)
1617

1718

1819
def update_ruamel_yaml_map(
@@ -116,7 +117,7 @@ def lcs_list_update(original: list[_T], new: list[_T]) -> None:
116117
original_idx += 1
117118

118119

119-
def _shared_id_sequences(*seqs: list[Any]) -> list[list[int]]:
120+
def _shared_id_sequences(*seqs: Sequence[object]) -> Sequence[list[int]]:
120121
"""Map list elements to integers which are equal iff the objects are with __eq__."""
121122
# Don't use "in" because that would mean the elements must be hashable,
122123
# which we don't want to require. This means we have to loop over every element,

src/usethis/_io.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import re
44
from abc import ABCMeta, abstractmethod
5-
from typing import TYPE_CHECKING, Generic, TypeAlias, TypeVar
5+
from typing import TYPE_CHECKING, Any, Generic, TypeAlias, TypeVar
66

77
from typing_extensions import assert_never
88

@@ -13,7 +13,7 @@
1313
from collections.abc import Sequence
1414
from pathlib import Path
1515
from types import TracebackType
16-
from typing import Any, ClassVar
16+
from typing import ClassVar
1717

1818
from typing_extensions import Self
1919

@@ -192,7 +192,7 @@ def __contains__(self, keys: Sequence[Key]) -> bool:
192192
def __getitem__(self, keys: Sequence[Key]) -> object:
193193
raise NotImplementedError
194194

195-
def __setitem__(self, keys: Sequence[Key], value: Any) -> None:
195+
def __setitem__(self, keys: Sequence[Key], value: object) -> None:
196196
"""Set a value in the configuration file."""
197197
return self.set_value(keys=keys, value=value, exists_ok=True)
198198

@@ -202,21 +202,23 @@ def __delitem__(self, keys: Sequence[Key]) -> None:
202202

203203
@abstractmethod
204204
def set_value(
205-
self, *, keys: Sequence[Key], value: Any, exists_ok: bool = False
205+
self, *, keys: Sequence[Key], value: object, exists_ok: bool = False
206206
) -> None:
207207
"""Set a value in the configuration file."""
208208
raise NotImplementedError
209209

210210
@abstractmethod
211-
def extend_list(self, *, keys: Sequence[Key], values: list[Any]) -> None:
211+
def extend_list(self, *, keys: Sequence[Key], values: Sequence[object]) -> None:
212212
"""Extend a list in the configuration file.
213213
214214
This method will always extend the list, even if it results in duplicates.
215215
"""
216216
raise NotImplementedError
217217

218218
@abstractmethod
219-
def remove_from_list(self, *, keys: Sequence[Key], values: list[Any]) -> None:
219+
def remove_from_list(
220+
self, *, keys: Sequence[Key], values: Sequence[object]
221+
) -> None:
220222
"""Remove values from a list in the configuration file."""
221223
raise NotImplementedError
222224

src/usethis/_pipeweld/containers.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING
4-
53
from pydantic import BaseModel, RootModel
64

7-
if TYPE_CHECKING:
8-
from typing import Any
9-
10-
115
_HASH_SALT = "e6fdde87-adc6-42f6-8e66-4aabe4ba05f2"
126

137

@@ -21,7 +15,7 @@ def __getitem__(self, item: int) -> Series | Parallel | DepGroup | str:
2115
def __setitem__(self, item: int, value: Series | Parallel | DepGroup | str) -> None:
2216
self.root[item] = value
2317

24-
def __eq__(self, other: Any):
18+
def __eq__(self, other: object):
2519
if not isinstance(other, Series):
2620
return False
2721
return self.root == other.root
@@ -37,7 +31,7 @@ def __hash__(self):
3731
def __or__(self, other: Parallel) -> Parallel:
3832
return Parallel(self.root | other.root)
3933

40-
def __eq__(self, other: Any):
34+
def __eq__(self, other: object):
4135
if not isinstance(other, Parallel):
4236
return False
4337

src/usethis/_test.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import socket
55
from contextlib import contextmanager
66
from pathlib import Path
7-
from typing import IO, TYPE_CHECKING, Any
7+
from typing import IO, TYPE_CHECKING
88

99
from typer.testing import CliRunner as TyperCliRunner # noqa: TID251
1010

@@ -47,10 +47,10 @@ def invoke_safe(
4747
self,
4848
app: Typer,
4949
args: str | Sequence[str] | None = None,
50-
input: bytes | str | IO[Any] | None = None, # noqa: A002
50+
input: bytes | str | IO[str] | None = None, # noqa: A002
5151
env: Mapping[str, str] | None = None,
5252
color: bool = False,
53-
**extra: Any,
53+
**extra: object,
5454
) -> Result:
5555
return self.invoke(
5656
app,
@@ -66,11 +66,11 @@ def invoke( # noqa: PLR0913
6666
self,
6767
app: Typer,
6868
args: str | Sequence[str] | None = None,
69-
input: bytes | str | IO[Any] | None = None, # noqa: A002
69+
input: bytes | str | IO[str] | None = None, # noqa: A002
7070
env: Mapping[str, str] | None = None,
7171
catch_exceptions: bool = True,
7272
color: bool = False,
73-
**extra: Any,
73+
**extra: object,
7474
) -> Result:
7575
if catch_exceptions:
7676
msg = "`catch_exceptions=True` is forbidden in usethis tests. Use `.invoke_safe()` instead."

src/usethis/_tool/base.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
)
3939

4040
if TYPE_CHECKING:
41+
from collections.abc import Sequence
42+
4143
from usethis._io import KeyValueFileManager
4244
from usethis._tool.config import ConfigItem
4345
from usethis._tool.rule import Rule
@@ -549,7 +551,7 @@ def _get_select_keys(self, file_manager: KeyValueFileManager[object]) -> list[st
549551
)
550552
raise UnhandledConfigEntryError(msg)
551553

552-
def select_rules(self, rules: list[Rule]) -> bool:
554+
def select_rules(self, rules: Sequence[Rule]) -> bool:
553555
"""Select the rules managed by the tool.
554556
555557
These rules are not validated; it is assumed they are valid rules for the tool,
@@ -598,7 +600,7 @@ def _get_ignore_keys(self, file_manager: KeyValueFileManager[object]) -> list[st
598600
)
599601
raise UnhandledConfigEntryError(msg)
600602

601-
def ignore_rules(self, rules: list[Rule]) -> bool:
603+
def ignore_rules(self, rules: Sequence[Rule]) -> bool:
602604
"""Ignore rules managed by the tool.
603605
604606
Ignoring a rule is different from deselecting it - it means that even if it
@@ -664,7 +666,7 @@ def unignore_rules(self, rules: list[str]) -> bool:
664666

665667
return True
666668

667-
def deselect_rules(self, rules: list[Rule]) -> bool:
669+
def deselect_rules(self, rules: Sequence[Rule]) -> bool:
668670
"""Deselect the rules managed by the tool.
669671
670672
Any rules that aren't already selected are ignored.

0 commit comments

Comments
 (0)