2020-11-12 02:26:59 +00:00
""" Helper utility functions, usually bridging Tox and Poetry functionality """
2020-12-05 00:25:40 +00:00
# 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
2020-11-26 19:32:22 +00:00
import sys
2020-12-05 00:25:40 +00:00
import typing
2020-11-12 02:26:59 +00:00
from pathlib import Path
2020-12-04 21:05:04 +00:00
from typing import List
2020-11-12 02:26:59 +00:00
from typing import Sequence
from typing import Set
from poetry . core . packages import Package as PoetryPackage
from tox import reporter
2020-11-13 03:14:49 +00:00
from tox . action import Action as ToxAction
2020-11-12 02:26:59 +00:00
from tox . venv import VirtualEnv as ToxVirtualEnv
from tox_poetry_installer import constants
from tox_poetry_installer import exceptions
from tox_poetry_installer . datatypes import PackageMap
2020-12-05 00:25:40 +00:00
if typing . TYPE_CHECKING :
from tox_poetry_installer import _poetry
2020-11-12 02:26:59 +00:00
def install_to_venv (
2020-12-05 00:25:40 +00:00
poetry : " _poetry.Poetry " , venv : ToxVirtualEnv , packages : Sequence [ PoetryPackage ]
2020-11-12 02:26:59 +00:00
) :
""" Install a bunch of packages to a virtualenv
: param poetry : Poetry object the packages were sourced from
: param venv : Tox virtual environment to install the packages to
: param packages : List of packages to install to the virtual environment
"""
2020-12-05 00:25:40 +00:00
from tox_poetry_installer import _poetry
2020-11-12 02:26:59 +00:00
reporter . verbosity1 (
f " { constants . REPORTER_PREFIX } Installing { len ( packages ) } packages to environment at { venv . envconfig . envdir } "
)
2020-12-05 00:25:40 +00:00
installer = _poetry . PipInstaller (
env = _poetry . VirtualEnv ( path = Path ( venv . envconfig . envdir ) ) ,
io = _poetry . NullIO ( ) ,
2020-11-12 02:26:59 +00:00
pool = poetry . pool ,
)
for dependency in packages :
2020-11-13 03:14:49 +00:00
reporter . verbosity1 ( f " { constants . REPORTER_PREFIX } Installing { dependency } " )
2020-11-12 02:26:59 +00:00
installer . install ( dependency )
def find_transients ( packages : PackageMap , dependency_name : str ) - > Set [ 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 .
"""
2020-12-05 00:25:40 +00:00
from tox_poetry_installer import _poetry
2020-11-12 02:26:59 +00:00
2020-12-16 23:47:27 +00:00
def find_deps_of_deps ( name : str , searched : Set [ str ] ) - > PackageMap :
searched . add ( name )
if name in _poetry . Provider . UNSAFE_PACKAGES :
reporter . warning (
2020-12-17 01:31:41 +00:00
f " { constants . REPORTER_PREFIX } Installing package ' { name } ' using Poetry is not supported and will be skipped "
2020-12-16 23:47:27 +00:00
)
reporter . verbosity2 (
2020-12-17 00:01:20 +00:00
f " { constants . REPORTER_PREFIX } Skip { name } : designated unsafe by Poetry "
2020-12-16 23:47:27 +00:00
)
2020-12-17 00:01:20 +00:00
return dict ( )
transients : PackageMap = { }
package = packages [ name ]
if not package . python_constraint . allows ( constants . PLATFORM_VERSION ) :
2020-12-16 23:47:27 +00:00
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 :
reporter . verbosity2 (
f " { constants . REPORTER_PREFIX } Skip { package } : incompatible platform requirement ' { package . platform } ' for current platform ' { sys . platform } ' "
)
else :
2020-12-17 00:14:03 +00:00
reporter . verbosity2 (
f " { constants . REPORTER_PREFIX } Including { package } for installation "
)
2020-12-16 23:47:27 +00:00
transients [ name ] = package
2020-12-17 00:14:03 +00:00
for index , dep in enumerate ( package . requires ) :
reporter . verbosity2 (
f " { constants . REPORTER_PREFIX } Processing dependency { index + 1 } / { len ( package . requires ) } for { package } : { dep . name } "
)
2020-12-16 23:47:27 +00:00
if dep . name not in searched :
transients . update ( find_deps_of_deps ( dep . name , searched ) )
2020-12-17 00:14:03 +00:00
else :
reporter . verbosity2 (
f " { constants . REPORTER_PREFIX } Package with name ' { dep . name } ' has already been processed, skipping "
)
2020-12-16 23:47:27 +00:00
return transients
searched : Set [ str ] = set ( )
2020-11-12 02:26:59 +00:00
2020-12-16 23:47:27 +00:00
try :
2020-11-26 19:32:22 +00:00
transients : PackageMap = find_deps_of_deps (
packages [ dependency_name ] . name , searched
)
2020-11-12 02:26:59 +00:00
except KeyError :
2020-12-17 01:31:41 +00:00
if dependency_name in _poetry . Provider . UNSAFE_PACKAGES :
reporter . warning (
f " { constants . REPORTER_PREFIX } Installing package ' { dependency_name } ' using Poetry is not supported and will be skipped "
)
return set ( )
2020-11-12 02:26:59 +00:00
if any (
delimiter in dependency_name
for delimiter in constants . PEP508_VERSION_DELIMITERS
) :
raise exceptions . LockedDepVersionConflictError (
f " Locked dependency ' { dependency_name } ' cannot include version specifier "
) from None
2020-12-17 01:31:41 +00:00
2020-11-12 02:26:59 +00:00
raise exceptions . LockedDepNotFoundError (
f " No version of locked dependency ' { dependency_name } ' found in the project lockfile "
) from None
2020-11-13 03:14:49 +00:00
2020-12-16 23:47:27 +00:00
return set ( transients . values ( ) )
2020-11-13 03:14:49 +00:00
2020-12-05 00:25:40 +00:00
def check_preconditions ( venv : ToxVirtualEnv , action : ToxAction ) - > " _poetry.Poetry " :
2020-11-13 03:14:49 +00:00
""" Check that the local project environment meets expectations """
# Skip running the plugin for the packaging environment. PEP-517 front ends can handle
# that better than we can, so let them do their thing. More to the point: if you're having
# problems in the packaging env that this plugin would solve, god help you.
if action . name == venv . envconfig . config . isolated_build_env :
raise exceptions . SkipEnvironment (
f " Skipping isolated packaging build env ' { action . name } ' "
)
2020-12-05 19:57:44 +00:00
from tox_poetry_installer import _poetry
2020-11-13 03:14:49 +00:00
try :
2020-12-05 00:25:40 +00:00
return _poetry . Factory ( ) . create_poetry ( venv . envconfig . config . toxinidir )
2020-11-13 03:14:49 +00:00
# Support running the plugin when the current tox project does not use Poetry for its
# environment/dependency management.
#
# ``RuntimeError`` is dangerous to blindly catch because it can be (and in Poetry's case,
# is) raised in many different places for different purposes.
except RuntimeError :
raise exceptions . SkipEnvironment (
" Project does not use Poetry for env management, skipping installation of locked dependencies "
) from None
2020-12-04 21:05:04 +00:00
def find_project_dependencies (
2020-12-05 00:25:40 +00:00
venv : ToxVirtualEnv , poetry : " _poetry.Poetry " , packages : PackageMap
2020-12-04 21:05:04 +00:00
) - > List [ PoetryPackage ] :
""" Install the dependencies of the project package
Install all primary dependencies of the project package .
: param venv : Tox virtual environment to install the packages to
: param poetry : Poetry object the packages were sourced from
: param packages : Mapping of package names to the corresponding package object
"""
base_dependencies : List [ PoetryPackage ] = [
packages [ item . name ]
for item in poetry . package . requires
if not item . is_optional ( )
]
extra_dependencies : List [ PoetryPackage ] = [ ]
for extra in venv . envconfig . extras :
2020-12-05 03:28:27 +00:00
reporter . verbosity1 (
f " { constants . REPORTER_PREFIX } Processing project extra ' { extra } ' "
)
2020-12-04 21:05:04 +00:00
try :
extra_dependencies + = [
packages [ item . name ] for item in poetry . package . extras [ extra ]
]
except KeyError :
raise exceptions . ExtraNotFoundError (
f " Environment ' { venv . name } ' specifies project extra ' { extra } ' which was not found in the lockfile "
) from None
dependencies : List [ PoetryPackage ] = [ ]
for dep in base_dependencies + extra_dependencies :
dependencies + = find_transients ( packages , dep . name . lower ( ) )
return dependencies