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 )
2021-01-22 23:48:15 +00:00
def find_transients (
packages : PackageMap , dependency_name : str , allow_missing : Sequence [ str ] = ( )
) - > Set [ PoetryPackage ] :
2020-11-12 02:26:59 +00:00
""" Using a poetry object identify all dependencies of a specific dependency
2021-01-22 23:48:15 +00:00
: param packages : All packages from the lockfile to use for identifying dependency relationships .
2020-11-12 02:26:59 +00:00
: param dependency_name : Bare name ( without version ) of the dependency to fetch the transient
dependencies of .
2021-01-22 23:48:15 +00:00
: 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 .
2020-11-12 02:26:59 +00:00
: 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 = { }
2021-01-22 23:48:15 +00:00
try :
package = packages [ name ]
except KeyError as err :
if name in allow_missing :
reporter . verbosity2 (
f " { constants . REPORTER_PREFIX } Skip { name } : package is not in lockfile but designated as allowed to be missing "
)
return dict ( )
raise err
2020-12-17 00:01:20 +00:00
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 """
2021-01-23 00:11:54 +00:00
# Skip running the plugin for the provisioning environment. The provisioned environment,
# for alternative Tox versions and/or the ``requires`` meta dependencies is specially
# handled by Tox and is out of scope for this plugin. Since one of the ways to install this
# plugin in the first place is via the Tox provisioning environment, it quickly becomes a
# chicken-and-egg problem.
if action . name == venv . envconfig . config . provision_tox_env :
raise exceptions . SkipEnvironment (
f " Skipping Tox provisioning env ' { action . name } ' "
)
2020-11-13 03:14:49 +00:00
# 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 :
2021-01-22 23:48:15 +00:00
dependencies + = find_transients (
packages , dep . name . lower ( ) , allow_missing = [ poetry . package . name ]
)
2020-12-04 21:05:04 +00:00
return dependencies