diff --git a/news/3190.bugfix.md b/news/3190.bugfix.md new file mode 100644 index 0000000000..0ad5fb87b6 --- /dev/null +++ b/news/3190.bugfix.md @@ -0,0 +1 @@ +Fix the `pre_install` and `post_install` signals receiving an exhausted generator, instead of a list of packages. diff --git a/src/pdm/cli/actions.py b/src/pdm/cli/actions.py index 99be844680..be167a6bfa 100644 --- a/src/pdm/cli/actions.py +++ b/src/pdm/cli/actions.py @@ -281,7 +281,7 @@ def do_sync( selection.validate() for group in selection: requirements.extend(project.get_dependencies(group)) - packages = resolve_from_lockfile(project, requirements, groups=list(selection)) + packages = list(resolve_from_lockfile(project, requirements, groups=list(selection))) if tracked_names and dry_run: packages = [p for p in packages if p.candidate.identify() in tracked_names] synchronizer = project.get_synchronizer()( diff --git a/tests/test_signals.py b/tests/test_signals.py index 2d63c47e58..9e2c63ff04 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -1,8 +1,12 @@ +from itertools import chain from unittest import mock import pytest from pdm import signals +from pdm.models.candidates import Candidate +from pdm.models.repositories import Package +from pdm.models.requirements import Requirement def test_post_init_signal(project_no_init, pdm): @@ -26,3 +30,82 @@ def test_post_lock_and_install_signals(project, pdm): signals.post_install.disconnect(post_install) for mocker in (pre_lock, post_lock, pre_install, post_install): mocker.assert_called_once() + + +@pytest.mark.usefixtures("working_set") +def test_lock_and_install_signals_injection_with_add(project, pdm): + pre_lock = signals.pre_lock.connect(mock.Mock(), weak=False) + post_lock = signals.post_lock.connect(mock.Mock(), weak=False) + pre_install = signals.pre_install.connect(mock.Mock(), weak=False) + post_install = signals.post_install.connect(mock.Mock(), weak=False) + pdm(["add", "requests"], obj=project, strict=True) + signals.pre_lock.disconnect(pre_lock) + signals.post_lock.disconnect(post_lock) + signals.pre_install.disconnect(pre_install) + signals.post_install.disconnect(post_install) + + assert isinstance(pre_lock.call_args.kwargs["requirements"], list) + assert all(isinstance(e, Requirement) for e in pre_lock.call_args.kwargs["requirements"]) + assert len(pre_lock.call_args.kwargs["requirements"]) == 1 + + assert isinstance(post_lock.call_args.kwargs["resolution"], dict) + assert all(isinstance(e, Candidate) for e in chain.from_iterable(post_lock.call_args.kwargs["resolution"].values())) + assert len(post_lock.call_args.kwargs["resolution"]) == 5 + + assert isinstance(pre_install.call_args.kwargs["packages"], list) + assert all(isinstance(e, Package) for e in pre_install.call_args.kwargs["packages"]) + assert len(pre_install.call_args.kwargs["packages"]) == 5 + + assert isinstance(post_install.call_args.kwargs["packages"], list) + assert all(isinstance(e, Package) for e in post_install.call_args.kwargs["packages"]) + assert len(post_install.call_args.kwargs["packages"]) == 5 + + +@pytest.mark.usefixtures("working_set") +def test_lock_and_install_signals_injection_with_install(project, pdm): + project.add_dependencies(["requests"]) + + pre_lock = signals.pre_lock.connect(mock.Mock(), weak=False) + post_lock = signals.post_lock.connect(mock.Mock(), weak=False) + pre_install = signals.pre_install.connect(mock.Mock(), weak=False) + post_install = signals.post_install.connect(mock.Mock(), weak=False) + pdm(["install"], obj=project, strict=True) + signals.pre_lock.disconnect(pre_lock) + signals.post_lock.disconnect(post_lock) + signals.pre_install.disconnect(pre_install) + signals.post_install.disconnect(post_install) + + assert isinstance(pre_lock.call_args.kwargs["requirements"], list) + assert all(isinstance(e, Requirement) for e in pre_lock.call_args.kwargs["requirements"]) + assert len(pre_lock.call_args.kwargs["requirements"]) == 1 + + assert isinstance(post_lock.call_args.kwargs["resolution"], dict) + assert all(isinstance(e, Candidate) for e in chain.from_iterable(post_lock.call_args.kwargs["resolution"].values())) + assert len(post_lock.call_args.kwargs["resolution"]) == 5 + + assert isinstance(pre_install.call_args.kwargs["packages"], list) + assert all(isinstance(e, Package) for e in pre_install.call_args.kwargs["packages"]) + assert len(pre_install.call_args.kwargs["packages"]) == 5 + + assert isinstance(post_install.call_args.kwargs["packages"], list) + assert all(isinstance(e, Package) for e in post_install.call_args.kwargs["packages"]) + assert len(post_install.call_args.kwargs["packages"]) == 5 + + +@pytest.mark.usefixtures("working_set") +def test_lock_signals_injection_with_update(project, pdm): + project.add_dependencies(["requests"]) + + pre_lock = signals.pre_lock.connect(mock.Mock(), weak=False) + post_lock = signals.post_lock.connect(mock.Mock(), weak=False) + pdm(["update"], obj=project, strict=True) + signals.pre_lock.disconnect(pre_lock) + signals.post_lock.disconnect(post_lock) + + assert isinstance(pre_lock.call_args.kwargs["requirements"], list) + assert all(isinstance(e, Requirement) for e in pre_lock.call_args.kwargs["requirements"]) + assert len(pre_lock.call_args.kwargs["requirements"]) == 1 + + assert isinstance(post_lock.call_args.kwargs["resolution"], dict) + assert all(isinstance(e, Candidate) for e in chain.from_iterable(post_lock.call_args.kwargs["resolution"].values())) + assert len(post_lock.call_args.kwargs["resolution"]) == 5