Repository: pypa/pip
Pull request #11277 (2022-07-20 14:01:28)

Respect --no-index from the requirements file
See #11276

SearchScope was extended with an extra parameter to be able to pass-on the
value of no_index as we do with the other parameters. This allows us to respect
its value regardless of the order in which options are evaluated.


Patch:
diff --git a/news/11276.bugfix.rst b/news/11276.bugfix.rst
new file mode 100644
index 000000000..af8f518be
--- /dev/null
+++ b/news/11276.bugfix.rst
@@ -0,0 +1,2 @@
+Fix ``--no-index`` when ``--index-url`` or ``--extra-index-url`` is specified
+inside a requirements file.
diff --git a/src/pip/_internal/index/collector.py b/src/pip/_internal/index/collector.py
index 6e5dac5ad..0291d54f7 100644
--- a/src/pip/_internal/index/collector.py
+++ b/src/pip/_internal/index/collector.py
@@ -558,6 +558,7 @@ class LinkCollector:
         search_scope = SearchScope.create(
             find_links=find_links,
             index_urls=index_urls,
+            no_index=options.no_index,
         )
         link_collector = LinkCollector(
             session=session,
diff --git a/src/pip/_internal/models/search_scope.py b/src/pip/_internal/models/search_scope.py
index e4e54c2f4..a64af7389 100644
--- a/src/pip/_internal/models/search_scope.py
+++ b/src/pip/_internal/models/search_scope.py
@@ -20,13 +20,14 @@ class SearchScope:
     Encapsulates the locations that pip is configured to search.
     """
 
-    __slots__ = ["find_links", "index_urls"]
+    __slots__ = ["find_links", "index_urls", "no_index"]
 
     @classmethod
     def create(
         cls,
         find_links: List[str],
         index_urls: List[str],
+        no_index: bool,
     ) -> "SearchScope":
         """
         Create a SearchScope object after normalizing the `find_links`.
@@ -60,15 +61,18 @@ class SearchScope:
         return cls(
             find_links=built_find_links,
             index_urls=index_urls,
+            no_index=no_index,
         )
 
     def __init__(
         self,
         find_links: List[str],
         index_urls: List[str],
+        no_index: bool,
     ) -> None:
         self.find_links = find_links
         self.index_urls = index_urls
+        self.no_index = no_index
 
     def get_formatted_locations(self) -> str:
         lines = []
diff --git a/src/pip/_internal/req/req_file.py b/src/pip/_internal/req/req_file.py
index 4550c72d6..06ea6f277 100644
--- a/src/pip/_internal/req/req_file.py
+++ b/src/pip/_internal/req/req_file.py
@@ -229,11 +229,13 @@ def handle_option_line(
     if finder:
         find_links = finder.find_links
         index_urls = finder.index_urls
-        if opts.index_url:
-            index_urls = [opts.index_url]
+        no_index = finder.search_scope.no_index
         if opts.no_index is True:
+            no_index = True
             index_urls = []
-        if opts.extra_index_urls:
+        if opts.index_url and not no_index:
+            index_urls = [opts.index_url]
+        if opts.extra_index_urls and not no_index:
             index_urls.extend(opts.extra_index_urls)
         if opts.find_links:
             # FIXME: it would be nice to keep track of the source
@@ -253,6 +255,7 @@ def handle_option_line(
         search_scope = SearchScope(
             find_links=find_links,
             index_urls=index_urls,
+            no_index=no_index,
         )
         finder.search_scope = search_scope
 


Test patch:
diff --git a/tests/functional/test_build_env.py b/tests/functional/test_build_env.py
index 693624618..437adb995 100644
--- a/tests/functional/test_build_env.py
+++ b/tests/functional/test_build_env.py
@@ -41,7 +41,7 @@ def run_with_build_env(
 
             link_collector = LinkCollector(
                 session=PipSession(),
-                search_scope=SearchScope.create([{scratch!r}], []),
+                search_scope=SearchScope.create([{scratch!r}], [], False),
             )
             selection_prefs = SelectionPreferences(
                 allow_yanked=True,
diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py
index 8774d8bc1..d30deced1 100644
--- a/tests/lib/__init__.py
+++ b/tests/lib/__init__.py
@@ -87,7 +87,11 @@ def make_test_search_scope(
     if index_urls is None:
         index_urls = []
 
-    return SearchScope.create(find_links=find_links, index_urls=index_urls)
+    return SearchScope.create(
+        find_links=find_links,
+        index_urls=index_urls,
+        no_index=False,
+    )
 
 
 def make_test_link_collector(
diff --git a/tests/unit/resolution_resolvelib/conftest.py b/tests/unit/resolution_resolvelib/conftest.py
index 9ef9f8c5c..a4ee32444 100644
--- a/tests/unit/resolution_resolvelib/conftest.py
+++ b/tests/unit/resolution_resolvelib/conftest.py
@@ -23,7 +23,7 @@ from tests.lib import TestData
 @pytest.fixture
 def finder(data: TestData) -> Iterator[PackageFinder]:
     session = PipSession()
-    scope = SearchScope([str(data.packages)], [])
+    scope = SearchScope([str(data.packages)], [], False)
     collector = LinkCollector(session, scope)
     prefs = SelectionPreferences(allow_yanked=False)
     finder = PackageFinder.create(collector, prefs)
diff --git a/tests/unit/test_index.py b/tests/unit/test_index.py
index cd3c748b7..78837b94e 100644
--- a/tests/unit/test_index.py
+++ b/tests/unit/test_index.py
@@ -593,7 +593,7 @@ class TestPackageFinder:
         """
         link_collector = LinkCollector(
             session=PipSession(),
-            search_scope=SearchScope([], []),
+            search_scope=SearchScope([], [], False),
         )
         selection_prefs = SelectionPreferences(
             allow_yanked=True,
@@ -614,7 +614,7 @@ class TestPackageFinder:
         """
         link_collector = LinkCollector(
             session=PipSession(),
-            search_scope=SearchScope([], []),
+            search_scope=SearchScope([], [], False),
         )
         finder = PackageFinder.create(
             link_collector=link_collector,
@@ -629,7 +629,7 @@ class TestPackageFinder:
         """
         link_collector = LinkCollector(
             session=PipSession(),
-            search_scope=SearchScope([], []),
+            search_scope=SearchScope([], [], False),
         )
         target_python = TargetPython(py_version_info=(3, 7, 3))
         finder = PackageFinder.create(
@@ -649,7 +649,7 @@ class TestPackageFinder:
         """
         link_collector = LinkCollector(
             session=PipSession(),
-            search_scope=SearchScope([], []),
+            search_scope=SearchScope([], [], False),
         )
         finder = PackageFinder.create(
             link_collector=link_collector,
@@ -668,7 +668,7 @@ class TestPackageFinder:
         """
         link_collector = LinkCollector(
             session=PipSession(),
-            search_scope=SearchScope([], []),
+            search_scope=SearchScope([], [], False),
         )
         selection_prefs = SelectionPreferences(allow_yanked=allow_yanked)
         finder = PackageFinder.create(
@@ -684,7 +684,7 @@ class TestPackageFinder:
         """
         link_collector = LinkCollector(
             session=PipSession(),
-            search_scope=SearchScope([], []),
+            search_scope=SearchScope([], [], False),
         )
         selection_prefs = SelectionPreferences(
             allow_yanked=True,
@@ -702,7 +702,7 @@ class TestPackageFinder:
         """
         link_collector = LinkCollector(
             session=PipSession(),
-            search_scope=SearchScope([], []),
+            search_scope=SearchScope([], [], False),
         )
         format_control = FormatControl(set(), {":all:"})
         selection_prefs = SelectionPreferences(
@@ -743,7 +743,7 @@ class TestPackageFinder:
 
         link_collector = LinkCollector(
             session=PipSession(),
-            search_scope=SearchScope([], []),
+            search_scope=SearchScope([], [], False),
         )
 
         finder = PackageFinder(
@@ -793,7 +793,7 @@ class TestPackageFinder:
         )
         link_collector = LinkCollector(
             session=PipSession(),
-            search_scope=SearchScope([], []),
+            search_scope=SearchScope([], [], False),
         )
         finder = PackageFinder(
             link_collector=link_collector,
diff --git a/tests/unit/test_req_file.py b/tests/unit/test_req_file.py
index 8928fd169..1365e1584 100644
--- a/tests/unit/test_req_file.py
+++ b/tests/unit/test_req_file.py
@@ -395,6 +395,13 @@ class TestProcessLine:
         line_processor("--no-index", "file", 1, finder=finder)
         assert finder.index_urls == []
 
+    def test_set_finder_no_index_is_remembered_for_later_invocations(
+        self, line_processor: LineProcessor, finder: PackageFinder
+    ) -> None:
+        line_processor("--no-index", "file", 1, finder=finder)
+        line_processor("--index-url=url", "file", 1, finder=finder)
+        assert finder.index_urls == []
+
     def test_set_finder_index_url(
         self, line_processor: LineProcessor, finder: PackageFinder, session: PipSession
     ) -> None:
diff --git a/tests/unit/test_search_scope.py b/tests/unit/test_search_scope.py
index ef21c10b8..d81283416 100644
--- a/tests/unit/test_search_scope.py
+++ b/tests/unit/test_search_scope.py
@@ -16,6 +16,7 @@ class TestSearchScope:
         search_scope = SearchScope(
             find_links=find_links,
             index_urls=index_urls,
+            no_index=False,
         )
 
         result = search_scope.get_formatted_locations()
@@ -29,6 +30,7 @@ class TestSearchScope:
         search_scope = SearchScope(
             find_links=[],
             index_urls=["file://index1/", "file://index2"],
+            no_index=False,
         )
         req = install_req_from_line("Complex_Name")
         assert req.name is not None