Skip to content

Commit 13c5020

Browse files
committed
cli/add-discover(refactor[typing]): Reduce raw config Any
why: Use object/TypedDict shapes for raw config sections without changing runtime behavior. what: - Add OrderedItem typed dict for label/section entries in add flow - Replace raw config/duplicate annotations with object-based types - Tighten discover workspace section handling with casts
1 parent 2ae81a8 commit 13c5020

2 files changed

Lines changed: 38 additions & 25 deletions

File tree

src/vcspull/cli/add.py

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@
2727
log = logging.getLogger(__name__)
2828

2929

30+
class _OrderedItem(t.TypedDict):
31+
"""Ordered config entry preserving label/section pairs."""
32+
33+
label: str
34+
section: object
35+
36+
3037
def create_add_subparser(parser: argparse.ArgumentParser) -> None:
3138
"""Create ``vcspull add`` argument subparser.
3239
@@ -164,40 +171,43 @@ def _normalize_detected_url(remote: str | None) -> tuple[str, str]:
164171

165172

166173
def _build_ordered_items(
167-
top_level_items: list[tuple[str, t.Any]] | None,
168-
raw_config: dict[str, t.Any],
169-
) -> list[dict[str, t.Any]]:
174+
top_level_items: list[tuple[str, object]] | None,
175+
raw_config: dict[str, object],
176+
) -> list[_OrderedItem]:
170177
"""Return deep-copied top-level items preserving original ordering."""
171-
source: list[tuple[str, t.Any]] = top_level_items or list(raw_config.items())
178+
source: list[tuple[str, object]] = top_level_items or list(raw_config.items())
172179

173-
ordered: list[dict[str, t.Any]] = []
180+
ordered: list[_OrderedItem] = []
174181
for label, section in source:
175182
ordered.append({"label": label, "section": copy.deepcopy(section)})
176183
return ordered
177184

178185

179186
def _aggregate_from_ordered_items(
180-
items: list[dict[str, t.Any]],
181-
) -> dict[str, t.Any]:
187+
items: list[_OrderedItem],
188+
) -> dict[str, object]:
182189
"""Collapse ordered top-level items into a mapping grouped by label."""
183-
aggregated: dict[str, t.Any] = {}
190+
aggregated: dict[str, object] = {}
184191
for entry in items:
185192
label = entry["label"]
186193
section = entry["section"]
187194
if isinstance(section, dict):
188-
workspace_section = aggregated.setdefault(label, {})
195+
workspace_section = t.cast(
196+
"dict[str, object]",
197+
aggregated.setdefault(label, {}),
198+
)
189199
for repo_name, repo_config in section.items():
190-
workspace_section[repo_name] = copy.deepcopy(repo_config)
200+
workspace_section[str(repo_name)] = copy.deepcopy(repo_config)
191201
else:
192202
aggregated[label] = copy.deepcopy(section)
193203
return aggregated
194204

195205

196206
def _collect_duplicate_sections(
197-
items: list[dict[str, t.Any]],
198-
) -> dict[str, list[t.Any]]:
207+
items: list[_OrderedItem],
208+
) -> dict[str, list[object]]:
199209
"""Return mapping of labels to their repeated sections (>= 2 occurrences)."""
200-
occurrences: dict[str, list[t.Any]] = {}
210+
occurrences: dict[str, list[object]] = {}
201211
for entry in items:
202212
label = entry["label"]
203213
occurrences.setdefault(label, []).append(copy.deepcopy(entry["section"]))
@@ -383,9 +393,9 @@ def add_repo(
383393
config_file_path = home_configs[0]
384394

385395
# Load existing config
386-
raw_config: dict[str, t.Any]
387-
duplicate_root_occurrences: dict[str, list[t.Any]]
388-
top_level_items: list[tuple[str, t.Any]]
396+
raw_config: dict[str, object]
397+
duplicate_root_occurrences: dict[str, list[object]]
398+
top_level_items: list[tuple[str, object]]
389399
display_config_path = str(PrivatePath(config_file_path))
390400

391401
if config_file_path.exists() and config_file_path.is_file():
@@ -439,7 +449,7 @@ def add_repo(
439449
new_repo_entry = {"repo": url}
440450

441451
def _ensure_workspace_label_for_merge(
442-
config_data: dict[str, t.Any],
452+
config_data: dict[str, object],
443453
) -> tuple[str, bool]:
444454
workspace_map: dict[pathlib.Path, str] = {}
445455
for label, section in config_data.items():
@@ -473,7 +483,7 @@ def _ensure_workspace_label_for_merge(
473483
return workspace_label, relabelled
474484

475485
def _prepare_no_merge_items(
476-
items: list[dict[str, t.Any]],
486+
items: list[_OrderedItem],
477487
) -> tuple[str, int, bool]:
478488
matching_indexes: list[int] = []
479489
for idx, entry in enumerate(items):

src/vcspull/cli/discover.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,8 @@ def discover_repos(
261261
config_scope = _classify_config_scope(config_file_path, cwd=cwd, home=home)
262262
allow_relative_workspace = config_scope == "project"
263263

264-
raw_config: dict[str, t.Any]
265-
duplicate_root_occurrences: dict[str, list[t.Any]]
264+
raw_config: dict[str, object]
265+
duplicate_root_occurrences: dict[str, list[object]]
266266
if config_file_path.exists() and config_file_path.is_file():
267267
try:
268268
(
@@ -595,18 +595,21 @@ def discover_repos(
595595
)
596596
workspace_map[workspace_path] = workspace_label
597597

598-
if workspace_label not in raw_config:
599-
raw_config[workspace_label] = {}
600-
elif not isinstance(raw_config[workspace_label], dict):
598+
section = raw_config.get(workspace_label)
599+
if section is None:
600+
section = {}
601+
raw_config[workspace_label] = section
602+
elif not isinstance(section, dict):
601603
log.warning(
602604
"Workspace root '%s' in config is not a dictionary. Skipping repo %s.",
603605
workspace_label,
604606
repo_name,
605607
)
606608
continue
607609

608-
if repo_name not in raw_config[workspace_label]:
609-
raw_config[workspace_label][repo_name] = {"repo": repo_url}
610+
workspace_section = t.cast("dict[str, object]", section)
611+
if repo_name not in workspace_section:
612+
workspace_section[repo_name] = {"repo": repo_url}
610613
log.info(
611614
"%s+%s Importing %s'%s'%s (%s%s%s) under '%s%s%s'.",
612615
Fore.GREEN,

0 commit comments

Comments
 (0)