Merge pull request #44 from enpaul/enp/resolver

Fix compatibility check failures in the core resolver
This commit is contained in:
Ethan Paul 2021-02-15 22:36:08 -05:00 committed by GitHub
commit 333bbe665f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 75 additions and 74 deletions

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "tox-poetry-installer" name = "tox-poetry-installer"
version = "0.6.3" version = "0.6.4"
license = "MIT" license = "MIT"
authors = ["Ethan Paul <24588726+enpaul@users.noreply.github.com>"] authors = ["Ethan Paul <24588726+enpaul@users.noreply.github.com>"]
description = "Tox plugin to install Tox environment dependencies using the Poetry backend and lockfile" description = "Tox plugin to install Tox environment dependencies using the Poetry backend and lockfile"
@ -22,6 +22,7 @@ classifiers = [
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
"Natural Language :: English", "Natural Language :: English",
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",

View File

@ -1,7 +1,7 @@
# pylint: disable=missing-docstring # pylint: disable=missing-docstring
__title__ = "tox-poetry-installer" __title__ = "tox-poetry-installer"
__summary__ = "Tox plugin to install Tox environment dependencies using the Poetry backend and lockfile" __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/" __url__ = "https://github.com/enpaul/tox-poetry-installer/"
__license__ = "MIT" __license__ = "MIT"
__authors__ = ["Ethan Paul <24588726+enpaul@users.noreply.github.com>"] __authors__ = ["Ethan Paul <24588726+enpaul@users.noreply.github.com>"]

View File

@ -5,11 +5,8 @@ in this module.
All constants should be type hinted. All constants should be type hinted.
""" """
import sys
from typing import Tuple from typing import Tuple
from poetry.core.semver.version import Version
from tox_poetry_installer import __about__ 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 # Prefix all reporter messages should include to indicate that they came from this module in the
# console output. # console output.
REPORTER_PREFIX: str = f"{__about__.__title__}:" 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,
)

View File

@ -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}" f"{constants.REPORTER_PREFIX} Loaded project pyproject.toml from {poetry.file}"
) )
virtualenv = utilities.convert_virtualenv(venv)
if not poetry.locker.is_fresh(): if not poetry.locker.is_fresh():
tox.reporter.warning( tox.reporter.warning(
f"The Poetry lock file is not up to date with the latest changes in {poetry.file}" 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: 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( tox.reporter.verbosity1(
f"{constants.REPORTER_PREFIX} Identified {len(dev_deps)} development dependencies to install to env" 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( env_deps = utilities.find_additional_deps(
packages, poetry, venv.envconfig.locked_deps packages, virtualenv, poetry, venv.envconfig.locked_deps
) )
tox.reporter.verbosity1( 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: if not venv.envconfig.skip_install and not venv.envconfig.config.skipsdist:
project_deps = utilities.find_project_deps( project_deps = utilities.find_project_deps(
packages, poetry, venv.envconfig.extras packages, virtualenv, poetry, venv.envconfig.extras
) )
tox.reporter.verbosity1( tox.reporter.verbosity1(
f"{constants.REPORTER_PREFIX} Identified {len(project_deps)} project dependencies to install to env" f"{constants.REPORTER_PREFIX} Identified {len(project_deps)} project dependencies to install to env"

View File

@ -3,7 +3,6 @@
# See the docstring in 'tox_poetry_installer._poetry' for more context. # See the docstring in 'tox_poetry_installer._poetry' for more context.
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
import typing import typing
from pathlib import Path
from typing import Sequence from typing import Sequence
from typing import Set 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.venv import VirtualEnv as ToxVirtualEnv
from tox_poetry_installer import constants from tox_poetry_installer import constants
from tox_poetry_installer import utilities
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from tox_poetry_installer import _poetry from tox_poetry_installer import _poetry
@ -33,7 +33,7 @@ def install(
) )
pip = _poetry.PipInstaller( pip = _poetry.PipInstaller(
env=_poetry.VirtualEnv(path=Path(venv.envconfig.envdir)), env=utilities.convert_virtualenv(venv),
io=_poetry.NullIO(), io=_poetry.NullIO(),
pool=poetry.pool, pool=poetry.pool,
) )
@ -49,5 +49,5 @@ def install(
installed.add(dependency) installed.add(dependency)
else: else:
tox.reporter.verbosity2( tox.reporter.verbosity2(
f"{constants.REPORTER_PREFIX} Already installed {dependency}, skipping" f"{constants.REPORTER_PREFIX} Skipping {dependency}, already installed"
) )

View File

@ -2,13 +2,15 @@
# Silence this one globally to support the internal function imports for the proxied poetry module. # 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. # See the docstring in 'tox_poetry_installer._poetry' for more context.
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
import sys
import typing import typing
from pathlib import Path
from typing import List from typing import List
from typing import Sequence from typing import Sequence
from typing import Set from typing import Set
from typing import Union
import tox import tox
from poetry.core.packages import Dependency as PoetryDependency
from poetry.core.packages import Package as PoetryPackage from poetry.core.packages import Package as PoetryPackage
from tox.action import Action as ToxAction from tox.action import Action as ToxAction
from tox.venv import VirtualEnv as ToxVirtualEnv from tox.venv import VirtualEnv as ToxVirtualEnv
@ -56,81 +58,79 @@ def check_preconditions(venv: ToxVirtualEnv, action: ToxAction) -> "_poetry.Poet
) from None ) 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( 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]: ) -> List[PoetryPackage]:
"""Using a pool of packages, identify all transient dependencies of a given package name """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 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 :param venv: Poetry virtual environment to use for package compatibility checks
dependencies of.
:param allow_missing: Sequence of package names to allow to be missing from the lockfile. Any :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 packages that are not found in the lockfile but their name appears in this
list will be silently skipped from installation. list will be silently skipped from installation.
:returns: List of packages that need to be installed for the requested dependency. :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 .. note:: The package corresponding to the dependency specified by the ``dep`` parameter will
in the list of returned packages. be included in the returned list of packages.
""" """
from tox_poetry_installer import _poetry from tox_poetry_installer import _poetry
transients: List[PoetryPackage] = [] 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): if venv.is_valid_for_marker(transient.marker):
searched.add(name) for requirement in packages[transient.name].requires:
if requirement.name not in searched:
if name in _poetry.Provider.UNSAFE_PACKAGES: _deps_of_dep(requirement)
tox.reporter.warning(
f"{constants.REPORTER_PREFIX} Installing package '{name}' using Poetry is not supported and will be skipped"
)
tox.reporter.verbosity2( tox.reporter.verbosity2(
f"{constants.REPORTER_PREFIX} Skip {name}: designated unsafe by Poetry" f"{constants.REPORTER_PREFIX} Including {transient} for installation"
)
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}'"
) )
transients.append(packages[transient.name])
else: 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( 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: try:
find_deps_of_deps(packages[dep_name].name) if isinstance(dep, str):
except KeyError: 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: if dep_name in _poetry.Provider.UNSAFE_PACKAGES:
tox.reporter.warning( tox.reporter.warning(
f"{constants.REPORTER_PREFIX} Installing package '{dep_name}' using Poetry is not supported and will be skipped" 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 [] return []
if any( if any(
@ -148,13 +148,17 @@ def identify_transients(
def find_project_deps( 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]: ) -> List[PoetryPackage]:
"""Find the root project dependencies """Find the root project dependencies
Recursively identify the dependencies of the root project package Recursively identify the dependencies of the root project package
:param packages: Mapping of all locked package names to their corresponding package object :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 poetry: Poetry object for the current project
:param extras: Sequence of extra names to include the dependencies of :param extras: Sequence of extra names to include the dependencies of
""" """
@ -180,20 +184,24 @@ def find_project_deps(
dependencies: List[PoetryPackage] = [] dependencies: List[PoetryPackage] = []
for dep in base_deps + extra_deps: for dep in base_deps + extra_deps:
dependencies += identify_transients( dependencies += identify_transients(
packages, dep.name.lower(), allow_missing=[poetry.package.name] dep.name.lower(), packages, venv, allow_missing=[poetry.package.name]
) )
return dependencies return dependencies
def find_additional_deps( 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]: ) -> List[PoetryPackage]:
"""Find additional dependencies """Find additional dependencies
Recursively identify the dependencies of an arbitrary list of package names 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 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 poetry: Poetry object for the current project
:param dep_names: Sequence of additional dependency names to recursively find the transient :param dep_names: Sequence of additional dependency names to recursively find the transient
dependencies for dependencies for
@ -201,24 +209,26 @@ def find_additional_deps(
deps: List[PoetryPackage] = [] deps: List[PoetryPackage] = []
for dep_name in dep_names: for dep_name in dep_names:
deps += identify_transients( deps += identify_transients(
packages, dep_name.lower(), allow_missing=[poetry.package.name] dep_name.lower(), packages, venv, allow_missing=[poetry.package.name]
) )
return deps return deps
def find_dev_deps( def find_dev_deps(
packages: PackageMap, poetry: "_poetry.Poetry" packages: PackageMap, venv: "_poetry.VirtualEnv", poetry: "_poetry.Poetry"
) -> List[PoetryPackage]: ) -> List[PoetryPackage]:
"""Find the dev dependencies """Find the dev dependencies
Recursively identify the Poetry dev dependencies Recursively identify the Poetry dev dependencies
:param packages: Mapping of all locked package names to their corresponding package object :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 poetry: Poetry object for the current project
""" """
return find_additional_deps( return find_additional_deps(
packages, packages,
venv,
poetry, poetry,
poetry.pyproject.data["tool"]["poetry"].get("dev-dependencies", {}).keys(), poetry.pyproject.data["tool"]["poetry"].get("dev-dependencies", {}).keys(),
) )