Skip to content

Commit 3896cc0

Browse files
elifarleyclaude
andcommitted
Fix MyPy type errors in comparator and walker
- Add explicit type annotation for children list in walker.py (list[DirInfo | FileInfo]) - Add explicit type annotations for va/vb in sort.py comparator (float | str) - Add type: ignore[operator] comments for safe comparisons with union types The code uses isinstance checks to safely narrow types at runtime before comparisons, but MyPy cannot infer this narrowing across union types in all cases. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 7f3ae91 commit 3896cc0

File tree

13 files changed

+37
-47
lines changed

13 files changed

+37
-47
lines changed

src/cedarmapper/cli/main.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@
44

55
import sys
66
from pathlib import Path
7-
from typing import Optional, Tuple
87

98
import click
109

1110
from cedarmapper.core.walker import walk_directory
12-
from cedarmapper.render.tree import render_tree
1311
from cedarmapper.render.flat import render_flat
12+
from cedarmapper.render.tree import render_tree
1413

1514

16-
def run_cli(argv: Optional[list[str]] = None) -> Tuple[int, str]:
15+
def run_cli(argv: list[str] | None = None) -> tuple[int, str]:
1716
"""
1817
Programmatic entrypoint used by tests.
1918
@@ -167,7 +166,7 @@ def cli(
167166

168167

169168
# Keep a small compatibility alias for direct calls (tests should use run_cli)
170-
def main(argv: Optional[list[str]] = None) -> int:
169+
def main(argv: list[str] | None = None) -> int:
171170
code, _ = run_cli(argv)
172171
return code
173172

src/cedarmapper/core/analyzer.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@
22

33
from __future__ import annotations
44

5-
import os
65
import subprocess
76
from datetime import datetime
87
from pathlib import Path
98
from shutil import which
10-
from typing import Optional
119

1210
from .model import FileInfo
1311

@@ -16,7 +14,7 @@ def _tool_exists(name: str) -> bool:
1614
return which(name) is not None
1715

1816

19-
def is_binary_by_file_tool(path: Path) -> Optional[bool]:
17+
def is_binary_by_file_tool(path: Path) -> bool | None:
2018
"""Return True if binary, False if text; None if tool missing or error."""
2119
if not _tool_exists("file"):
2220
return None
@@ -37,7 +35,7 @@ def is_binary_by_file_tool(path: Path) -> Optional[bool]:
3735
return None
3836

3937

40-
def count_words_with_wc(path: Path) -> Optional[int]:
38+
def count_words_with_wc(path: Path) -> int | None:
4139
"""Return word count (int) or None if wc missing or error."""
4240
if not _tool_exists("wc"):
4341
return None

src/cedarmapper/core/model.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@
55
from dataclasses import dataclass, field
66
from datetime import datetime
77
from pathlib import Path
8-
from typing import List, Optional, Union
98

109

1110
@dataclass
1211
class FileInfo:
1312
path: Path
1413
size_bytes: int
15-
word_count: Optional[int] # None for binary or skipped
14+
word_count: int | None # None for binary or skipped
1615
mtime: datetime
17-
error: Optional[str] = None # store error strings (permissions/tool missing)
16+
error: str | None = None # store error strings (permissions/tool missing)
1817

1918

2019
@dataclass
@@ -23,5 +22,5 @@ class DirInfo:
2322
size_bytes: int
2423
word_count: int # sum of non-binary file word_counts (binary files contribute 0)
2524
mtime: datetime # most recent modification time among contained files
26-
children: List[Union["DirInfo", FileInfo]] = field(default_factory=list)
27-
error: Optional[str] = None
25+
children: list[DirInfo | FileInfo] = field(default_factory=list)
26+
error: str | None = None

src/cedarmapper/core/walker.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from datetime import datetime
66
from pathlib import Path
7-
from typing import Set, Union
87

98
from .analyzer import get_file_info
109
from .model import DirInfo, FileInfo
@@ -15,13 +14,13 @@ def walk_directory(
1514
) -> DirInfo:
1615
"""Traverse directory tree, always fully descend for aggregation."""
1716
root = root.resolve()
18-
seen_inodes: Set[tuple[int, int]] = set() # (st_dev, st_ino)
17+
seen_inodes: set[tuple[int, int]] = set() # (st_dev, st_ino)
1918

2019
def _walk(p: Path) -> DirInfo:
2120
total_bytes = 0
2221
total_words = 0
2322
most_recent = datetime.fromtimestamp(0)
24-
children = []
23+
children: list[DirInfo | FileInfo] = []
2524

2625
try:
2726
for entry in sorted(p.iterdir(), key=lambda x: x.name):

src/cedarmapper/render/flat.py

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

55
from datetime import datetime
6-
from typing import List, Union
6+
from functools import cmp_to_key
77

88
from cedarmapper.core.model import DirInfo, FileInfo
9-
from cedarmapper.render.sort import parse_sort_spec, make_comparator
10-
from functools import cmp_to_key
9+
from cedarmapper.render.sort import make_comparator, parse_sort_spec
1110

1211

1312
def _fmt_count(c: int | None) -> str:
@@ -59,9 +58,9 @@ def render_flat(
5958
- date_mode: 'seconds' | 'day' | 'none'
6059
- sort_spec: string describing sorting like 'wi-sd'
6160
"""
62-
rows: List[_DisplayRow] = []
61+
rows: list[_DisplayRow] = []
6362

64-
def _render_node(node: Union[DirInfo, FileInfo], depth: int, parent_path: str) -> None:
63+
def _render_node(node: DirInfo | FileInfo, depth: int, parent_path: str) -> None:
6564
display_path = (
6665
f"{parent_path}{node.path.name}"
6766
if isinstance(node, FileInfo)
@@ -108,7 +107,7 @@ def _render_node(node: Union[DirInfo, FileInfo], depth: int, parent_path: str) -
108107
rows.sort(key=cmp_to_key(comp))
109108

110109
# Build lines
111-
lines: List[str] = []
110+
lines: list[str] = []
112111
for r in rows:
113112
date_str = _fmt_mtime(r.mtime, date_mode)
114113
if date_mode == "none":

src/cedarmapper/render/sort.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@
1818

1919
from __future__ import annotations
2020

21+
from collections.abc import Callable, Iterable
2122
from dataclasses import dataclass
22-
from functools import cmp_to_key
23-
from typing import Callable, Iterable, List, Tuple, Union
2423
from datetime import datetime
2524

2625

@@ -31,9 +30,9 @@ class SortSpec:
3130
reverse: bool = False
3231

3332

34-
def parse_sort_spec(spec: str) -> List[SortSpec]:
33+
def parse_sort_spec(spec: str) -> list[SortSpec]:
3534
"""Parse a spec string like 'wi-sd' into [SortSpec(...), ...]."""
36-
specs: List[SortSpec] = []
35+
specs: list[SortSpec] = []
3736
if not spec:
3837
return specs
3938
negate = False
@@ -48,7 +47,7 @@ def parse_sort_spec(spec: str) -> List[SortSpec]:
4847
return specs
4948

5049

51-
def _safe_num(v: Union[int, None]) -> float:
50+
def _safe_num(v: int | None) -> float:
5251
"""Normalize numeric values; treat None as -inf so that present numbers sort after absent ones."""
5352
if v is None:
5453
return float("-inf")
@@ -76,6 +75,9 @@ def _cmp(a, b) -> int:
7675
key = s.key
7776
rev = -1 if s.reverse else 1
7877

78+
va: float | str
79+
vb: float | str
80+
7981
if key == "w":
8082
va = _safe_num(a.word_count)
8183
vb = _safe_num(b.word_count)
@@ -105,9 +107,9 @@ def _cmp(a, b) -> int:
105107
return 1 * rev
106108
else:
107109
# string comparison
108-
if va < vb:
110+
if va < vb: # type: ignore[operator]
109111
return -1 * rev
110-
if va > vb:
112+
if va > vb: # type: ignore[operator]
111113
return 1 * rev
112114
# else equal -> continue to next spec
113115
return 0

src/cedarmapper/render/tree.py

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

55
from datetime import datetime
6-
from typing import List, Union
6+
from functools import cmp_to_key
77

88
from cedarmapper.core.model import DirInfo, FileInfo
9-
from cedarmapper.render.sort import parse_sort_spec, make_comparator
10-
from functools import cmp_to_key
9+
from cedarmapper.render.sort import make_comparator, parse_sort_spec
1110

1211

1312
def _fmt_count(c: int | None) -> str:
@@ -43,7 +42,7 @@ def render_tree(
4342
- numbered_indent: if True, prefix each entry with the node depth number instead of indent chars.
4443
- sort_spec: sort string passed to parse_sort_spec
4544
"""
46-
lines: List[str] = []
45+
lines: list[str] = []
4746

4847
# Characters used for branches (similar to `tree` command)
4948
VERTICAL = "│"
@@ -58,18 +57,18 @@ def render_tree(
5857
comparator = make_comparator(specs)
5958

6059
def _render_node(
61-
node: Union[DirInfo, FileInfo],
60+
node: DirInfo | FileInfo,
6261
depth: int,
63-
is_last_stack: List[bool], # stack indicating whether current ancestor is last
62+
is_last_stack: list[bool], # stack indicating whether current ancestor is last
6463
parent_display_path: str,
6564
) -> None:
6665
# Build prefix either with numbered indent or with branch characters
6766
if numbered_indent:
6867
prefix = f"{depth:>3} "
6968
else:
7069
# build tree-like prefix
71-
prefix_parts: List[str] = []
72-
for i, is_last in enumerate(is_last_stack[:-1]):
70+
prefix_parts: list[str] = []
71+
for _i, is_last in enumerate(is_last_stack[:-1]):
7372
prefix_parts.append(" " if is_last else f"{VERTICAL} ")
7473
if len(is_last_stack) > 0:
7574
prefix_parts.append(LAST if is_last_stack[-1] else BRANCH)

tests/test_analyzer.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
import tempfile
44
from pathlib import Path
55

6-
import pytest
7-
8-
from cedarmapper.core.analyzer import _heuristic_is_binary, get_file_info, is_binary_by_file_tool
6+
from cedarmapper.core.analyzer import _heuristic_is_binary, get_file_info
97

108

119
class TestHeuristicBinary:

tests/test_cli.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import tempfile
44
from pathlib import Path
55

6-
import pytest
7-
86
from cedarmapper.cli.main import run_cli
97

108

tests/test_sort.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from cedarmapper.render.sort import parse_sort_spec, SortSpec
1+
from cedarmapper.render.sort import SortSpec, parse_sort_spec
22

33

44
def test_parse_simple_keys():

0 commit comments

Comments
 (0)