Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow non-existent path dependencies - reloaded #520

Merged
merged 4 commits into from
Jan 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 28 additions & 59 deletions src/poetry/core/packages/directory_dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

import functools

from pathlib import Path
from typing import TYPE_CHECKING
from typing import Iterable

from poetry.core.packages.dependency import Dependency
from poetry.core.packages.path_dependency import PathDependency
from poetry.core.packages.utils.utils import is_python_project
from poetry.core.packages.utils.utils import path_to_url
from poetry.core.pyproject.toml import PyProjectTOML


class DirectoryDependency(Dependency):
if TYPE_CHECKING:
from pathlib import Path


class DirectoryDependency(PathDependency):
def __init__(
self,
name: str,
Expand All @@ -22,74 +25,40 @@ def __init__(
develop: bool = False,
extras: Iterable[str] | None = None,
) -> None:
self._path = path
self._base = base or Path.cwd()
self._full_path = path

if not self._path.is_absolute():
try:
self._full_path = self._base.joinpath(self._path).resolve()
except FileNotFoundError:
raise ValueError(f"Directory {self._path} does not exist")

self._develop = develop

if not self._full_path.exists():
raise ValueError(f"Directory {self._path} does not exist")

if self._full_path.is_file():
raise ValueError(f"{self._path} is a file, expected a directory")

if not is_python_project(self._full_path):
raise ValueError(
f"Directory {self._full_path} does not seem to be a Python package"
)

super().__init__(
name,
"*",
path,
source_type="directory",
groups=groups,
optional=optional,
allows_prereleases=True,
source_type="directory",
source_url=self._full_path.as_posix(),
base=base,
extras=extras,
)
self._develop = develop

# cache this function to avoid multiple IO reads and parsing
self.supports_poetry = functools.lru_cache(maxsize=1)(self._supports_poetry)

@property
def path(self) -> Path:
return self._path

@property
def full_path(self) -> Path:
return self._full_path

@property
def base(self) -> Path:
return self._base

@property
def develop(self) -> bool:
return self._develop

def _supports_poetry(self) -> bool:
return PyProjectTOML(self._full_path / "pyproject.toml").is_poetry_project()

def is_directory(self) -> bool:
return True

@property
def base_pep_508_name(self) -> str:
requirement = self.pretty_name

if self.extras:
extras = ",".join(sorted(self.extras))
requirement += f"[{extras}]"
def _validate(self) -> str:
message = super()._validate()
if message:
return message

path = path_to_url(self.full_path)
requirement += f" @ {path}"
if self._full_path.is_file():
return (
f"{self._full_path} for {self.pretty_name} is a file,"
" expected a directory"
)
if not is_python_project(self._full_path):
return (
f"Directory {self._full_path} for {self.pretty_name} does not seem"
" to be a Python package"
)
return ""

return requirement
def _supports_poetry(self) -> bool:
return PyProjectTOML(self._full_path / "pyproject.toml").is_poetry_project()
70 changes: 20 additions & 50 deletions src/poetry/core/packages/file_dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
import io
import warnings

from pathlib import Path
from typing import TYPE_CHECKING
from typing import Iterable

from poetry.core.packages.dependency import Dependency
from poetry.core.packages.utils.utils import path_to_url
from poetry.core.packages.path_dependency import PathDependency


class FileDependency(Dependency):
if TYPE_CHECKING:
from pathlib import Path


class FileDependency(PathDependency):
def __init__(
self,
name: str,
Expand All @@ -21,47 +24,27 @@ def __init__(
base: Path | None = None,
extras: Iterable[str] | None = None,
) -> None:
self._path = path
self._base = base or Path.cwd()
self._full_path = path

if not self._path.is_absolute():
try:
self._full_path = self._base.joinpath(self._path).resolve()
except FileNotFoundError:
raise ValueError(f"Directory {self._path} does not exist")

if not self._full_path.exists():
raise ValueError(f"File {self._path} does not exist")

if self._full_path.is_dir():
raise ValueError(f"{self._path} is a directory, expected a file")

super().__init__(
name,
"*",
path,
source_type="file",
groups=groups,
optional=optional,
allows_prereleases=True,
source_type="file",
source_url=self._full_path.as_posix(),
base=base,
extras=extras,
)

@property
def base(self) -> Path:
return self._base

@property
def path(self) -> Path:
return self._path
def _validate(self) -> str:
message = super()._validate()
if message:
return message

@property
def full_path(self) -> Path:
return self._full_path

def is_file(self) -> bool:
return True
if self._full_path.is_dir():
return (
f"{self._full_path} for {self.pretty_name} is a directory,"
" expected a file"
)
return ""

def hash(self, hash_name: str = "sha256") -> str:
warnings.warn(
Expand All @@ -75,16 +58,3 @@ def hash(self, hash_name: str = "sha256") -> str:
h.update(content)

return h.hexdigest()

@property
def base_pep_508_name(self) -> str:
requirement = self.pretty_name

if self.extras:
extras = ",".join(sorted(self.extras))
requirement += f"[{extras}]"

path = path_to_url(self.full_path)
requirement += f" @ {path}"

return requirement
94 changes: 94 additions & 0 deletions src/poetry/core/packages/path_dependency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from __future__ import annotations

import logging

from abc import ABC
from abc import abstractmethod
from pathlib import Path
from typing import Iterable

from poetry.core.packages.dependency import Dependency
from poetry.core.packages.utils.utils import path_to_url


logger = logging.getLogger(__name__)


class PathDependency(Dependency, ABC):
@abstractmethod
def __init__(
self,
name: str,
path: Path,
*,
source_type: str,
neersighted marked this conversation as resolved.
Show resolved Hide resolved
groups: Iterable[str] | None = None,
optional: bool = False,
base: Path | None = None,
extras: Iterable[str] | None = None,
) -> None:
assert source_type in ("file", "directory")
self._path = path
self._base = base or Path.cwd()
self._full_path = path

if not self._path.is_absolute():
self._full_path = self._base.joinpath(self._path).resolve()

super().__init__(
name,
"*",
groups=groups,
optional=optional,
allows_prereleases=True,
source_type=source_type,
source_url=self._full_path.as_posix(),
extras=extras,
)
# cache validation result to avoid unnecessary file system access
self._validation_error = self._validate()
self.validate(raise_error=False)

@property
def path(self) -> Path:
return self._path

@property
def full_path(self) -> Path:
return self._full_path

@property
def base(self) -> Path:
return self._base

def is_file(self) -> bool:
return self._source_type == "file"

def is_directory(self) -> bool:
return self._source_type == "directory"

def validate(self, *, raise_error: bool) -> bool:
if not self._validation_error:
return True
if raise_error:
raise ValueError(self._validation_error)
logger.warning(self._validation_error)
return False

@property
def base_pep_508_name(self) -> str:
requirement = self.pretty_name

if self.extras:
extras = ",".join(sorted(self.extras))
requirement += f"[{extras}]"

path = path_to_url(self.full_path)
requirement += f" @ {path}"

return requirement

def _validate(self) -> str:
if not self._full_path.exists():
return f"Path {self._full_path} for {self.pretty_name} does not exist"
return ""
34 changes: 25 additions & 9 deletions tests/masonry/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from contextlib import contextmanager
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Iterator

import pytest
Expand All @@ -18,6 +19,10 @@
from tests.testutils import validate_wheel_contents


if TYPE_CHECKING:
from _pytest.logging import LogCaptureFixture


@contextmanager
def cwd(directory: str | Path) -> Iterator[None]:
prev = os.getcwd()
Expand Down Expand Up @@ -72,12 +77,15 @@ def test_build_wheel_with_bad_path_dev_dep_succeeds() -> None:
api.build_wheel(tmp_dir)


def test_build_wheel_with_bad_path_dep_fails() -> None:
with pytest.raises(ValueError) as err, temporary_directory() as tmp_dir, cwd(
def test_build_wheel_with_bad_path_dep_succeeds(caplog: LogCaptureFixture) -> None:
with temporary_directory() as tmp_dir, cwd(
os.path.join(fixtures, "with_bad_path_dep")
):
api.build_wheel(tmp_dir)
assert "does not exist" in str(err.value)
assert len(caplog.records) == 1
record = caplog.records[0]
assert record.levelname == "WARNING"
assert "does not exist" in record.message


@pytest.mark.skipif(
Expand Down Expand Up @@ -123,12 +131,15 @@ def test_build_sdist_with_bad_path_dev_dep_succeeds() -> None:
api.build_sdist(tmp_dir)


def test_build_sdist_with_bad_path_dep_fails() -> None:
with pytest.raises(ValueError) as err, temporary_directory() as tmp_dir, cwd(
def test_build_sdist_with_bad_path_dep_succeeds(caplog: LogCaptureFixture) -> None:
with temporary_directory() as tmp_dir, cwd(
os.path.join(fixtures, "with_bad_path_dep")
):
api.build_sdist(tmp_dir)
assert "does not exist" in str(err.value)
assert len(caplog.records) == 1
record = caplog.records[0]
assert record.levelname == "WARNING"
assert "does not exist" in record.message


def test_prepare_metadata_for_build_wheel() -> None:
Expand Down Expand Up @@ -209,12 +220,17 @@ def test_prepare_metadata_for_build_wheel_with_bad_path_dev_dep_succeeds() -> No
api.prepare_metadata_for_build_wheel(tmp_dir)


def test_prepare_metadata_for_build_wheel_with_bad_path_dep_succeeds() -> None:
with pytest.raises(ValueError) as err, temporary_directory() as tmp_dir, cwd(
def test_prepare_metadata_for_build_wheel_with_bad_path_dep_succeeds(
caplog: LogCaptureFixture,
) -> None:
with temporary_directory() as tmp_dir, cwd(
os.path.join(fixtures, "with_bad_path_dep")
):
api.prepare_metadata_for_build_wheel(tmp_dir)
assert "does not exist" in str(err.value)
assert len(caplog.records) == 1
record = caplog.records[0]
assert record.levelname == "WARNING"
assert "does not exist" in record.message


def test_build_editable_wheel() -> None:
Expand Down
Loading