mirror of
				https://github.com/enpaul/tox-poetry-installer.git
				synced 2025-11-04 07:46:06 +00:00 
			
		
		
		
	Update module content to conform to code quality requirements
Add missing exception Add missing docstrings Lint and blacken Fix broken handling of poetrys "unsafe" packages
This commit is contained in:
		@@ -1,17 +1,27 @@
 | 
				
			|||||||
from pathlib import Path
 | 
					"""Tox plugin for installing environments using Poetry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This plugin makes use of the ``tox_testenv_install_deps`` Tox plugin hook to replace the default
 | 
				
			||||||
 | 
					installation functionality to install dependencies from the Poetry lockfile for the project. It
 | 
				
			||||||
 | 
					does this by using ``poetry`` to read in the lockfile, identify necessary dependencies, and then
 | 
				
			||||||
 | 
					use Poetry's ``PipInstaller`` class to install those packages into the Tox environment.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
from typing import Dict, List
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					from typing import Dict
 | 
				
			||||||
 | 
					from typing import List
 | 
				
			||||||
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					from typing import Tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from poetry.factory import Factory
 | 
					from poetry.factory import Factory as PoetryFactory
 | 
				
			||||||
from poetry.factory import Poetry
 | 
					from poetry.factory import Poetry
 | 
				
			||||||
from poetry.packages import Package
 | 
					from poetry.installation.pip_installer import PipInstaller as PoetryPipInstaller
 | 
				
			||||||
from poetry.installation.pip_installer import PipInstaller
 | 
					from poetry.io.null_io import NullIO as PoetryNullIO
 | 
				
			||||||
from poetry.io.null_io import NullIO
 | 
					from poetry.packages import Package as PoetryPackage
 | 
				
			||||||
from poetry.utils.env import VirtualEnv
 | 
					from poetry.puzzle.provider import Provider as PoetryProvider
 | 
				
			||||||
 | 
					from poetry.utils.env import VirtualEnv as PoetryVirtualEnv
 | 
				
			||||||
 | 
					from tox import hookimpl
 | 
				
			||||||
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
 | 
				
			||||||
from tox import hookimpl
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__title__ = "tox-poetry-installer"
 | 
					__title__ = "tox-poetry-installer"
 | 
				
			||||||
@@ -22,22 +32,47 @@ __license__ = "MIT"
 | 
				
			|||||||
__authors__ = ["Ethan Paul <e@enp.one>"]
 | 
					__authors__ = ["Ethan Paul <e@enp.one>"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PEP440_VERSION_DELIMITERS: Tuple[str, ...] = ("~=", "==", "!=", ">", "<")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ToxPoetryInstallerException(Exception):
 | 
				
			||||||
 | 
					    """Error while installing locked dependencies to the test environment"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NoLockedDependencyError(ToxPoetryInstallerException):
 | 
				
			||||||
 | 
					    """Cannot install a package that is not in the lockfile"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _make_poetry(venv: ToxVirtualEnv) -> Poetry:
 | 
					def _make_poetry(venv: ToxVirtualEnv) -> Poetry:
 | 
				
			||||||
    return Factory().create_poetry(venv.envconfig.config.toxinidir)
 | 
					    """Helper to make a poetry object from a toxenv"""
 | 
				
			||||||
 | 
					    return PoetryFactory().create_poetry(venv.envconfig.config.toxinidir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _find_locked_dependencies(poetry: Poetry, dependency_name: str) -> List[Package]:
 | 
					def _find_locked_dependencies(
 | 
				
			||||||
    packages: Dict[str, Package] = {
 | 
					    poetry: Poetry, dependency_name: str
 | 
				
			||||||
 | 
					) -> List[PoetryPackage]:
 | 
				
			||||||
 | 
					    """Using a poetry object identify all dependencies of a specific dependency
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param poetry: Populated poetry object which can be used to build a populated locked
 | 
				
			||||||
 | 
					                   repository object.
 | 
				
			||||||
 | 
					    :param dependency_name: Bare name (without version) of the dependency to fetch the transient
 | 
				
			||||||
 | 
					                            dependencies of.
 | 
				
			||||||
 | 
					    :returns: List of packages that need to be installed for the requested dependency.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. note:: The package corresponding to the dependency named by ``dependency_name`` is included
 | 
				
			||||||
 | 
					              in the list of returned packages.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    packages: Dict[str, PoetryPackage] = {
 | 
				
			||||||
        package.name: package
 | 
					        package.name: package
 | 
				
			||||||
        for package in poetry.locker.locked_repository(True).packages
 | 
					        for package in poetry.locker.locked_repository(True).packages
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        top_level = packages[dependency_name]
 | 
					        top_level = packages[dependency_name]
 | 
				
			||||||
    except KeyError:
 | 
					 | 
				
			||||||
        raise
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def find_transients(name: str) -> List[Package]:
 | 
					        def find_transients(name: str) -> List[PoetryPackage]:
 | 
				
			||||||
 | 
					            if name in PoetryProvider.UNSAFE_PACKAGES:
 | 
				
			||||||
 | 
					                return []
 | 
				
			||||||
            transients = [packages[name]]
 | 
					            transients = [packages[name]]
 | 
				
			||||||
            for dep in packages[name].requires:
 | 
					            for dep in packages[name].requires:
 | 
				
			||||||
                transients += find_transients(dep.name)
 | 
					                transients += find_transients(dep.name)
 | 
				
			||||||
@@ -45,30 +80,57 @@ def _find_locked_dependencies(poetry: Poetry, dependency_name: str) -> List[Pack
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return find_transients(top_level.name)
 | 
					        return find_transients(top_level.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except KeyError:
 | 
				
			||||||
 | 
					        if any(delimiter in dependency_name for delimiter in PEP440_VERSION_DELIMITERS):
 | 
				
			||||||
 | 
					            message = "specifying a version in the tox environment definition is incompatible with installing from a lockfile"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            message = (
 | 
				
			||||||
 | 
					                "no version of the package was found in the current project's lockfile"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        raise NoLockedDependencyError(
 | 
				
			||||||
 | 
					            f"Cannot install requirement '{dependency_name}': {message}"
 | 
				
			||||||
 | 
					        ) from None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@hookimpl
 | 
					@hookimpl
 | 
				
			||||||
def tox_testenv_install_deps(venv: ToxVirtualEnv, action: ToxAction):
 | 
					def tox_testenv_install_deps(
 | 
				
			||||||
 | 
					    venv: ToxVirtualEnv, action: ToxAction
 | 
				
			||||||
 | 
					) -> Optional[List[PoetryPackage]]:
 | 
				
			||||||
 | 
					    """Install the dependencies for the current environment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Loads the local Poetry environment and the corresponding lockfile then pulls the dependencies
 | 
				
			||||||
 | 
					    specified by the Tox environment. Finally these dependencies are installed into the Tox
 | 
				
			||||||
 | 
					    environment using the Poetry ``PipInstaller`` backend.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param venv: Tox virtual environment object with configuration for the local Tox environment.
 | 
				
			||||||
 | 
					    :param action: Tox action object
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    logger = logging.getLogger(__name__)
 | 
					    logger = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if action.name == venv.envconfig.config.isolated_build_env:
 | 
					    if action.name == venv.envconfig.config.isolated_build_env:
 | 
				
			||||||
        logger.debug(f"Environment {action.name} is isolated build environment; skipping Poetry-based dependency installation")
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            f"Environment {action.name} is isolated build environment; skipping Poetry-based dependency installation"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    poetry = _make_poetry(venv)
 | 
					    poetry = _make_poetry(venv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    logger.debug(f"Loaded project pyproject.toml from {poetry.file}")
 | 
					    logger.debug(f"Loaded project pyproject.toml from {poetry.file}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dependencies = []
 | 
					    dependencies: List[PoetryPackage] = []
 | 
				
			||||||
    for env_dependency in venv.envconfig.deps:
 | 
					    for env_dependency in venv.envconfig.deps:
 | 
				
			||||||
        dependencies += _find_locked_dependencies(poetry, env_dependency.name)
 | 
					        dependencies += _find_locked_dependencies(poetry, env_dependency.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    logger.debug(f"Identified {len(dependencies)} dependencies for environment {action.name}")
 | 
					    logger.debug(
 | 
				
			||||||
 | 
					        f"Identified {len(dependencies)} dependencies for environment {action.name}"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    installer = PipInstaller(
 | 
					    installer = PoetryPipInstaller(
 | 
				
			||||||
        env=VirtualEnv(path=Path(venv.envconfig.envdir)),
 | 
					        env=PoetryVirtualEnv(path=Path(venv.envconfig.envdir)),
 | 
				
			||||||
        io=NullIO(),
 | 
					        io=PoetryNullIO(),
 | 
				
			||||||
        pool=poetry.pool
 | 
					        pool=poetry.pool,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for dependency in dependencies:
 | 
					    for dependency in dependencies:
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user