diff --git a/src/poetry/utils/dependency_specification.py b/src/poetry/utils/dependency_specification.py index 2c73a5006b5..16d415b4bf1 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( @@ -70,6 +71,14 @@ 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 not is_explicit_path and is_archive_file: + specification = self._parse_path(requirement) + if specification is not None: + return specification specification = self._parse_pep508(requirement) @@ -82,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: @@ -153,18 +161,19 @@ 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() + 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_absolute_path = path.is_absolute() and path.exists() + + if ( + 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(): - 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..41358c77acc 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,32 @@ 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, + } + + +def test_parse_bare_package_name_does_not_check_path( + mocker: MockerFixture, + artifact_cache: ArtifactCache, +) -> None: + parser = RequirementsParser(artifact_cache=artifact_cache) + parse_path = mocker.patch.object(parser, "_parse_path", wraps=parser._parse_path) + + assert parser.parse("demo") == {"name": "demo"} + parse_path.assert_not_called()