Skip to content

Commit

Permalink
feat: support choosing groups to lock (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
frostming authored Jul 10, 2024
1 parent 1c6e877 commit 3d35f10
Show file tree
Hide file tree
Showing 10 changed files with 1,072 additions and 6 deletions.
17 changes: 17 additions & 0 deletions doc/src/plugins/backend.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,20 @@ hatchling
build-backend = "hatchling.build"
[tool.hatch.metadata.hooks.build-locked]
Select groups to lock
~~~~~~~~~~~~~~~~~~~~~

By default, the default group and all optional groups will be locked, but you can specify the groups to lock by setting `locked-groups` in the configuration.

.. code-block:: toml
:caption: pyproject.toml
# for pdm-backend
[tool.pdm.build]
locked = true
locked-groups = ["default", "optional1"]
# for hatchling
[tool.hatch.metadata.hooks.build-locked]
locked-groups = ["default", "optional1"]
9 changes: 6 additions & 3 deletions src/pdm_build_locked/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,15 @@ def get_locked_group_name(group: str) -> str:
return group_name


def update_metadata_with_locked(metadata: MutableMapping[str, Any], root: Path) -> None: # pragma: no cover
def update_metadata_with_locked(
metadata: MutableMapping[str, Any], root: Path, groups: list[str] | None = None
) -> None: # pragma: no cover
"""Inplace update the metadata(pyproject.toml) with the locked dependencies.
Args:
metadata (dict[str, Any]): The metadata dictionary
root (Path): The path to the project root
groups (list[str], optional): The groups to lock. Defaults to default + all optional groups.
Raises:
UnsupportedRequirement
Expand All @@ -94,10 +97,10 @@ def update_metadata_with_locked(metadata: MutableMapping[str, Any], root: Path)
)
return

groups = ["default"]
optional_groups = list(metadata.get("optional-dependencies", {}))
locked_groups = lockfile_content.get("metadata", {}).get("groups", [])
groups.extend(optional_groups)
if groups is None:
groups = ["default", *optional_groups]
for group in groups:
locked_group = get_locked_group_name(group)
if locked_group in optional_groups:
Expand Down
4 changes: 3 additions & 1 deletion src/pdm_build_locked/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ def pdm_build_hook_enabled(self, context: Context) -> bool:

def pdm_build_initialize(self, context: Context) -> None:
static_fields = list(context.config.metadata)
update_metadata_with_locked(context.config.metadata, context.root)
update_metadata_with_locked(
context.config.metadata, context.root, context.config.build_config.get("locked-groups")
)
new_fields = set(context.config.metadata) - set(static_fields)
for field in new_fields:
if field in context.config.metadata.get("dynamic", []):
Expand Down
6 changes: 5 additions & 1 deletion src/pdm_build_locked/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ def handle(self, project: Project, options: argparse.Namespace) -> None:
if dev_dependencies := project.pyproject.settings.get("dev-dependencies"):
pdm_dev_dependencies = dev_dependencies.keys()

groups = {group for group in project.all_dependencies if group not in pdm_dev_dependencies}
groups = project.pyproject.settings.get("build", {}).get("locked-groups", None)
if groups is None:
groups = {group for group in project.all_dependencies if group not in pdm_dev_dependencies}
else:
groups = set(groups)

locked_groups = [get_locked_group_name(group) for group in groups]
if duplicate_groups := groups.intersection(locked_groups):
Expand Down
2 changes: 1 addition & 1 deletion src/pdm_build_locked/hatchling.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class BuildLockedMetadataHook(MetadataHookInterface):
PLUGIN_NAME = "build-locked"

def update(self, metadata: dict) -> None:
update_metadata_with_locked(metadata, Path(self.root))
update_metadata_with_locked(metadata, Path(self.root), self.config.get("locked-groups"))


@hookimpl
Expand Down
942 changes: 942 additions & 0 deletions tests/data/large-selected/pdm.lock

Large diffs are not rendered by default.

69 changes: 69 additions & 0 deletions tests/data/large-selected/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
########################################################################################################################
# Project Settings #
########################################################################################################################
[project]
name = "large"
dynamic = ["version"]
description = "test"
keywords = [
"python",
"module",
]
classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
]
dependencies = [
"cement==3.0.8",
"jinja2==3.1.2",
"dacite==1.8.1",
"matplotlib==3.7.2",
"platformdirs==3.8.1",
"packaging==23.1",
"requests==2.31.0",
"gitpython==3.1.32",
"matplotlib==3.7.1"
]
requires-python = ">=3.8"

[project.optional-dependencies]
cow = [
"pycowsay"
]
extras = [
"typing-extensions"
]

[build-system]
requires = ["pdm-backend>=2.0.7"]
build-backend = "pdm.backend"


########################################################################################################################
# Tools Settings #
########################################################################################################################

####################
# PDM #
####################
[tool.pdm.version]
source = "scm"

[tool.pdm.build]
package-dir = "src"
locked = true
locked-groups = ["default"]

[tool.pdm.dev-dependencies]
dev = [
# format
"black>=23.7.0",
"isort>=5.12.0",
# lint
"mypy>=1.4.1",
]
Empty file.
12 changes: 12 additions & 0 deletions tests/unit/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import pytest
from pkginfo import Wheel

from tests.utils import count_group_dependencies


def build_wheel(src_dir: Path, wheel_dir: Path) -> Wheel:
from build.__main__ import build_package
Expand All @@ -26,6 +28,16 @@ def test_pdm_backend(temp_dir: Path, data_base_path: Path, test_project: str) ->
}


@pytest.mark.usefixtures("assert_pyproject_unmodified")
@pytest.mark.parametrize("test_project", ["large-selected"])
def test_pdm_backend_with_selected_groups(temp_dir: Path, data_base_path: Path, test_project: str) -> None:
project = data_base_path / test_project
wheel = build_wheel(project, temp_dir)
assert count_group_dependencies(wheel, "locked") == 26
assert count_group_dependencies(wheel, "extras-locked") == 0
assert count_group_dependencies(wheel, "cow-locked") == 0


@pytest.mark.usefixtures("assert_pyproject_unmodified")
@pytest.mark.parametrize("test_project", ["lock-disabled"])
def test_pdm_backend_disabled(temp_dir: Path, data_base_path: Path, test_project: str) -> None:
Expand Down
17 changes: 17 additions & 0 deletions tests/unit/test_build_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,23 @@ def test_build_locked_pyproject(pdm: PDMCallable, data_base_path: Path, temp_dir
assert count_group_dependencies(wheel, "cow-locked") == 1


@pytest.mark.usefixtures("assert_pyproject_unmodified")
@pytest.mark.parametrize("test_project", ["large-selected"])
def test_build_locked_pyproject_with_selected_groups(
pdm: PDMCallable, data_base_path: Path, temp_dir: Path, test_project: str
) -> None:
"""this project has a lockfile and the pyproject.toml setting tool.pdm.build.locked"""
project_path = data_base_path.joinpath(test_project).as_posix()
cmd = ["build", "--project", project_path, "--dest", temp_dir.as_posix()]
result = pdm(cmd)
assert result.exit_code == 0

wheel = wheel_from_tempdir(temp_dir)
assert count_group_dependencies(wheel, "locked") == 26
assert count_group_dependencies(wheel, "extras-locked") == 0
assert count_group_dependencies(wheel, "cow-locked") == 0


@pytest.mark.usefixtures("assert_pyproject_unmodified")
@pytest.mark.parametrize("test_project", ["invalid"])
def test_build_locked_invalid(pdm: PDMCallable, data_base_path: Path, temp_dir: Path, test_project: str) -> None:
Expand Down

0 comments on commit 3d35f10

Please sign in to comment.