diff --git a/tox_poetry_installer/constants.py b/tox_poetry_installer/constants.py index ee739f9..0fd0947 100644 --- a/tox_poetry_installer/constants.py +++ b/tox_poetry_installer/constants.py @@ -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, -) diff --git a/tox_poetry_installer/hooks.py b/tox_poetry_installer/hooks.py index 6ddc742..ef89994 100644 --- a/tox_poetry_installer/hooks.py +++ b/tox_poetry_installer/hooks.py @@ -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" diff --git a/tox_poetry_installer/installer.py b/tox_poetry_installer/installer.py index b45b910..28fb020 100644 --- a/tox_poetry_installer/installer.py +++ b/tox_poetry_installer/installer.py @@ -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" ) diff --git a/tox_poetry_installer/utilities.py b/tox_poetry_installer/utilities.py index 12952a2..43aaf57 100644 --- a/tox_poetry_installer/utilities.py +++ b/tox_poetry_installer/utilities.py @@ -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,8 +58,18 @@ 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""" + 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 @@ -75,62 +87,45 @@ def identify_transients( 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,7 +143,10 @@ 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 @@ -180,14 +178,17 @@ 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 @@ -201,14 +202,14 @@ 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 @@ -219,6 +220,7 @@ def find_dev_deps( """ return find_additional_deps( packages, + venv, poetry, poetry.pyproject.data["tool"]["poetry"].get("dev-dependencies", {}).keys(), )