From fb0d970fa3ad5cc2a8672bd7f985369668873b6a Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Wed, 15 Jan 2025 21:52:06 +0100 Subject: [PATCH 1/2] chore(test): remove unused variables --- tests/console/commands/test_search.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 From acbfc43c1855e1c7aaf38b434d9dd4ee078bb51d Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Wed, 15 Jan 2025 22:08:36 +0100 Subject: [PATCH 2/2] fix(search): fallback to find packages for PyPI If not results are found when searching PyPI fallback to using the find package mechanism to at the minimum list any matches for valid PEP 508 specifications. Resolves: #9884 --- src/poetry/repositories/pypi_repository.py | 15 ++++ .../pypi.org/search/search-disallowed.html | 89 +++++++++++++++++++ tests/repositories/fixtures/pypi.py | 19 ++++ tests/repositories/test_pypi_repository.py | 24 +++++ 4 files changed, 147 insertions(+) create mode 100644 tests/repositories/fixtures/pypi.org/search/search-disallowed.html 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/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