Skip to content

Commit

Permalink
More tests and docs
Browse files Browse the repository at this point in the history
Signed-off-by: Jean-Christophe Morin <[email protected]>
  • Loading branch information
JeanChristopheMorinPerso committed Jan 1, 2025
1 parent d39e1d7 commit 0d113a3
Show file tree
Hide file tree
Showing 11 changed files with 402 additions and 21 deletions.
5 changes: 5 additions & 0 deletions docs/source/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ modifying packages (both metadata and files), etc.

This page documents the hooks available to plugins and how to implement plugins.

List installed plugins
======================

To list all installed plugins, use the :option:`--list-plugins` command line argument.

Register a plugin
=================

Expand Down
6 changes: 6 additions & 0 deletions src/rez_pip/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ def _run(args: argparse.Namespace, pipArgs: typing.List[str], pipWorkArea: str)
itertools.chain(*rez_pip.plugins.getHook().groupPackages(packages=packages)) # type: ignore[arg-type]
)

# TODO: Verify that no packages are in two or more groups? It should theorically
# not be possible since plugins are called one after the other? But it could happen
# if a plugin forgets to pop items from the package list... The problem is that we
# can't know which plugin did what, so we could only say "something went wrong"
# and can't point to which plugin is at fault.

# Remove empty groups
_packageGroups = [group for group in _packageGroups if group]

Expand Down
39 changes: 20 additions & 19 deletions src/rez_pip/plugins/PySide6.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
"""PySide6 plugin.
For PySide6, we need a merge hook. If User says "install PySide6", we need to install PySide6, PySide6-Addons and PySide6-Essentials and shiboken6.
But PySide6, PySide6-Addons and PySide6-Essentials have to be merged. Additionally, shiboken6 needs to be broken down to remove PySide6 (core).
Because shiboken6 vendors PySide6-core... See https://inspector.pypi.io/project/shiboken6/6.6.1/packages/bb/72/e54f758e49e8da0dcd9490d006c41a814b0e56898ce4ca054d60cdba97bd/shiboken6-6.6.1-cp38-abi3-manylinux_2_28_x86_64.whl/.
On Windows, the PySide6/openssl folder has to be added to PATH, see https://inspector.pypi.io/project/pyside6/6.6.1/packages/ec/3d/1da1b88d74cb5318466156bac91f17ad4272c6c83a973e107ad9a9085009/PySide6-6.6.1-cp38-abi3-win_amd64.whl/PySide6/__init__.py#line.81.
So it's at least a 3 steps process:
1. Merge PySide6, PySide6-Essentials and PySide6-Addons into the same install. Unvendor shiboken.
2. Install shiboken + cleanup. The Cleanup could be its own hook here specific to shiboken.
"""

from __future__ import annotations

import os
import shutil
import typing
import logging

import packaging.utils
import packaging.version
Expand All @@ -30,15 +20,21 @@
if typing.TYPE_CHECKING:
from rez_pip.compat import importlib_metadata

# PySide6 was initiall a single package that had shiboken as a dependency.
# Starting from 6.3.0, the package was spit in 3, PySide6, PySide6-Essentials and
# PySide6-Addons.
_LOG = logging.getLogger(__name__)


@rez_pip.plugins.hookimpl
def prePipResolve(
packages: typing.List[str],
packages: typing.Tuple[str],
) -> None:
"""
PySide6 was initially a single package that had shiboken as a dependency.
Starting from 6.3.0, the package was spit in 3, PySide6, PySide6-Essentials and
PySide6-Addons.
So we need to intercept what the user installs and install all 3 packages together.
Not doing that would result in a broken install (eventually).
"""
pyside6Seen = False
variantsSeens = []

Expand All @@ -60,7 +56,7 @@ def prePipResolve(


@rez_pip.plugins.hookimpl
def postPipResolve(packages: typing.List[rez_pip.pip.PackageInfo]) -> None:
def postPipResolve(packages: typing.Tuple[rez_pip.pip.PackageInfo]) -> None:
"""
This hook is implemented out of extra caution. We really don't want PySide6-Addons
or PySide6-Essentials to be installed without PySide6.
Expand Down Expand Up @@ -91,7 +87,7 @@ def groupPackages(
packages: typing.List[rez_pip.pip.PackageInfo],
) -> typing.List[rez_pip.pip.PackageGroup[rez_pip.pip.PackageInfo]]:
data = []
for index, package in enumerate(packages[:]):
for package in packages[:]:
if packaging.utils.canonicalize_name(package.name) in [
"pyside6",
"pyside6-addons",
Expand All @@ -116,5 +112,10 @@ def cleanup(dist: "importlib_metadata.Distribution", path: str) -> None:
# PySide6 >=6.3, <6.6.2 were shipping some shiboken6 folders by mistake.
# Not removing these extra folders would stop python from being able to import
# the correct shiboken (that lives in a separate rez package).
shutil.rmtree(os.path.join(path, "python", "shiboken6"))
shutil.rmtree(os.path.join(path, "python", "shiboken6_generator"))
for innerpath in [
os.path.join(path, "python", "shiboken6"),
os.path.join(path, "python", "shiboken6_generator"),
]:
if os.path.exists(innerpath):
_LOG.debug("Removing %r", innerpath)
shutil.rmtree(innerpath)
3 changes: 3 additions & 0 deletions src/rez_pip/plugins/shiboken6.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"""shiboken6 plugin.
"""

from __future__ import annotations

import os
Expand Down
Empty file added tests/plugins/__init__.py
Empty file.
2 changes: 0 additions & 2 deletions tests/plugins/test_plugins.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import typing

import pluggy

import rez_pip.plugins
Expand Down
220 changes: 220 additions & 0 deletions tests/plugins/test_pyside6.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import sys
import typing
import pathlib

import pytest

import rez_pip.pip
import rez_pip.plugins
import rez_pip.exceptions

from . import utils


if sys.version_info[:2] < (3, 8):
import mock
else:
from unittest import mock


@pytest.fixture(scope="module")
def setupPluginManager():
yield utils.initializePluginManager("pyside6")


@pytest.mark.parametrize(
"packages",
[
("asd",),
("pyside6",),
("PysiDe6",),
("pyside6", "pyside6-addons"),
("pyside6", "pyside6-essentials"),
("pyside6", "pyside6-essentials", "pyside6-addons"),
("pyside6", "pyside6-addons", "asdasdad"),
],
)
def test_prePipResolve_noop(packages: typing.Tuple[str, ...]):
rez_pip.plugins.getHook().prePipResolve(packages=packages)


@pytest.mark.parametrize("packages", [("pyside6-addons",), ("PysiDe6_essentials",)])
def test_prePipResolve_raises(packages: typing.Tuple[str, ...]):
with pytest.raises(rez_pip.exceptions.RezPipError):
rez_pip.plugins.getHook().prePipResolve(packages=packages)


def fakePackage(name: str, **kwargs) -> mock.Mock:
value = mock.MagicMock()
value.configure_mock(name=name, **kwargs)
return value


@pytest.mark.parametrize(
"packages",
[
(fakePackage("asd"),),
(fakePackage("pyside6"),),
(fakePackage("PysiDe6"),),
(fakePackage("pyside6"), fakePackage("pyside6-addons")),
(fakePackage("pyside6"), fakePackage("pyside6-essentials")),
(
fakePackage("pyside6"),
fakePackage("pyside6-essentials"),
fakePackage("pyside6-addons"),
),
(
fakePackage("pyside6"),
fakePackage("pyside6-addons"),
fakePackage("asdasdad"),
),
],
)
def test_postPipResolve_noop(packages: typing.Tuple[str, ...]):
rez_pip.plugins.getHook().postPipResolve(packages=packages)


@pytest.mark.parametrize(
"packages",
[
(fakePackage("pyside6-addons"),),
(fakePackage("PysiDe6_essentials"),),
(fakePackage("PysiDe6_essentials"), fakePackage("asd")),
],
)
def test_postPipResolve_raises(packages: typing.Tuple[str, ...]):
with pytest.raises(rez_pip.exceptions.RezPipError):
rez_pip.plugins.getHook().postPipResolve(packages=packages)


@pytest.mark.parametrize(
"packages",
[[fakePackage("asd")]],
)
def test_groupPackages_noop(packages: typing.List[str]):
assert rez_pip.plugins.getHook().groupPackages(packages=packages) == [
[rez_pip.pip.PackageGroup(tuple())]
]


class FakePackageInfo:
def __init__(self, name: str, version: str):
self.name = name
self.version = version

def __eq__(self, value):
return self.name == value.name and self.version == value.version


@pytest.mark.parametrize(
"packages,expectedGroups",
[
[
[fakePackage("pyside6", version="1")],
[[rez_pip.pip.PackageGroup((FakePackageInfo("pyside6", "1"),))]],
],
[
[
fakePackage("pyside6", version="1"),
fakePackage("pyside6_addons", version="1"),
],
[
[
rez_pip.pip.PackageGroup(
(
FakePackageInfo("pyside6", "1"),
FakePackageInfo("pyside6_addons", "1"),
)
)
]
],
],
[
[
fakePackage("pyside6", version="1"),
fakePackage("pyside6_essentials", version="1"),
],
[
[
rez_pip.pip.PackageGroup(
(
FakePackageInfo("pyside6", "1"),
FakePackageInfo("pyside6_essentials", "1"),
)
)
]
],
],
[
[
fakePackage("pyside6", version="1"),
fakePackage("pyside6_essentials", version="1"),
fakePackage("pyside6-Addons", version="1"),
],
[
[
rez_pip.pip.PackageGroup(
(
FakePackageInfo("pyside6", "1"),
FakePackageInfo("pyside6_essentials", "1"),
FakePackageInfo("pyside6-Addons", "1"),
)
)
]
],
],
[
[
fakePackage("pyside6", version="1"),
fakePackage("asdasd", version=2),
fakePackage("pyside6_essentials", version="1"),
fakePackage("pyside6-Addons", version="1"),
],
[
[
rez_pip.pip.PackageGroup(
(
FakePackageInfo("pyside6", "1"),
FakePackageInfo("pyside6_essentials", "1"),
FakePackageInfo("pyside6-Addons", "1"),
)
)
]
],
],
],
)
def test_groupPackages(
packages: typing.List[str], expectedGroups: list[rez_pip.pip.PackageGroup]
):
data = rez_pip.plugins.getHook().groupPackages(packages=packages)
assert data == expectedGroups


@pytest.mark.parametrize("package", [fakePackage("asd")])
def test_cleanup_noop(package, tmp_path: pathlib.Path):
(tmp_path / "python" / "shiboken6").mkdir(parents=True)
(tmp_path / "python" / "shiboken6_generator").mkdir(parents=True)

rez_pip.plugins.getHook().cleanup(dist=package, path=tmp_path)

assert (tmp_path / "python" / "shiboken6").exists()
assert (tmp_path / "python" / "shiboken6_generator").exists()


@pytest.mark.parametrize(
"package",
[
fakePackage("pyside6"),
fakePackage("pyside6_essentials"),
fakePackage("PySiDe6-AddoNs"),
],
)
def test_cleanup(package, tmp_path: pathlib.Path):
(tmp_path / "python" / "shiboken6").mkdir(parents=True)
(tmp_path / "python" / "shiboken6_generator").mkdir(parents=True)

rez_pip.plugins.getHook().cleanup(dist=package, path=tmp_path)

assert not (tmp_path / "python" / "shiboken6").exists()
assert not (tmp_path / "python" / "shiboken6_generator").exists()
52 changes: 52 additions & 0 deletions tests/plugins/test_shiboken6.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import sys
import pathlib

import pytest

import rez_pip.pip
import rez_pip.plugins
import rez_pip.exceptions

from . import utils


if sys.version_info[:2] < (3, 8):
import mock
else:
from unittest import mock


@pytest.fixture(scope="module")
def setupPluginManager():
yield utils.initializePluginManager("shiboken6")


def fakePackage(name: str, **kwargs) -> mock.Mock:
value = mock.MagicMock()
value.configure_mock(name=name, **kwargs)
return value


@pytest.mark.parametrize("package", [fakePackage("asd")])
def test_cleanup_noop(package, tmp_path: pathlib.Path):
(tmp_path / "python" / "PySide6").mkdir(parents=True)

rez_pip.plugins.getHook().cleanup(dist=package, path=tmp_path)

assert (tmp_path / "python" / "PySide6").exists()


@pytest.mark.parametrize(
"package",
[
fakePackage("shiboken6"),
fakePackage("shiboken6_essentials"),
fakePackage("ShIbOkEn6-AddoNs"),
],
)
def test_cleanup(package, tmp_path: pathlib.Path):
(tmp_path / "python" / "PySide6").mkdir(parents=True)

rez_pip.plugins.getHook().cleanup(dist=package, path=tmp_path)

assert not (tmp_path / "python" / "shiboken6").exists()
Loading

0 comments on commit 0d113a3

Please sign in to comment.