Skip to content

Commit 07c1396

Browse files
rd4398claude
andcommitted
refactor: rename RequirementResolver to BootstrapRequirementResolver
Rename class and files to better reflect purpose: - RequirementResolver → BootstrapRequirementResolver - requirement_resolver.py → bootstrap_requirement_resolver.py - test_requirement_resolver.py → test_bootstrap_requirement_resolver.py The new name clarifies that this resolver is specifically for the bootstrap process, reducing naming confusion. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Rohan Devasthale <rdevasth@redhat.com>
1 parent 3cd2c8e commit 07c1396

3 files changed

Lines changed: 90 additions & 102 deletions

File tree

src/fromager/requirement_resolver.py renamed to src/fromager/bootstrap_requirement_resolver.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
logger = logging.getLogger(__name__)
2323

2424

25-
class RequirementResolver:
26-
"""Resolve package requirements from PyPI or dependency graph.
25+
class BootstrapRequirementResolver:
26+
"""Resolve package requirements from PyPI or dependency graph during bootstrap.
2727
28-
Single Responsibility: Coordinate resolution strategies.
28+
Single Responsibility: Coordinate resolution strategies for bootstrap process.
2929
Reason to Change: Resolution algorithm or provider priorities change.
3030
3131
Resolution strategies (in order):

src/fromager/bootstrapper.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
from packaging.version import Version
2020

2121
from . import (
22+
bootstrap_requirement_resolver,
2223
build_environment,
2324
dependencies,
2425
finders,
2526
hooks,
2627
progress,
27-
requirement_resolver,
2828
resolver,
2929
server,
3030
sources,
@@ -103,8 +103,8 @@ def __init__(
103103
self.test_mode = test_mode
104104
self.why: list[tuple[RequirementType, Requirement, Version]] = []
105105

106-
# Delegate resolution to RequirementResolver
107-
self._resolver = requirement_resolver.RequirementResolver(
106+
# Delegate resolution to BootstrapRequirementResolver
107+
self._resolver = bootstrap_requirement_resolver.BootstrapRequirementResolver(
108108
ctx=ctx,
109109
prev_graph=prev_graph,
110110
)
@@ -180,7 +180,7 @@ def resolve_version(
180180
181181
Git URL resolution stays in Bootstrapper because it requires
182182
build orchestration (BuildEnvironment, build dependencies).
183-
Delegates PyPI/graph resolution to RequirementResolver.
183+
Delegates PyPI/graph resolution to BootstrapRequirementResolver.
184184
"""
185185
if req.url:
186186
if req_type != RequirementType.TOP_LEVEL:

tests/test_requirement_resolver.py renamed to tests/test_bootstrap_requirement_resolver.py

Lines changed: 83 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Tests for requirement_resolver module."""
1+
"""Tests for bootstrap_requirement_resolver module."""
22

33
from unittest.mock import MagicMock, patch
44

@@ -7,9 +7,9 @@
77
from packaging.utils import canonicalize_name
88
from packaging.version import Version
99

10+
from fromager.bootstrap_requirement_resolver import BootstrapRequirementResolver
1011
from fromager.context import WorkContext
1112
from fromager.dependency_graph import DependencyGraph
12-
from fromager.requirement_resolver import RequirementResolver
1313
from fromager.requirements_file import RequirementType
1414

1515
# Test fixture: previous dependency graph
@@ -65,8 +65,8 @@
6565

6666

6767
def test_resolve_from_graph_no_changes(tmp_context: WorkContext) -> None:
68-
"""RequirementResolver resolves from previous graph with no changes."""
69-
resolver = RequirementResolver(tmp_context, old_graph)
68+
"""BootstrapRequirementResolver resolves from previous graph with no changes."""
69+
resolver = BootstrapRequirementResolver(tmp_context, old_graph)
7070

7171
# Resolving new dependency that doesn't exist in graph
7272
assert (
@@ -105,8 +105,8 @@ def test_resolve_from_graph_no_changes(tmp_context: WorkContext) -> None:
105105

106106

107107
def test_resolve_from_graph_install_dep_upgrade(tmp_context: WorkContext) -> None:
108-
"""RequirementResolver prefers top-level requirements over history."""
109-
resolver = RequirementResolver(tmp_context, old_graph)
108+
"""BootstrapRequirementResolver prefers top-level requirements over history."""
109+
resolver = BootstrapRequirementResolver(tmp_context, old_graph)
110110

111111
# Simulating new bootstrap with a toplevel requirement of pbr==8
112112
tmp_context.dependency_graph.add_dependency(
@@ -143,8 +143,8 @@ def test_resolve_from_graph_install_dep_upgrade(tmp_context: WorkContext) -> Non
143143

144144

145145
def test_resolve_from_graph_install_dep_downgrade(tmp_context: WorkContext) -> None:
146-
"""RequirementResolver handles version downgrades from top-level requirements."""
147-
resolver = RequirementResolver(tmp_context, old_graph)
146+
"""BootstrapRequirementResolver handles version downgrades from top-level requirements."""
147+
resolver = BootstrapRequirementResolver(tmp_context, old_graph)
148148

149149
# Simulating new bootstrap with a toplevel requirement of pbr<=6
150150
tmp_context.dependency_graph.add_dependency(
@@ -181,8 +181,8 @@ def test_resolve_from_graph_install_dep_downgrade(tmp_context: WorkContext) -> N
181181

182182

183183
def test_resolve_from_graph_toplevel_dep(tmp_context: WorkContext) -> None:
184-
"""RequirementResolver resolves top-level dependencies correctly."""
185-
resolver = RequirementResolver(tmp_context, old_graph)
184+
"""BootstrapRequirementResolver resolves top-level dependencies correctly."""
185+
resolver = BootstrapRequirementResolver(tmp_context, old_graph)
186186

187187
# Simulating new bootstrap with a toplevel requirement for foo
188188
tmp_context.dependency_graph.add_dependency(
@@ -236,8 +236,8 @@ def test_resolve_from_graph_toplevel_dep(tmp_context: WorkContext) -> None:
236236

237237

238238
def test_resolve_from_graph_no_previous_graph(tmp_context: WorkContext) -> None:
239-
"""RequirementResolver returns None when no previous graph is available."""
240-
resolver = RequirementResolver(tmp_context, prev_graph=None)
239+
"""BootstrapRequirementResolver returns None when no previous graph is available."""
240+
resolver = BootstrapRequirementResolver(tmp_context, prev_graph=None)
241241

242242
assert (
243243
resolver._resolve_from_graph(
@@ -278,7 +278,7 @@ def test_resolve_from_graph_new_parent_reuses_existing_version(
278278
req_version=Version("25.0"),
279279
)
280280

281-
resolver = RequirementResolver(tmp_context, prev_graph)
281+
resolver = BootstrapRequirementResolver(tmp_context, prev_graph)
282282

283283
# Resolve packaging>=24.0 via a NEW parent "wheel" that is NOT in prev_graph.
284284
# packaging==25.0 satisfies >=24.0 and exists in the graph, so it should
@@ -322,7 +322,7 @@ def test_resolve_from_graph_different_req_type_reuses_existing_version(
322322
req_version=Version("2.0"),
323323
)
324324

325-
resolver = RequirementResolver(tmp_context, prev_graph)
325+
resolver = BootstrapRequirementResolver(tmp_context, prev_graph)
326326

327327
# Now resolve bar>=1.5 as an INSTALL dep of foo (different req_type).
328328
# bar==2.0 satisfies >=1.5 and exists in the graph under the same parent
@@ -381,7 +381,7 @@ def test_resolve_from_graph_parent_specific_preferred_over_name_fallback(
381381
req_version=Version("3.0"),
382382
)
383383

384-
resolver = RequirementResolver(tmp_context, prev_graph)
384+
resolver = BootstrapRequirementResolver(tmp_context, prev_graph)
385385

386386
# Resolve bar>=1.0 as install dep of foo. The parent-specific lookup
387387
# should return bar==2.0 (from foo), NOT bar==3.0 (from baz via fallback).
@@ -422,7 +422,7 @@ def test_resolve_from_graph_name_fallback_returns_none_for_missing_package(
422422
req_version=Version("2.0"),
423423
)
424424

425-
resolver = RequirementResolver(tmp_context, prev_graph)
425+
resolver = BootstrapRequirementResolver(tmp_context, prev_graph)
426426

427427
result = resolver._resolve_from_graph(
428428
req=Requirement("missing-pkg>=1.0"),
@@ -434,8 +434,8 @@ def test_resolve_from_graph_name_fallback_returns_none_for_missing_package(
434434

435435

436436
def test_resolve_rejects_git_urls_for_source(tmp_context: WorkContext) -> None:
437-
"""RequirementResolver.resolve() rejects git URLs when pre_built=False."""
438-
resolver = RequirementResolver(tmp_context)
437+
"""BootstrapRequirementResolver.resolve() rejects git URLs when pre_built=False."""
438+
resolver = BootstrapRequirementResolver(tmp_context)
439439

440440
with pytest.raises(
441441
ValueError, match="Git URL requirements must be handled by Bootstrapper"
@@ -448,20 +448,17 @@ def test_resolve_rejects_git_urls_for_source(tmp_context: WorkContext) -> None:
448448
)
449449

450450

451-
@patch("fromager.requirement_resolver.wheels.resolve_prebuilt_wheel_all")
452-
@patch("fromager.requirement_resolver.wheels.get_wheel_server_urls")
451+
@patch("fromager.resolver.resolve_from_provider")
453452
def test_resolve_allows_git_urls_for_prebuilt(
454-
mock_get_servers: MagicMock,
455-
mock_resolve_wheel: MagicMock,
453+
mock_resolve: MagicMock,
456454
tmp_context: WorkContext,
457455
) -> None:
458-
"""RequirementResolver.resolve() allows git URLs when pre_built=True (test mode fallback)."""
459-
resolver = RequirementResolver(tmp_context)
456+
"""BootstrapRequirementResolver.resolve() allows git URLs when pre_built=True (test mode fallback)."""
457+
resolver = BootstrapRequirementResolver(tmp_context)
460458
req = Requirement("mypkg @ git+https://github.com/example/repo.git")
461459

462-
# Mock wheel resolution to return expected result (as list)
463-
mock_get_servers.return_value = ["https://pypi.org/simple"]
464-
mock_resolve_wheel.return_value = [
460+
# Mock resolution to return expected result (as list)
461+
mock_resolve.return_value = [
465462
("https://files.pythonhosted.org/mypkg-1.0-py3-none-any.whl", Version("1.0"))
466463
]
467464

@@ -473,32 +470,30 @@ def test_resolve_allows_git_urls_for_prebuilt(
473470
parent_req=None,
474471
)
475472

476-
# Verify it routed to wheel resolution
477-
mock_resolve_wheel.assert_called_once()
473+
# Verify resolution was called
474+
mock_resolve.assert_called_once()
478475
assert url == "https://files.pythonhosted.org/mypkg-1.0-py3-none-any.whl"
479476
assert version == Version("1.0")
480477

481478

482-
@patch("fromager.requirement_resolver.wheels.resolve_prebuilt_wheel_all")
483-
@patch("fromager.requirement_resolver.wheels.get_wheel_server_urls")
479+
@patch("fromager.resolver.resolve_from_provider")
484480
def test_resolve_auto_routes_to_prebuilt(
485-
mock_get_servers: MagicMock,
486-
mock_resolve_wheel: MagicMock,
481+
mock_resolve: MagicMock,
487482
tmp_context: WorkContext,
488483
) -> None:
489-
"""resolve(pre_built=None) with pbi.pre_built=True routes to wheels.resolve_prebuilt_wheel_all."""
484+
"""resolve(pre_built=None) with pbi.pre_built=True routes to wheel resolution."""
490485
req = Requirement("setuptools>=40")
491486

492487
# Mock package build info to return pre_built=True
493488
mock_pbi = MagicMock()
494489
mock_pbi.pre_built = True
490+
mock_pbi.wheel_server_url = None
495491

496492
with patch.object(tmp_context, "package_build_info", return_value=mock_pbi):
497-
resolver = RequirementResolver(tmp_context)
493+
resolver = BootstrapRequirementResolver(tmp_context)
498494

499-
# Mock wheel resolution to return expected result (as list)
500-
mock_get_servers.return_value = ["https://pypi.org/simple"]
501-
mock_resolve_wheel.return_value = [
495+
# Mock resolution to return expected result (as list)
496+
mock_resolve.return_value = [
502497
(
503498
"https://files.pythonhosted.org/setuptools-1.0-py3-none-any.whl",
504499
Version("1.0"),
@@ -513,29 +508,35 @@ def test_resolve_auto_routes_to_prebuilt(
513508
pre_built=None,
514509
)
515510

516-
# Verify it routed to wheel resolution
517-
mock_resolve_wheel.assert_called_once()
511+
# Verify resolution was called
512+
mock_resolve.assert_called_once()
518513
assert url == "https://files.pythonhosted.org/setuptools-1.0-py3-none-any.whl"
519514
assert version == Version("1.0")
520515

521516

522-
@patch("fromager.requirement_resolver.sources.resolve_source_all")
517+
@patch("fromager.resolver.resolve_from_provider")
523518
def test_resolve_auto_routes_to_source(
524-
mock_resolve_source: MagicMock,
519+
mock_resolve: MagicMock,
525520
tmp_context: WorkContext,
526521
) -> None:
527-
"""resolve(pre_built=None) with pbi.pre_built=False routes to sources.resolve_source_all."""
522+
"""resolve(pre_built=None) with pbi.pre_built=False routes to source resolution."""
528523
req = Requirement("mypackage>=1.0")
529524

530525
# Mock package build info to return pre_built=False
531526
mock_pbi = MagicMock()
532527
mock_pbi.pre_built = False
528+
mock_pbi.resolver_include_sdists = True
529+
mock_pbi.resolver_include_wheels = True
530+
mock_pbi.resolver_ignore_platform = True
531+
mock_pbi.resolver_sdist_server_url = MagicMock(
532+
return_value="https://pypi.org/simple"
533+
)
533534

534535
with patch.object(tmp_context, "package_build_info", return_value=mock_pbi):
535-
resolver = RequirementResolver(tmp_context)
536+
resolver = BootstrapRequirementResolver(tmp_context)
536537

537538
# Mock source resolution to return expected result (as list)
538-
mock_resolve_source.return_value = [
539+
mock_resolve.return_value = [
539540
("https://files.pythonhosted.org/mypackage-2.0.tar.gz", Version("2.0"))
540541
]
541542

@@ -547,70 +548,57 @@ def test_resolve_auto_routes_to_source(
547548
pre_built=None,
548549
)
549550

550-
# Verify it routed to source resolution
551-
mock_resolve_source.assert_called_once()
551+
# Verify resolution was called
552+
mock_resolve.assert_called_once()
552553
assert url == "https://files.pythonhosted.org/mypackage-2.0.tar.gz"
553554
assert version == Version("2.0")
554555

555556

556-
@patch("fromager.requirement_resolver.wheels.resolve_prebuilt_wheel_all")
557-
@patch("fromager.requirement_resolver.wheels.get_wheel_server_urls")
558-
@patch("fromager.requirement_resolver.sources.resolve_source_all")
557+
@patch("fromager.resolver.resolve_from_provider")
559558
def test_resolve_prebuilt_after_source_uses_separate_cache(
560-
mock_resolve_source: MagicMock,
561-
mock_get_servers: MagicMock,
562-
mock_resolve_wheel: MagicMock,
559+
mock_resolve: MagicMock,
563560
tmp_context: WorkContext,
564561
) -> None:
565562
"""resolve(pre_built=True) after same req resolved as source uses separate cache."""
566563
req = Requirement("testpkg==1.5")
567564

568-
# Mock package build info to return pre_built=False initially
569-
mock_pbi = MagicMock()
570-
mock_pbi.pre_built = False
571-
572-
with patch.object(tmp_context, "package_build_info", return_value=mock_pbi):
573-
resolver = RequirementResolver(tmp_context)
574-
575-
# Mock source resolution (as list)
576-
mock_resolve_source.return_value = [
577-
("https://files.pythonhosted.org/testpkg-1.5.tar.gz", Version("1.5"))
578-
]
579-
580-
# First call: resolve as source (pre_built=None, auto-detects to False)
581-
url1, version1 = resolver.resolve(
582-
req=req,
583-
req_type=RequirementType.INSTALL,
584-
parent_req=None,
585-
pre_built=None,
586-
)
587-
588-
assert url1 == "https://files.pythonhosted.org/testpkg-1.5.tar.gz"
589-
assert version1 == Version("1.5")
590-
assert mock_resolve_source.call_count == 1
591-
592-
# Mock wheel resolution for second call (as list)
593-
mock_get_servers.return_value = ["https://pypi.org/simple"]
594-
mock_resolve_wheel.return_value = [
565+
# Set up side_effect to return different results for each call
566+
mock_resolve.side_effect = [
567+
# First call: source resolution
568+
[("https://files.pythonhosted.org/testpkg-1.5.tar.gz", Version("1.5"))],
569+
# Second call: wheel resolution
570+
[
595571
(
596572
"https://files.pythonhosted.org/testpkg-1.5-py3-none-any.whl",
597573
Version("1.5"),
598574
)
599-
]
575+
],
576+
]
600577

601-
# Second call: resolve same req as prebuilt (explicit pre_built=True)
602-
# This should NOT return the cached source result
603-
url2, version2 = resolver.resolve(
604-
req=req,
605-
req_type=RequirementType.INSTALL,
606-
parent_req=None,
607-
pre_built=True,
608-
)
578+
resolver = BootstrapRequirementResolver(tmp_context)
579+
580+
# First call: resolve as source (explicit pre_built=False)
581+
url1, version1 = resolver.resolve(
582+
req=req,
583+
req_type=RequirementType.INSTALL,
584+
parent_req=None,
585+
pre_built=False,
586+
)
609587

610-
# Verify it called wheel resolution (not cached)
611-
assert mock_resolve_wheel.call_count == 1
612-
assert url2 == "https://files.pythonhosted.org/testpkg-1.5-py3-none-any.whl"
613-
assert version2 == Version("1.5")
588+
assert url1 == "https://files.pythonhosted.org/testpkg-1.5.tar.gz"
589+
assert version1 == Version("1.5")
590+
assert mock_resolve.call_count == 1
591+
592+
# Second call: resolve same req as prebuilt (explicit pre_built=True)
593+
# This should NOT return the cached source result
594+
url2, version2 = resolver.resolve(
595+
req=req,
596+
req_type=RequirementType.INSTALL,
597+
parent_req=None,
598+
pre_built=True,
599+
)
614600

615-
# Verify source was only called once (first time, not second)
616-
assert mock_resolve_source.call_count == 1
601+
# Verify it called resolution again (not cached) because cache keys differ
602+
assert mock_resolve.call_count == 2
603+
assert url2 == "https://files.pythonhosted.org/testpkg-1.5-py3-none-any.whl"
604+
assert version2 == Version("1.5")

0 commit comments

Comments
 (0)