From e172d61c6c8268b168d57b44d75e408bfa1a4f39 Mon Sep 17 00:00:00 2001 From: Miro <200482516+Mirochill@users.noreply.github.com> Date: Mon, 18 May 2026 20:25:43 +0200 Subject: [PATCH 1/4] Support bare local file paths in requirements parser --- src/poetry/utils/dependency_specification.py | 25 +++++++++++++------- tests/utils/test_dependency_specification.py | 19 +++++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/poetry/utils/dependency_specification.py b/src/poetry/utils/dependency_specification.py index 2c73a5006b5..c6a1370dfad 100644 --- a/src/poetry/utils/dependency_specification.py +++ b/src/poetry/utils/dependency_specification.py @@ -71,6 +71,11 @@ def __init__( def parse(self, requirement: str) -> DependencySpec: requirement = requirement.strip() + if os.path.sep not in requirement and "/" not in requirement: + specification = self._parse_path(requirement) + if specification is not None: + return specification + specification = self._parse_pep508(requirement) if specification is not None: @@ -153,18 +158,22 @@ def _parse_url(self, requirement: str) -> DependencySpec | None: return None def _parse_path(self, requirement: str) -> DependencySpec | None: - if (os.path.sep in requirement or "/" in requirement) and ( - self._cwd.joinpath(requirement).exists() - or ( - Path(requirement).expanduser().exists() - and Path(requirement).expanduser().is_absolute() - ) + path = Path(requirement).expanduser() + relative_path = self._cwd.joinpath(requirement) + is_explicit_path = os.path.sep in requirement or "/" in requirement + is_relative_path = relative_path.exists() + is_relative_file = is_relative_path and relative_path.is_file() + is_absolute_path = path.exists() and path.is_absolute() + + if ( + (is_explicit_path and is_relative_path) + or is_relative_file + or is_absolute_path ): - path = Path(requirement).expanduser() is_absolute = path.is_absolute() if not path.is_absolute(): - path = self._cwd.joinpath(requirement) + path = relative_path if path.is_file(): package = self._direct_origin.get_package_from_file(path.resolve()) diff --git a/tests/utils/test_dependency_specification.py b/tests/utils/test_dependency_specification.py index 6e13423b9fd..bd3e4cdc2a4 100644 --- a/tests/utils/test_dependency_specification.py +++ b/tests/utils/test_dependency_specification.py @@ -18,6 +18,7 @@ from poetry.utils.cache import ArtifactCache from poetry.utils.dependency_specification import DependencySpec + from tests.types import FixtureDirGetter @pytest.mark.parametrize( @@ -190,3 +191,21 @@ def _mock(self: Path) -> bool: ) for specification in expected_variants ) + + +def test_parse_bare_local_file_dependency( + tmp_path: Path, + fixture_dir: FixtureDirGetter, + artifact_cache: ArtifactCache, +) -> None: + source = fixture_dir("distributions") / "demo-0.1.2-py2.py3-none-any.whl" + path = tmp_path / source.name + path.write_bytes(source.read_bytes()) + + assert RequirementsParser( + artifact_cache=artifact_cache, + cwd=tmp_path, + ).parse(path.name) == { + "name": "demo", + "path": path.name, + } From c1782eacfefd17f8f03b299d66e736c4fe2bc79a Mon Sep 17 00:00:00 2001 From: Miro <200482516+Mirochill@users.noreply.github.com> Date: Sun, 24 May 2026 18:16:14 +0200 Subject: [PATCH 2/4] Address review feedback on bare path parsing --- src/poetry/utils/dependency_specification.py | 16 +++++++++------- tests/utils/test_dependency_specification.py | 12 ++++++++++++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/poetry/utils/dependency_specification.py b/src/poetry/utils/dependency_specification.py index c6a1370dfad..070131b8a0a 100644 --- a/src/poetry/utils/dependency_specification.py +++ b/src/poetry/utils/dependency_specification.py @@ -27,6 +27,7 @@ BaseSpec = TypeVar("BaseSpec", DependencySpec, InlineTable) GIT_URL_SCHEMES = {"git+http", "git+https", "git+ssh"} +PACKAGE_ARCHIVE_EXTENSIONS = (".whl", ".tar.gz", ".tar.bz2", ".tar.xz", ".zip") def dependency_to_specification( @@ -71,7 +72,11 @@ def __init__( def parse(self, requirement: str) -> DependencySpec: requirement = requirement.strip() - if os.path.sep not in requirement and "/" not in requirement: + if ( + os.path.sep not in requirement + and "/" not in requirement + and requirement.lower().endswith(PACKAGE_ARCHIVE_EXTENSIONS) + ): specification = self._parse_path(requirement) if specification is not None: return specification @@ -162,14 +167,11 @@ def _parse_path(self, requirement: str) -> DependencySpec | None: relative_path = self._cwd.joinpath(requirement) is_explicit_path = os.path.sep in requirement or "/" in requirement is_relative_path = relative_path.exists() - is_relative_file = is_relative_path and relative_path.is_file() - is_absolute_path = path.exists() and path.is_absolute() + is_absolute_path = path.is_absolute() and path.exists() if ( - (is_explicit_path and is_relative_path) - or is_relative_file - or is_absolute_path - ): + is_relative_path and (is_explicit_path or relative_path.is_file()) + ) or is_absolute_path: is_absolute = path.is_absolute() if not path.is_absolute(): diff --git a/tests/utils/test_dependency_specification.py b/tests/utils/test_dependency_specification.py index bd3e4cdc2a4..54dde9a0f96 100644 --- a/tests/utils/test_dependency_specification.py +++ b/tests/utils/test_dependency_specification.py @@ -209,3 +209,15 @@ def test_parse_bare_local_file_dependency( "name": "demo", "path": path.name, } + + +def test_parse_bare_package_name_does_not_check_path( + mocker: MockerFixture, + artifact_cache: ArtifactCache, +) -> None: + exists = mocker.patch("pathlib.Path.exists") + + assert RequirementsParser(artifact_cache=artifact_cache).parse("demo") == { + "name": "demo" + } + exists.assert_not_called() From da5249373b1af2a914a374c555f3bdd131c4e2f4 Mon Sep 17 00:00:00 2001 From: Miro <200482516+Mirochill@users.noreply.github.com> Date: Sun, 24 May 2026 18:22:02 +0200 Subject: [PATCH 3/4] Fix bare package name parser test --- tests/utils/test_dependency_specification.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/utils/test_dependency_specification.py b/tests/utils/test_dependency_specification.py index 54dde9a0f96..41358c77acc 100644 --- a/tests/utils/test_dependency_specification.py +++ b/tests/utils/test_dependency_specification.py @@ -215,9 +215,8 @@ def test_parse_bare_package_name_does_not_check_path( mocker: MockerFixture, artifact_cache: ArtifactCache, ) -> None: - exists = mocker.patch("pathlib.Path.exists") + parser = RequirementsParser(artifact_cache=artifact_cache) + parse_path = mocker.patch.object(parser, "_parse_path", wraps=parser._parse_path) - assert RequirementsParser(artifact_cache=artifact_cache).parse("demo") == { - "name": "demo" - } - exists.assert_not_called() + assert parser.parse("demo") == {"name": "demo"} + parse_path.assert_not_called() From 887f055707bae7bd6b9c84f108fccdcc359d6877 Mon Sep 17 00:00:00 2001 From: Miro <200482516+Mirochill@users.noreply.github.com> Date: Sun, 24 May 2026 18:28:37 +0200 Subject: [PATCH 4/4] Skip path probing for plain requirement names --- src/poetry/utils/dependency_specification.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/poetry/utils/dependency_specification.py b/src/poetry/utils/dependency_specification.py index 070131b8a0a..16d415b4bf1 100644 --- a/src/poetry/utils/dependency_specification.py +++ b/src/poetry/utils/dependency_specification.py @@ -71,12 +71,11 @@ def __init__( def parse(self, requirement: str) -> DependencySpec: requirement = requirement.strip() + is_explicit_path = os.path.sep in requirement or "/" in requirement + is_archive_file = requirement.lower().endswith(PACKAGE_ARCHIVE_EXTENSIONS) + is_path_like = is_explicit_path or is_archive_file - if ( - os.path.sep not in requirement - and "/" not in requirement - and requirement.lower().endswith(PACKAGE_ARCHIVE_EXTENSIONS) - ): + if not is_explicit_path and is_archive_file: specification = self._parse_path(requirement) if specification is not None: return specification @@ -92,11 +91,10 @@ def parse(self, requirement: str) -> DependencySpec: extras = [e.strip() for e in extras_m.group(1).split(",")] requirement, _ = requirement.split("[") - specification = ( - self._parse_url(requirement) - or self._parse_path(requirement) - or self._parse_simple(requirement) - ) + specification = self._parse_url(requirement) + if specification is None and is_path_like: + specification = self._parse_path(requirement) + specification = specification or self._parse_simple(requirement) if specification: if extras: