Skip to content

Commit 575c361

Browse files
spoorccclaude
andcommitted
Reduce cyclomatic complexity below 9 in diff and update-patch commands
Extract per-project logic from Diff.__call__ (was CC=11) into _diff_project and from UpdatePatch.__call__ (was CC=10) into _process_project. All methods now have CC <= 8. https://claude.ai/code/session_01RTgPqrX37jFkK843dq65Bu Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent df63293 commit 575c361

4 files changed

Lines changed: 134 additions & 114 deletions

File tree

dfetch/commands/diff.py

Lines changed: 53 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,12 @@
5858
import pathlib
5959

6060
import dfetch.commands.command
61+
import dfetch.manifest.project
6162
from dfetch.log import get_logger
6263
from dfetch.project import create_super_project
6364
from dfetch.project.metadata import Metadata
64-
from dfetch.project.superproject import NoVcsSuperProject, RevisionRange
65-
from dfetch.util.util import catch_runtime_exceptions, in_directory
65+
from dfetch.project.superproject import NoVcsSuperProject, RevisionRange, SuperProject
66+
from dfetch.util.util import in_directory
6667

6768
logger = get_logger(__name__)
6869

@@ -115,51 +116,62 @@ def __call__(self, args: argparse.Namespace) -> None:
115116
)
116117

117118
with in_directory(superproject.root_directory):
118-
exceptions: list[str] = []
119119
projects = superproject.manifest.selected_projects(args.projects)
120120
if not projects:
121121
raise RuntimeError(
122122
f"No (such) project found! {', '.join(args.projects)}"
123123
)
124+
had_errors: bool = False
124125
for project in projects:
125-
with catch_runtime_exceptions(exceptions) as exceptions:
126-
if not os.path.exists(project.destination):
127-
raise RuntimeError(
128-
"You cannot generate a diff of a project that was never fetched"
129-
)
130-
subproject = superproject.get_sub_project(project)
131-
132-
if not subproject:
133-
raise RuntimeError("No subproject!")
134-
135-
old_rev = old_rev or superproject.get_file_revision(
136-
subproject.metadata_path
137-
)
138-
if not old_rev:
139-
raise RuntimeError(
140-
"When not providing any revisions, dfetch starts from"
141-
f" the last revision to {Metadata.FILENAME} in {subproject.local_path}."
142-
" Please either commit this, or specify a revision to start from with --revs"
143-
)
144-
patch = superproject.diff(
145-
project.destination,
146-
revisions=RevisionRange(old_rev, new_rev),
147-
ignore=(Metadata.FILENAME,),
148-
)
149-
150-
msg = self._rev_msg(old_rev, new_rev)
151-
if patch:
152-
patch_path = pathlib.Path(f"{project.name}.patch")
153-
logger.print_info_line(
154-
project.name,
155-
f"Generating patch {patch_path} {msg} in {superproject.root_directory}",
156-
)
157-
patch_path.write_text(patch, encoding="UTF-8")
158-
else:
159-
logger.print_info_line(project.name, f"No diffs found {msg}")
160-
161-
if exceptions:
162-
raise RuntimeError("\n".join(exceptions))
126+
try:
127+
self._diff_project(superproject, project, old_rev, new_rev)
128+
except RuntimeError as exc:
129+
logger.print_warning_line(project.name, str(exc))
130+
had_errors = True
131+
132+
if had_errors:
133+
raise RuntimeError()
134+
135+
def _diff_project(
136+
self,
137+
superproject: SuperProject,
138+
project: dfetch.manifest.project.ProjectEntry,
139+
old_rev: str,
140+
new_rev: str,
141+
) -> None:
142+
"""Generate a diff patch for a single project."""
143+
if not os.path.exists(project.destination):
144+
raise RuntimeError(
145+
"You cannot generate a diff of a project that was never fetched"
146+
)
147+
subproject = superproject.get_sub_project(project)
148+
149+
if not subproject:
150+
raise RuntimeError("No subproject!")
151+
152+
old_rev = old_rev or superproject.get_file_revision(subproject.metadata_path)
153+
if not old_rev:
154+
raise RuntimeError(
155+
"When not providing any revisions, dfetch starts from"
156+
f" the last revision to {Metadata.FILENAME} in {subproject.local_path}."
157+
" Please either commit this, or specify a revision to start from with --revs"
158+
)
159+
patch = superproject.diff(
160+
project.destination,
161+
revisions=RevisionRange(old_rev, new_rev),
162+
ignore=(Metadata.FILENAME,),
163+
)
164+
165+
msg = self._rev_msg(old_rev, new_rev)
166+
if patch:
167+
patch_path = pathlib.Path(f"{project.name}.patch")
168+
logger.print_info_line(
169+
project.name,
170+
f"Generating patch {patch_path} {msg} in {superproject.root_directory}",
171+
)
172+
patch_path.write_text(patch, encoding="UTF-8")
173+
else:
174+
logger.print_info_line(project.name, f"No diffs found {msg}")
163175

164176
@staticmethod
165177
def _parse_revs(revs_arg: str) -> tuple[str, str]:

dfetch/commands/update_patch.py

Lines changed: 75 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,8 @@
4040
from dfetch.project import create_super_project
4141
from dfetch.project.gitsuperproject import GitSuperProject
4242
from dfetch.project.metadata import Metadata
43-
from dfetch.project.superproject import NoVcsSuperProject, RevisionRange
43+
from dfetch.project.superproject import NoVcsSuperProject, RevisionRange, SuperProject
4444
from dfetch.util.util import (
45-
catch_runtime_exceptions,
4645
check_no_path_traversal,
4746
in_directory,
4847
)
@@ -76,7 +75,7 @@ def __call__(self, args: argparse.Namespace) -> None:
7675
"""Perform the update patch."""
7776
superproject = create_super_project()
7877

79-
exceptions: list[str] = []
78+
had_errors: bool = False
8079

8180
if isinstance(superproject, NoVcsSuperProject):
8281
raise RuntimeError(
@@ -88,70 +87,79 @@ def __call__(self, args: argparse.Namespace) -> None:
8887

8988
with in_directory(superproject.root_directory):
9089
for project in superproject.manifest.selected_projects(args.projects):
91-
with catch_runtime_exceptions(exceptions) as exceptions:
92-
subproject = dfetch.project.create_sub_project(project)
93-
destination = project.destination
94-
95-
def _ignored(dst: str = destination) -> list[str]:
96-
return list(superproject.ignored_files(dst))
97-
98-
# Check if the project has a patch, maybe suggest creating one?
99-
if not subproject.patch:
100-
logger.print_warning_line(
101-
project.name,
102-
f'skipped - there is no patch file, use "dfetch diff {project.name}"'
103-
" to generate one instead",
104-
)
105-
continue
106-
107-
# Check if the project was ever fetched
108-
on_disk_version = subproject.on_disk_version()
109-
if not on_disk_version:
110-
logger.print_warning_line(
111-
project.name,
112-
f'skipped - the project was never fetched before, use "dfetch update {project.name}"',
113-
)
114-
continue
115-
116-
# Make sure no uncommitted changes (don't care about ignored files)
117-
if superproject.has_local_changes_in_dir(subproject.local_path):
118-
logger.print_warning_line(
119-
project.name,
120-
f"skipped - Uncommitted changes in {subproject.local_path}",
121-
)
122-
continue
123-
124-
# force update to fetched version from metadata without applying patch
125-
subproject.update(
126-
force=True,
127-
ignored_files_callback=_ignored,
128-
patch_count=len(subproject.patch) - 1,
129-
)
130-
131-
# generate reverse patch
132-
patch_text = superproject.diff(
133-
subproject.local_path,
134-
revisions=RevisionRange("", ""),
135-
ignore=(Metadata.FILENAME,),
136-
reverse=True,
137-
)
138-
139-
# Select patch to overwrite & make backup
140-
if not self._update_patch(
141-
subproject.patch[-1],
142-
superproject.root_directory,
143-
project.name,
144-
patch_text,
145-
):
146-
continue
147-
148-
# force update again to fetched version from metadata but with applying patch
149-
subproject.update(
150-
force=True, ignored_files_callback=_ignored, patch_count=-1
151-
)
152-
153-
if exceptions:
154-
raise RuntimeError("\n".join(exceptions))
90+
try:
91+
self._process_project(superproject, project)
92+
except RuntimeError as exc:
93+
logger.print_warning_line(project.name, str(exc))
94+
had_errors = True
95+
96+
if had_errors:
97+
raise RuntimeError()
98+
99+
def _process_project(
100+
self,
101+
superproject: SuperProject,
102+
project: dfetch.manifest.project.ProjectEntry,
103+
) -> None:
104+
"""Perform the patch update for a single project."""
105+
subproject = dfetch.project.create_sub_project(project)
106+
destination = project.destination
107+
108+
def _ignored(dst: str = destination) -> list[str]:
109+
return list(superproject.ignored_files(dst))
110+
111+
# Check if the project has a patch, maybe suggest creating one?
112+
if not subproject.patch:
113+
logger.print_warning_line(
114+
project.name,
115+
f'skipped - there is no patch file, use "dfetch diff {project.name}"'
116+
" to generate one instead",
117+
)
118+
return
119+
120+
# Check if the project was ever fetched
121+
on_disk_version = subproject.on_disk_version()
122+
if not on_disk_version:
123+
logger.print_warning_line(
124+
project.name,
125+
f'skipped - the project was never fetched before, use "dfetch update {project.name}"',
126+
)
127+
return
128+
129+
# Make sure no uncommitted changes (don't care about ignored files)
130+
if superproject.has_local_changes_in_dir(subproject.local_path):
131+
logger.print_warning_line(
132+
project.name,
133+
f"skipped - Uncommitted changes in {subproject.local_path}",
134+
)
135+
return
136+
137+
# force update to fetched version from metadata without applying patch
138+
subproject.update(
139+
force=True,
140+
ignored_files_callback=_ignored,
141+
patch_count=len(subproject.patch) - 1,
142+
)
143+
144+
# generate reverse patch
145+
patch_text = superproject.diff(
146+
subproject.local_path,
147+
revisions=RevisionRange("", ""),
148+
ignore=(Metadata.FILENAME,),
149+
reverse=True,
150+
)
151+
152+
# Select patch to overwrite & make backup
153+
if not self._update_patch(
154+
subproject.patch[-1],
155+
superproject.root_directory,
156+
project.name,
157+
patch_text,
158+
):
159+
return
160+
161+
# force update again to fetched version from metadata but with applying patch
162+
subproject.update(force=True, ignored_files_callback=_ignored, patch_count=-1)
155163

156164
def _update_patch(
157165
self,

tests/test_report.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,9 @@ def test_multiple_files_partitioned_correctly(self, tmp_path):
174174
"COPYING": bad_guess,
175175
}
176176

177-
with patch("dfetch.commands.report.glob.glob", return_value=["LICENSE", "COPYING"]):
177+
with patch(
178+
"dfetch.commands.report.glob.glob", return_value=["LICENSE", "COPYING"]
179+
):
178180
with patch("dfetch.commands.report.is_license_file", return_value=True):
179181
with patch(
180182
"dfetch.commands.report.guess_license_in_file",
@@ -195,4 +197,4 @@ def test_multiple_files_partitioned_correctly(self, tmp_path):
195197

196198
def test_license_probability_threshold_value():
197199
"""Threshold must be 0.80 as documented."""
198-
assert LICENSE_PROBABILITY_THRESHOLD == 0.80
200+
assert LICENSE_PROBABILITY_THRESHOLD == 0.80

tests/test_stdout_reporter.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,7 @@ def test_add_project_prints_multiple_licenses_comma_separated():
102102
trove_classifier=None,
103103
probability=0.92,
104104
)
105-
scan = LicenseScanResult(
106-
identified=[lic1, lic2], was_scanned=True, threshold=0.80
107-
)
105+
scan = LicenseScanResult(identified=[lic1, lic2], was_scanned=True, threshold=0.80)
108106

109107
meta = _make_metadata()
110108
with patch(
@@ -182,4 +180,4 @@ def test_add_project_unclassified_files_not_shown_in_stdout():
182180
def test_dump_to_file_returns_false():
183181
"""StdoutReporter.dump_to_file should always return False (no file written)."""
184182
reporter = StdoutReporter(_make_manifest())
185-
assert reporter.dump_to_file("report.json") is False
183+
assert reporter.dump_to_file("report.json") is False

0 commit comments

Comments
 (0)