Skip to content

Commit

Permalink
feat(api): pass local version label to build backend interface
Browse files Browse the repository at this point in the history
fix: `update`, `add` and `remove` shall not uninstall extra dependencies

With this change unrequested extras dependencies will also be kept when running `install` and are only removed when running `sync`!
Fix`build`  help regarding `--clean` (#9994)
  • Loading branch information
finswimmer authored and abn committed Jan 14, 2025
1 parent c5547ec commit f11dda9
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 6 deletions.
17 changes: 13 additions & 4 deletions src/poetry/core/masonry/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def prepare_metadata_for_build_wheel(
metadata_directory: str, config_settings: dict[str, Any] | None = None
) -> str:
poetry = Factory().create_poetry(Path().resolve(), with_groups=False)
builder = WheelBuilder(poetry)
builder = WheelBuilder(poetry, config_settings=config_settings)
metadata_path = Path(metadata_directory)
dist_info = builder.prepare_metadata(metadata_path)
return dist_info.name
Expand All @@ -56,7 +56,10 @@ def build_wheel(
metadata_path = None if metadata_directory is None else Path(metadata_directory)

return WheelBuilder.make_in(
poetry, Path(wheel_directory), metadata_directory=metadata_path
poetry,
Path(wheel_directory),
metadata_directory=metadata_path,
config_settings=config_settings,
)


Expand All @@ -66,7 +69,9 @@ def build_sdist(
"""Builds an sdist, places it in sdist_directory"""
poetry = Factory().create_poetry(Path().resolve(), with_groups=False)

path = SdistBuilder(poetry).build(Path(sdist_directory))
path = SdistBuilder(poetry, config_settings=config_settings).build(
Path(sdist_directory)
)

return path.name

Expand All @@ -80,7 +85,11 @@ def build_editable(
metadata_path = None if metadata_directory is None else Path(metadata_directory)

return WheelBuilder.make_in(
poetry, Path(wheel_directory), metadata_directory=metadata_path, editable=True
poetry,
Path(wheel_directory),
metadata_directory=metadata_path,
editable=True,
config_settings=config_settings,
)


Expand Down
19 changes: 18 additions & 1 deletion src/poetry/core/masonry/builders/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from functools import cached_property
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any


if TYPE_CHECKING:
Expand All @@ -27,15 +28,24 @@
class Builder:
format: str | None = None

def __init__(self, poetry: Poetry, executable: Path | None = None) -> None:
def __init__(
self,
poetry: Poetry,
executable: Path | None = None,
config_settings: dict[str, Any] | None = None,
) -> None:
from poetry.core.masonry.metadata import Metadata

if not poetry.is_package_mode:
raise RuntimeError(
"Building a package is not possible in non-package mode."
)

self._config_settings = config_settings or {}

self._poetry = poetry
self._apply_local_version_label()

self._package = poetry.package
self._path: Path = poetry.pyproject_path.parent
self._excluded_files: set[str] | None = None
Expand Down Expand Up @@ -72,6 +82,13 @@ def executable(self) -> Path:
def default_target_dir(self) -> Path:
return self._path / "dist"

def _apply_local_version_label(self) -> None:
"""Apply local version label from config settings to the poetry package version if present."""
if local_version_label := self._config_settings.get("local-version"):
self._poetry.package.version = self._poetry.package.version.replace(
local=local_version_label
)

def build(self, target_dir: Path | None) -> Path:
raise NotImplementedError

Expand Down
6 changes: 5 additions & 1 deletion src/poetry/core/masonry/builders/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from io import StringIO
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from typing import TextIO

import packaging.tags
Expand Down Expand Up @@ -61,8 +62,9 @@ def __init__(
executable: Path | None = None,
editable: bool = False,
metadata_directory: Path | None = None,
config_settings: dict[str, Any] | None = None,
) -> None:
super().__init__(poetry, executable=executable)
super().__init__(poetry, executable=executable, config_settings=config_settings)

self._records: list[tuple[str, str, int]] = []
self._original_path = self._path
Expand All @@ -80,13 +82,15 @@ def make_in(
executable: Path | None = None,
editable: bool = False,
metadata_directory: Path | None = None,
config_settings: dict[str, Any] | None = None,
) -> str:
wb = WheelBuilder(
poetry,
original=original,
executable=executable,
editable=editable,
metadata_directory=metadata_directory,
config_settings=config_settings,
)
wb.build(target_dir=directory)

Expand Down
18 changes: 18 additions & 0 deletions tests/masonry/builders/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,21 @@ def test_metadata_with_wildcard_dependency_constraint() -> None:

requires = metadata.get_all("Requires-Dist")
assert requires == ["google-api-python-client (>=1.8,!=2.0.*)"]


@pytest.mark.parametrize(
["local_version", "expected_version"],
[
("", "1.2.3"),
("some-label", "1.2.3+some-label"),
],
)
def test_builder_apply_local_version_label(
local_version: str, expected_version: str
) -> None:
builder = Builder(
Factory().create_poetry(Path(__file__).parent / "fixtures" / "complete"),
config_settings={"local-version": local_version},
)

assert builder._poetry.package.version.text == expected_version
120 changes: 120 additions & 0 deletions tests/masonry/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ def test_build_wheel(project: str) -> None:
)


def test_build_wheel_with_local_version() -> None:
with temporary_directory() as tmp_dir, cwd(fixtures / "complete"):
filename = api.build_wheel(
str(tmp_dir), config_settings={"local-version": "some-label"}
)
validate_wheel_contents(
name="my_package",
version="1.2.3+some-label",
path=tmp_dir / filename,
files=["entry_points.txt"],
)


def test_build_wheel_with_include() -> None:
with temporary_directory() as tmp_dir, cwd(fixtures / "with-include"):
filename = api.build_wheel(str(tmp_dir))
Expand Down Expand Up @@ -106,6 +119,19 @@ def test_build_sdist(project: str) -> None:
)


def test_build_sdist_with_local_version() -> None:
with temporary_directory() as tmp_dir, cwd(fixtures / "complete"):
filename = api.build_sdist(
str(tmp_dir), config_settings={"local-version": "some-label"}
)
validate_sdist_contents(
name="my-package",
version="1.2.3+some-label",
path=tmp_dir / filename,
files=["LICENSE"],
)


def test_build_sdist_with_include() -> None:
with temporary_directory() as tmp_dir, cwd(fixtures / "with-include"):
filename = api.build_sdist(str(tmp_dir))
Expand Down Expand Up @@ -208,6 +234,85 @@ def test_prepare_metadata_for_build_wheel(project: str) -> None:
assert f.read() == metadata


def test_prepare_metadata_for_build_wheel_with_local_version() -> None:
local_version = "some-label"
entry_points = """\
[console_scripts]
extra-script=my_package.extra:main
my-2nd-script=my_package:main2
my-script=my_package:main
[poetry.application.plugin]
my-command=my_package.plugins:MyApplicationPlugin
"""
wheel_data = f"""\
Wheel-Version: 1.0
Generator: poetry-core {__version__}
Root-Is-Purelib: true
Tag: py3-none-any
"""
metadata = f"""\
Metadata-Version: 2.3
Name: my-package
Version: 1.2.3+{local_version}
Summary: Some description.
License: MIT
Keywords: packaging,dependency,poetry
Author: Sébastien Eustace
Author-email: [email protected]
Maintainer: People Everywhere
Maintainer-email: [email protected]
Requires-Python: >=3.6,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: time
Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0)
Requires-Dist: cleo (>=0.6,<0.7)
Requires-Dist: pendulum (>=1.4,<2.0) ; (python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5") and (extra == "time")
Project-URL: Documentation, https://python-poetry.org/docs
Project-URL: Homepage, https://python-poetry.org/
Project-URL: Issue Tracker, https://github.com/python-poetry/poetry/issues
Project-URL: Repository, https://github.com/python-poetry/poetry
Description-Content-Type: text/x-rst
My Package
==========
"""
with temporary_directory() as tmp_dir, cwd(fixtures / "complete"):
dirname = api.prepare_metadata_for_build_wheel(
str(tmp_dir), config_settings={"local-version": local_version}
)

assert dirname == f"my_package-1.2.3+{local_version}.dist-info"

dist_info = Path(tmp_dir, dirname)

assert (dist_info / "entry_points.txt").exists()
assert (dist_info / "WHEEL").exists()
assert (dist_info / "METADATA").exists()

with (dist_info / "entry_points.txt").open(encoding="utf-8") as f:
assert f.read() == entry_points

with (dist_info / "WHEEL").open(encoding="utf-8") as f:
assert f.read() == wheel_data

with (dist_info / "METADATA").open(encoding="utf-8") as f:
assert f.read() == metadata


def test_prepare_metadata_for_build_wheel_with_bad_path_dev_dep_succeeds() -> None:
with temporary_directory() as tmp_dir, cwd(fixtures / "with_bad_path_dev_dep"):
api.prepare_metadata_for_build_wheel(str(tmp_dir))
Expand Down Expand Up @@ -244,6 +349,21 @@ def test_build_editable_wheel(project: str) -> None:
assert z.read("my_package.pth").decode().strip() == pkg_dir.as_posix()


def test_build_editable_wheel_with_local_version() -> None:
pkg_dir = fixtures / "complete"
with temporary_directory() as tmp_dir, cwd(pkg_dir):
filename = api.build_editable(
str(tmp_dir), config_settings={"local-version": "some-label"}
)
wheel_pth = Path(tmp_dir) / filename

validate_wheel_contents(
name="my_package",
version="1.2.3+some-label",
path=wheel_pth,
)


@pytest.mark.parametrize("project", ["complete", "complete_new", "complete_dynamic"])
def test_build_wheel_with_metadata_directory(project: str) -> None:
pkg_dir = fixtures / project
Expand Down

0 comments on commit f11dda9

Please sign in to comment.