diff --git a/src/poetry/repositories/pypi_repository.py b/src/poetry/repositories/pypi_repository.py index dd9bb7295b8..327e24f69cc 100644 --- a/src/poetry/repositories/pypi_repository.py +++ b/src/poetry/repositories/pypi_repository.py @@ -1,5 +1,6 @@ from __future__ import annotations +import contextlib import logging from typing import TYPE_CHECKING @@ -9,9 +10,11 @@ import requests.adapters from cachecontrol.controller import logger as cache_control_logger +from poetry.core.packages.dependency import Dependency from poetry.core.packages.package import Package from poetry.core.packages.utils.link import Link from poetry.core.version.exceptions import InvalidVersionError +from poetry.core.version.requirements import InvalidRequirementError from poetry.repositories.exceptions import PackageNotFoundError from poetry.repositories.http_repository import HTTPRepository @@ -76,6 +79,18 @@ def search(self, query: str | list[str]) -> list[Package]: level="debug", ) + if not results: + # in cases like PyPI search might not be available, we fallback to explicit searches + # to allow for a nicer ux rather than finding nothing at all + # see: https://discuss.python.org/t/fastly-interfering-with-pypi-search/73597/6 + # + tokens = query if isinstance(query, list) else [query] + for token in tokens: + with contextlib.suppress(InvalidRequirementError): + results.extend( + self.find_packages(Dependency.create_from_pep_508(token)) + ) + return results def get_package_info(self, name: NormalizedName) -> dict[str, Any]: diff --git a/tests/console/commands/test_search.py b/tests/console/commands/test_search.py index 2fbc577ea7c..d440dcfa1cb 100644 --- a/tests/console/commands/test_search.py +++ b/tests/console/commands/test_search.py @@ -2,7 +2,6 @@ import re -from pathlib import Path from typing import TYPE_CHECKING import pytest @@ -19,10 +18,7 @@ from poetry.repositories.legacy_repository import LegacyRepository from tests.types import CommandTesterFactory -TESTS_DIRECTORY = Path(__file__).parent.parent.parent -FIXTURES_DIRECTORY = ( - TESTS_DIRECTORY / "repositories" / "fixtures" / "pypi.org" / "search" -) + SQLALCHEMY_SEARCH_OUTPUT_PYPI = """\ Package Version Source Description broadway-sqlalchemy 0.0.1 PyPI A broadway extension wrapping Flask-SQLAlchemy diff --git a/tests/repositories/fixtures/pypi.org/search/search-disallowed.html b/tests/repositories/fixtures/pypi.org/search/search-disallowed.html new file mode 100644 index 00000000000..25305b1c201 --- /dev/null +++ b/tests/repositories/fixtures/pypi.org/search/search-disallowed.html @@ -0,0 +1,89 @@ + + + + + + + + + + + +
+ A required part of this site couldn’t load. This may be due to a browser + extension, network issues, or browser settings. Please check your + connection, disable any ad blockers, or try using a different browser. +
+ + + diff --git a/tests/repositories/fixtures/pypi.py b/tests/repositories/fixtures/pypi.py index d72577679f2..1ac0e2f7012 100644 --- a/tests/repositories/fixtures/pypi.py +++ b/tests/repositories/fixtures/pypi.py @@ -71,6 +71,25 @@ def lookup(name: str) -> Path | None: return lookup +@pytest.fixture +def with_disallowed_pypi_search_html( + http: type[httpretty], pypi_repository: PyPiRepository +) -> None: + def search_callback( + request: HTTPrettyRequest, uri: str, headers: dict[str, Any] + ) -> HTTPrettyResponse: + search_html = FIXTURE_PATH_REPOSITORIES_PYPI.joinpath( + "search", "search-disallowed.html" + ) + return 200, headers, search_html.read_bytes() + + http.register_uri( + http.GET, + re.compile(r"https://pypi.org/search(\?(.*))?$"), + body=search_callback, + ) + + @pytest.fixture(autouse=True) def pypi_repository( http: type[httpretty], diff --git a/tests/repositories/test_pypi_repository.py b/tests/repositories/test_pypi_repository.py index 34f18b59ef1..52c34c2d806 100644 --- a/tests/repositories/test_pypi_repository.py +++ b/tests/repositories/test_pypi_repository.py @@ -361,3 +361,27 @@ def test_get_release_info_includes_only_supported_types( assert len(release_info["files"]) == 1 assert release_info["files"][0]["file"] == "hbmqtt-0.9.6.tar.gz" + + +@pytest.mark.parametrize( + ("query", "count"), + [ + ("non-existent", 0), # no match + ("requests", 6), # exact match + ("hbmqtt==0.9.6", 1), # exact dependency match + ("requests>=2.18.4", 2), # range dependency match + ("request", 0), # partial match + ("reques*", 0), # bad token + ("reques t", 0), # bad token + (["requests", "hbmqtt"], 7), # list of tokens + ], +) +def test_search_fallbacks_to_find_packages( + query: str | list[str], + count: int, + pypi_repository: PyPiRepository, + with_disallowed_pypi_search_html: None, +) -> None: + repo = pypi_repository + packages = repo.search(query) + assert len(packages) == count