mirror of
				https://github.com/enpaul/tox-poetry-installer.git
				synced 2025-11-04 07:46:06 +00:00 
			
		
		
		
	Merge pull request #44 from enpaul/enp/resolver
Fix compatibility check failures in the core resolver
This commit is contained in:
		@@ -1,6 +1,6 @@
 | 
			
		||||
[tool.poetry]
 | 
			
		||||
name = "tox-poetry-installer"
 | 
			
		||||
version = "0.6.3"
 | 
			
		||||
version = "0.6.4"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
authors = ["Ethan Paul <24588726+enpaul@users.noreply.github.com>"]
 | 
			
		||||
description = "Tox plugin to install Tox environment dependencies using the Poetry backend and lockfile"
 | 
			
		||||
@@ -22,6 +22,7 @@ classifiers = [
 | 
			
		||||
  "License :: OSI Approved :: MIT License",
 | 
			
		||||
  "Natural Language :: English",
 | 
			
		||||
  "Operating System :: OS Independent",
 | 
			
		||||
  "Programming Language :: Python :: 3",
 | 
			
		||||
  "Programming Language :: Python :: 3.6",
 | 
			
		||||
  "Programming Language :: Python :: 3.7",
 | 
			
		||||
  "Programming Language :: Python :: 3.8",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
# pylint: disable=missing-docstring
 | 
			
		||||
__title__ = "tox-poetry-installer"
 | 
			
		||||
__summary__ = "Tox plugin to install Tox environment dependencies using the Poetry backend and lockfile"
 | 
			
		||||
__version__ = "0.6.3"
 | 
			
		||||
__version__ = "0.6.4"
 | 
			
		||||
__url__ = "https://github.com/enpaul/tox-poetry-installer/"
 | 
			
		||||
__license__ = "MIT"
 | 
			
		||||
__authors__ = ["Ethan Paul <24588726+enpaul@users.noreply.github.com>"]
 | 
			
		||||
 
 | 
			
		||||
@@ -5,11 +5,8 @@ in this module.
 | 
			
		||||
 | 
			
		||||
All constants should be type hinted.
 | 
			
		||||
"""
 | 
			
		||||
import sys
 | 
			
		||||
from typing import Tuple
 | 
			
		||||
 | 
			
		||||
from poetry.core.semver.version import Version
 | 
			
		||||
 | 
			
		||||
from tox_poetry_installer import __about__
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -20,12 +17,3 @@ PEP508_VERSION_DELIMITERS: Tuple[str, ...] = ("~=", "==", "!=", ">", "<")
 | 
			
		||||
# Prefix all reporter messages should include to indicate that they came from this module in the
 | 
			
		||||
# console output.
 | 
			
		||||
REPORTER_PREFIX: str = f"{__about__.__title__}:"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Semver compatible version of the current python platform version. Used for checking
 | 
			
		||||
# whether a package is compatible with the current python system version
 | 
			
		||||
PLATFORM_VERSION: Version = Version(
 | 
			
		||||
    major=sys.version_info.major,
 | 
			
		||||
    minor=sys.version_info.minor,
 | 
			
		||||
    patch=sys.version_info.micro,
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -84,6 +84,8 @@ def tox_testenv_install_deps(venv: ToxVirtualEnv, action: ToxAction) -> Optional
 | 
			
		||||
        f"{constants.REPORTER_PREFIX} Loaded project pyproject.toml from {poetry.file}"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    virtualenv = utilities.convert_virtualenv(venv)
 | 
			
		||||
 | 
			
		||||
    if not poetry.locker.is_fresh():
 | 
			
		||||
        tox.reporter.warning(
 | 
			
		||||
            f"The Poetry lock file is not up to date with the latest changes in {poetry.file}"
 | 
			
		||||
@@ -101,7 +103,7 @@ def tox_testenv_install_deps(venv: ToxVirtualEnv, action: ToxAction) -> Optional
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if venv.envconfig.install_dev_deps:
 | 
			
		||||
            dev_deps = utilities.find_dev_deps(packages, poetry)
 | 
			
		||||
            dev_deps = utilities.find_dev_deps(packages, virtualenv, poetry)
 | 
			
		||||
            tox.reporter.verbosity1(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Identified {len(dev_deps)} development dependencies to install to env"
 | 
			
		||||
            )
 | 
			
		||||
@@ -112,7 +114,7 @@ def tox_testenv_install_deps(venv: ToxVirtualEnv, action: ToxAction) -> Optional
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        env_deps = utilities.find_additional_deps(
 | 
			
		||||
            packages, poetry, venv.envconfig.locked_deps
 | 
			
		||||
            packages, virtualenv, poetry, venv.envconfig.locked_deps
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        tox.reporter.verbosity1(
 | 
			
		||||
@@ -121,7 +123,7 @@ def tox_testenv_install_deps(venv: ToxVirtualEnv, action: ToxAction) -> Optional
 | 
			
		||||
 | 
			
		||||
        if not venv.envconfig.skip_install and not venv.envconfig.config.skipsdist:
 | 
			
		||||
            project_deps = utilities.find_project_deps(
 | 
			
		||||
                packages, poetry, venv.envconfig.extras
 | 
			
		||||
                packages, virtualenv, poetry, venv.envconfig.extras
 | 
			
		||||
            )
 | 
			
		||||
            tox.reporter.verbosity1(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Identified {len(project_deps)} project dependencies to install to env"
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@
 | 
			
		||||
# See the docstring in 'tox_poetry_installer._poetry' for more context.
 | 
			
		||||
# pylint: disable=import-outside-toplevel
 | 
			
		||||
import typing
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from typing import Sequence
 | 
			
		||||
from typing import Set
 | 
			
		||||
 | 
			
		||||
@@ -12,6 +11,7 @@ from poetry.core.packages import Package as PoetryPackage
 | 
			
		||||
from tox.venv import VirtualEnv as ToxVirtualEnv
 | 
			
		||||
 | 
			
		||||
from tox_poetry_installer import constants
 | 
			
		||||
from tox_poetry_installer import utilities
 | 
			
		||||
 | 
			
		||||
if typing.TYPE_CHECKING:
 | 
			
		||||
    from tox_poetry_installer import _poetry
 | 
			
		||||
@@ -33,7 +33,7 @@ def install(
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    pip = _poetry.PipInstaller(
 | 
			
		||||
        env=_poetry.VirtualEnv(path=Path(venv.envconfig.envdir)),
 | 
			
		||||
        env=utilities.convert_virtualenv(venv),
 | 
			
		||||
        io=_poetry.NullIO(),
 | 
			
		||||
        pool=poetry.pool,
 | 
			
		||||
    )
 | 
			
		||||
@@ -49,5 +49,5 @@ def install(
 | 
			
		||||
            installed.add(dependency)
 | 
			
		||||
        else:
 | 
			
		||||
            tox.reporter.verbosity2(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Already installed {dependency}, skipping"
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Skipping {dependency}, already installed"
 | 
			
		||||
            )
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,15 @@
 | 
			
		||||
# Silence this one globally to support the internal function imports for the proxied poetry module.
 | 
			
		||||
# See the docstring in 'tox_poetry_installer._poetry' for more context.
 | 
			
		||||
# pylint: disable=import-outside-toplevel
 | 
			
		||||
import sys
 | 
			
		||||
import typing
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from typing import List
 | 
			
		||||
from typing import Sequence
 | 
			
		||||
from typing import Set
 | 
			
		||||
from typing import Union
 | 
			
		||||
 | 
			
		||||
import tox
 | 
			
		||||
from poetry.core.packages import Dependency as PoetryDependency
 | 
			
		||||
from poetry.core.packages import Package as PoetryPackage
 | 
			
		||||
from tox.action import Action as ToxAction
 | 
			
		||||
from tox.venv import VirtualEnv as ToxVirtualEnv
 | 
			
		||||
@@ -56,81 +58,79 @@ def check_preconditions(venv: ToxVirtualEnv, action: ToxAction) -> "_poetry.Poet
 | 
			
		||||
        ) from None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def convert_virtualenv(venv: ToxVirtualEnv) -> "_poetry.VirtualEnv":
 | 
			
		||||
    """Convert a Tox venv to a Poetry venv
 | 
			
		||||
 | 
			
		||||
    :param venv: Tox ``VirtualEnv`` object representing a tox virtual environment
 | 
			
		||||
    :returns: Poetry ``VirtualEnv`` object representing a poetry virtual environment
 | 
			
		||||
    """
 | 
			
		||||
    from tox_poetry_installer import _poetry
 | 
			
		||||
 | 
			
		||||
    return _poetry.VirtualEnv(path=Path(venv.envconfig.envdir))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def identify_transients(
 | 
			
		||||
    packages: PackageMap, dep_name: str, allow_missing: Sequence[str] = ()
 | 
			
		||||
    dep: Union[PoetryDependency, str],
 | 
			
		||||
    packages: PackageMap,
 | 
			
		||||
    venv: "_poetry.VirtualEnv",
 | 
			
		||||
    allow_missing: Sequence[str] = (),
 | 
			
		||||
) -> List[PoetryPackage]:
 | 
			
		||||
    """Using a pool of packages, identify all transient dependencies of a given package name
 | 
			
		||||
 | 
			
		||||
    :param dep: Either the Poetry dependency or the dependency's bare package name to recursively
 | 
			
		||||
                identify the transient dependencies of
 | 
			
		||||
    :param packages: All packages from the lockfile to use for identifying dependency relationships.
 | 
			
		||||
    :param dep_name: Bare name (without version) of the dependency to fetch the transient
 | 
			
		||||
                            dependencies of.
 | 
			
		||||
    :param venv: Poetry virtual environment to use for package compatibility checks
 | 
			
		||||
    :param allow_missing: Sequence of package names to allow to be missing from the lockfile. Any
 | 
			
		||||
                          packages that are not found in the lockfile but their name appears in this
 | 
			
		||||
                          list will be silently skipped from installation.
 | 
			
		||||
    :returns: List of packages that need to be installed for the requested dependency.
 | 
			
		||||
 | 
			
		||||
    .. note:: The package corresponding to the dependency named by ``dep_name`` is included
 | 
			
		||||
              in the list of returned packages.
 | 
			
		||||
    .. note:: The package corresponding to the dependency specified by the ``dep`` parameter will
 | 
			
		||||
              be included in the returned list of packages.
 | 
			
		||||
    """
 | 
			
		||||
    from tox_poetry_installer import _poetry
 | 
			
		||||
 | 
			
		||||
    transients: List[PoetryPackage] = []
 | 
			
		||||
    searched: Set[str] = set()
 | 
			
		||||
 | 
			
		||||
    searched: Set[PoetryPackage] = set()
 | 
			
		||||
    def _deps_of_dep(transient: PoetryDependency):
 | 
			
		||||
        searched.add(transient.name)
 | 
			
		||||
 | 
			
		||||
    def find_deps_of_deps(name: str):
 | 
			
		||||
        searched.add(name)
 | 
			
		||||
 | 
			
		||||
        if name in _poetry.Provider.UNSAFE_PACKAGES:
 | 
			
		||||
            tox.reporter.warning(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Installing package '{name}' using Poetry is not supported and will be skipped"
 | 
			
		||||
            )
 | 
			
		||||
        if venv.is_valid_for_marker(transient.marker):
 | 
			
		||||
            for requirement in packages[transient.name].requires:
 | 
			
		||||
                if requirement.name not in searched:
 | 
			
		||||
                    _deps_of_dep(requirement)
 | 
			
		||||
            tox.reporter.verbosity2(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Skip {name}: designated unsafe by Poetry"
 | 
			
		||||
            )
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            package = packages[name]
 | 
			
		||||
        except KeyError as err:
 | 
			
		||||
            if name in allow_missing:
 | 
			
		||||
                tox.reporter.verbosity2(
 | 
			
		||||
                    f"{constants.REPORTER_PREFIX} Skip {name}: package is not in lockfile but designated as allowed to be missing"
 | 
			
		||||
                )
 | 
			
		||||
                return
 | 
			
		||||
            raise err
 | 
			
		||||
 | 
			
		||||
        if not package.python_constraint.allows(constants.PLATFORM_VERSION):
 | 
			
		||||
            tox.reporter.verbosity2(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Skip {package}: incompatible Python requirement '{package.python_constraint}' for current version '{constants.PLATFORM_VERSION}'"
 | 
			
		||||
            )
 | 
			
		||||
        elif package.platform is not None and package.platform != sys.platform:
 | 
			
		||||
            tox.reporter.verbosity2(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Skip {package}: incompatible platform requirement '{package.platform}' for current platform '{sys.platform}'"
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Including {transient} for installation"
 | 
			
		||||
            )
 | 
			
		||||
            transients.append(packages[transient.name])
 | 
			
		||||
        else:
 | 
			
		||||
            for index, dep in enumerate(package.requires):
 | 
			
		||||
                tox.reporter.verbosity2(
 | 
			
		||||
                    f"{constants.REPORTER_PREFIX} Processing {package} dependency {index + 1}/{len(package.requires)}: {dep.name}"
 | 
			
		||||
                )
 | 
			
		||||
                if dep.name not in searched:
 | 
			
		||||
                    find_deps_of_deps(dep.name)
 | 
			
		||||
                else:
 | 
			
		||||
                    tox.reporter.verbosity2(
 | 
			
		||||
                        f"{constants.REPORTER_PREFIX} Skip {package}: already included for installation"
 | 
			
		||||
                    )
 | 
			
		||||
            tox.reporter.verbosity2(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Including {package} for installation"
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Skipping {transient}: package requires {transient.marker}"
 | 
			
		||||
            )
 | 
			
		||||
            transients.append(package)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        find_deps_of_deps(packages[dep_name].name)
 | 
			
		||||
    except KeyError:
 | 
			
		||||
        if isinstance(dep, str):
 | 
			
		||||
            dep = packages[dep].to_dependency()
 | 
			
		||||
 | 
			
		||||
        _deps_of_dep(dep)
 | 
			
		||||
    except KeyError as err:
 | 
			
		||||
        dep_name = err.args[0]
 | 
			
		||||
 | 
			
		||||
        if dep_name in _poetry.Provider.UNSAFE_PACKAGES:
 | 
			
		||||
            tox.reporter.warning(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Installing package '{dep_name}' using Poetry is not supported and will be skipped"
 | 
			
		||||
            )
 | 
			
		||||
            tox.reporter.verbosity2(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Skipping {dep_name}: designated unsafe by Poetry"
 | 
			
		||||
            )
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
        if dep_name in allow_missing:
 | 
			
		||||
            tox.reporter.verbosity2(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Skipping {dep_name}: package is allowed to be unlocked"
 | 
			
		||||
            )
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
        if any(
 | 
			
		||||
@@ -148,13 +148,17 @@ def identify_transients(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def find_project_deps(
 | 
			
		||||
    packages: PackageMap, poetry: "_poetry.Poetry", extras: Sequence[str] = ()
 | 
			
		||||
    packages: PackageMap,
 | 
			
		||||
    venv: "_poetry.VirtualEnv",
 | 
			
		||||
    poetry: "_poetry.Poetry",
 | 
			
		||||
    extras: Sequence[str] = (),
 | 
			
		||||
) -> List[PoetryPackage]:
 | 
			
		||||
    """Find the root project dependencies
 | 
			
		||||
 | 
			
		||||
    Recursively identify the dependencies of the root project package
 | 
			
		||||
 | 
			
		||||
    :param packages: Mapping of all locked package names to their corresponding package object
 | 
			
		||||
    :param venv: Poetry virtual environment to use for package compatibility checks
 | 
			
		||||
    :param poetry: Poetry object for the current project
 | 
			
		||||
    :param extras: Sequence of extra names to include the dependencies of
 | 
			
		||||
    """
 | 
			
		||||
@@ -180,20 +184,24 @@ def find_project_deps(
 | 
			
		||||
    dependencies: List[PoetryPackage] = []
 | 
			
		||||
    for dep in base_deps + extra_deps:
 | 
			
		||||
        dependencies += identify_transients(
 | 
			
		||||
            packages, dep.name.lower(), allow_missing=[poetry.package.name]
 | 
			
		||||
            dep.name.lower(), packages, venv, allow_missing=[poetry.package.name]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return dependencies
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def find_additional_deps(
 | 
			
		||||
    packages: PackageMap, poetry: "_poetry.Poetry", dep_names: Sequence[str]
 | 
			
		||||
    packages: PackageMap,
 | 
			
		||||
    venv: "_poetry.VirtualEnv",
 | 
			
		||||
    poetry: "_poetry.Poetry",
 | 
			
		||||
    dep_names: Sequence[str],
 | 
			
		||||
) -> List[PoetryPackage]:
 | 
			
		||||
    """Find additional dependencies
 | 
			
		||||
 | 
			
		||||
    Recursively identify the dependencies of an arbitrary list of package names
 | 
			
		||||
 | 
			
		||||
    :param packages: Mapping of all locked package names to their corresponding package object
 | 
			
		||||
    :param venv: Poetry virtual environment to use for package compatibility checks
 | 
			
		||||
    :param poetry: Poetry object for the current project
 | 
			
		||||
    :param dep_names: Sequence of additional dependency names to recursively find the transient
 | 
			
		||||
                      dependencies for
 | 
			
		||||
@@ -201,24 +209,26 @@ def find_additional_deps(
 | 
			
		||||
    deps: List[PoetryPackage] = []
 | 
			
		||||
    for dep_name in dep_names:
 | 
			
		||||
        deps += identify_transients(
 | 
			
		||||
            packages, dep_name.lower(), allow_missing=[poetry.package.name]
 | 
			
		||||
            dep_name.lower(), packages, venv, allow_missing=[poetry.package.name]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return deps
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def find_dev_deps(
 | 
			
		||||
    packages: PackageMap, poetry: "_poetry.Poetry"
 | 
			
		||||
    packages: PackageMap, venv: "_poetry.VirtualEnv", poetry: "_poetry.Poetry"
 | 
			
		||||
) -> List[PoetryPackage]:
 | 
			
		||||
    """Find the dev dependencies
 | 
			
		||||
 | 
			
		||||
    Recursively identify the Poetry dev dependencies
 | 
			
		||||
 | 
			
		||||
    :param packages: Mapping of all locked package names to their corresponding package object
 | 
			
		||||
    :param venv: Poetry virtual environment to use for package compatibility checks
 | 
			
		||||
    :param poetry: Poetry object for the current project
 | 
			
		||||
    """
 | 
			
		||||
    return find_additional_deps(
 | 
			
		||||
        packages,
 | 
			
		||||
        venv,
 | 
			
		||||
        poetry,
 | 
			
		||||
        poetry.pyproject.data["tool"]["poetry"].get("dev-dependencies", {}).keys(),
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user