Skip to content

Commit

Permalink
support for file dependencies with subdirectories (#467)
Browse files Browse the repository at this point in the history
analogous to git and url dependencies
  • Loading branch information
radoering authored May 8, 2023
1 parent 448a6c4 commit 3ba73c3
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 3 deletions.
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

0 comments on commit 3ba73c3

Please sign in to comment.