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

support for file dependencies with subdirectories #467

Merged
merged 1 commit into from
May 8, 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
5 changes: 5 additions & 0 deletions src/poetry/core/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ def create_dependency(
dependency = FileDependency(
name,
file_path,
directory=constraint.get("subdirectory", None),
groups=groups,
base=root_dir,
extras=constraint.get("extras", []),
Expand All @@ -306,12 +307,16 @@ def create_dependency(
dependency = FileDependency(
name,
path,
directory=constraint.get("subdirectory", None),
groups=groups,
optional=optional,
base=root_dir,
extras=constraint.get("extras", []),
)
else:
subdirectory = constraint.get("subdirectory", None)
if subdirectory:
path = path / subdirectory
dependency = DirectoryDependency(
name,
path,
Expand Down
8 changes: 8 additions & 0 deletions src/poetry/core/json/schemas/poetry-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,10 @@
"type": "string",
"description": "The path to the file."
},
"subdirectory": {
"type": "string",
"description": "The relative path to the directory where the package is located."
},
"python": {
"type": "string",
"description": "The python versions for which the dependency should be installed."
Expand Down Expand Up @@ -465,6 +469,10 @@
"type": "string",
"description": "The path to the dependency."
},
"subdirectory": {
"type": "string",
"description": "The relative path to the directory where the package is located."
},
"python": {
"type": "string",
"description": "The python versions for which the dependency should be installed."
Expand Down
13 changes: 11 additions & 2 deletions src/poetry/core/packages/dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,11 @@ def create_from_pep_508(
# handle RFC 8089 references
path = url_to_path(req.url)
dep = _make_file_or_dir_dep(
name=name, path=path, base=relative_to, extras=req.extras
name=name,
path=path,
base=relative_to,
subdirectory=link.subdirectory_fragment,
extras=req.extras,
)
else:
with suppress(ValueError):
Expand Down Expand Up @@ -502,6 +506,7 @@ def _make_file_or_dir_dep(
name: str,
path: Path,
base: Path | None = None,
subdirectory: str | None = None,
extras: list[str] | None = None,
) -> FileDependency | DirectoryDependency | None:
"""
Expand All @@ -517,8 +522,12 @@ def _make_file_or_dir_dep(
_path = Path(base) / path

if _path.is_file():
return FileDependency(name, path, base=base, extras=extras)
return FileDependency(
name, path, base=base, directory=subdirectory, extras=extras
)
elif _path.is_dir():
if subdirectory:
path = path / subdirectory
return DirectoryDependency(name, path, base=base, extras=extras)

return None
16 changes: 16 additions & 0 deletions src/poetry/core/packages/file_dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ def __init__(
self,
name: str,
path: Path,
*,
directory: str | None = None,
groups: Iterable[str] | None = None,
optional: bool = False,
base: Path | None = None,
Expand All @@ -31,9 +33,23 @@ def __init__(
groups=groups,
optional=optional,
base=base,
subdirectory=directory,
extras=extras,
)

@property
def directory(self) -> str | None:
return self.source_subdirectory

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

if self.directory:
requirement += f"#subdirectory={self.directory}"

return requirement

def _validate(self) -> str:
message = super()._validate()
if message:
Expand Down
1 change: 1 addition & 0 deletions src/poetry/core/packages/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ def to_dependency(self) -> Dependency:
dep = FileDependency(
self._name,
Path(self._source_url),
directory=self.source_subdirectory,
groups=list(self._dependency_groups.keys()),
optional=self.optional,
base=self.root_dir,
Expand Down
2 changes: 2 additions & 0 deletions src/poetry/core/packages/path_dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def __init__(
groups: Iterable[str] | None = None,
optional: bool = False,
base: Path | None = None,
subdirectory: str | None = None,
extras: Iterable[str] | None = None,
) -> None:
assert source_type in ("file", "directory")
Expand All @@ -47,6 +48,7 @@ def __init__(
allows_prereleases=True,
source_type=source_type,
source_url=self._full_path.as_posix(),
source_subdirectory=subdirectory,
extras=extras,
)
# cache validation result to avoid unnecessary file system access
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
My Package
==========
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"

readme = "README.rst"

homepage = "https://python-poetry.org"
repository = "https://github.com/python-poetry/poetry"
documentation = "https://python-poetry.org/docs"

keywords = ["packaging", "dependency", "poetry"]

classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]

# Requirements
[tool.poetry.dependencies]
python = "^3.6"

# Git dependency with subdirectory
pendulum = { git = "https://github.com/sdispater/pendulum.git", subdirectory = "sub", branch = "2.0" }

# File dependency with subdirectory
demo = [
{ path = "../distributions/demo-0.1.0-in-subdir.zip", subdirectory = "sub", platform = "linux" },
{ file = "../distributions/demo-0.1.0-in-subdir.zip", subdirectory = "sub", platform = "win32" }
]

# Dir dependency with subdirectory (same as path "../simple_project" without subdirectory)
simple-project = { path = "..", subdirectory = "simple_project" }

# Url dependency with subdirectory
foo = { url = "https://example.com/foo.zip", subdirectory = "sub" }
12 changes: 12 additions & 0 deletions tests/packages/test_directory_dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,18 @@ def test_directory_dependency_pep_508_local_relative() -> None:
_test_directory_dependency_pep_508("demo", path, requirement, expected)


def test_directory_dependency_pep_508_with_subdirectory() -> None:
path = (
Path(__file__).parent.parent
/ "fixtures"
/ "project_with_multi_constraints_dependency"
)
expected = f"demo @ {path.as_uri()}"

requirement = f"demo @ file://{path.parent.as_posix()}#subdirectory={path.name}"
_test_directory_dependency_pep_508("demo", path, requirement, expected)


def test_directory_dependency_pep_508_extras() -> None:
path = (
Path(__file__).parent.parent
Expand Down
10 changes: 9 additions & 1 deletion tests/packages/test_file_dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,15 @@ def test_file_dependency_pep_508_local_file_relative_path(
_test_file_dependency_pep_508(mocker, "demo", path, requirement, expected)


def test_absolute_file_dependency_to_pep_508_with_marker(mocker: MockerFixture) -> None:
def test_file_dependency_pep_508_with_subdirectory(mocker: MockerFixture) -> None:
path = DIST_PATH / "demo.zip"
expected = f"demo @ {path.as_uri()}#subdirectory=sub"

requirement = f"demo @ file://{path.as_posix()}#subdirectory=sub"
_test_file_dependency_pep_508(mocker, "demo", path, requirement, expected)


def test_to_pep_508_with_marker(mocker: MockerFixture) -> None:
wheel = "demo-0.1.0-py2.py3-none-any.whl"

abs_path = DIST_PATH / wheel
Expand Down
2 changes: 2 additions & 0 deletions tests/packages/test_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ def test_to_dependency_for_file() -> None:
"1.2.3",
source_type="file",
source_url=path.as_posix(),
source_subdirectory="qux",
features=["baz", "bar"],
)
dep = package.to_dependency()
Expand All @@ -380,6 +381,7 @@ def test_to_dependency_for_file() -> None:
assert dep.path == path
assert dep.source_type == "file"
assert dep.source_url == path.as_posix()
assert dep.source_subdirectory == "qux"


def test_to_dependency_for_url() -> None:
Expand Down
50 changes: 50 additions & 0 deletions tests/test_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
from _pytest.logging import LogCaptureFixture

from poetry.core.packages.dependency import Dependency
from poetry.core.packages.directory_dependency import DirectoryDependency
from poetry.core.packages.file_dependency import FileDependency
from poetry.core.packages.vcs_dependency import VCSDependency


Expand Down Expand Up @@ -151,6 +153,54 @@ def test_create_poetry() -> None:
]


def test_create_poetry_with_dependencies_with_subdirectory() -> None:
poetry = Factory().create_poetry(
fixtures_dir / "project_with_dependencies_with_subdirectory"
)
package = poetry.package
dependencies = {str(dep.name): dep for dep in package.requires}

# git dependency
pendulum = dependencies["pendulum"]
assert pendulum.is_vcs()
assert pendulum.pretty_constraint == "branch 2.0"
pendulum = cast("VCSDependency", pendulum)
assert pendulum.source == "https://github.com/sdispater/pendulum.git"
assert pendulum.directory == "sub"

# file dependency
demo = dependencies["demo"]
assert demo.is_file()
assert demo.pretty_constraint == "*"
demo = cast("FileDependency", demo)
assert demo.path == Path("../distributions/demo-0.1.0-in-subdir.zip")
assert demo.directory == "sub"
demo_dependencies = [dep for dep in package.requires if dep.name == "demo"]
assert len(demo_dependencies) == 2
assert demo_dependencies[0] == demo_dependencies[1]
assert {str(dep.marker) for dep in demo_dependencies} == {
'sys_platform == "win32"',
'sys_platform == "linux"',
}

# directory dependency
simple_project = dependencies["simple-project"]
assert simple_project.is_directory()
assert simple_project.pretty_constraint == "*"
simple_project = cast("DirectoryDependency", simple_project)
assert simple_project.path == Path("../simple_project")
with pytest.raises(AttributeError):
_ = simple_project.directory # type: ignore[attr-defined]

# url dependency
foo = dependencies["foo"]
assert foo.is_url()
assert foo.pretty_constraint == "*"
foo = cast("URLDependency", foo)
assert foo.url == "https://example.com/foo.zip"
assert foo.directory == "sub"


def test_create_poetry_with_packages_and_includes() -> None:
poetry = Factory().create_poetry(
fixtures_dir.parent / "masonry" / "builders" / "fixtures" / "with-include"
Expand Down