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-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
2021-02-10 01:38:53 +00:00
import tox
2020-11-12 02:26:59 +00:00
from poetry . core . packages import Package as PoetryPackage
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
2021-02-10 04:53:56 +00:00
def check_preconditions ( venv : ToxVirtualEnv , action : ToxAction ) - > " _poetry.Poetry " :
""" Check that the local project environment meets expectations """
# 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 } ' "
)
# 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 } ' "
)
from tox_poetry_installer import _poetry
try :
return _poetry . Factory ( ) . create_poetry ( venv . envconfig . config . toxinidir )
# 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
2021-02-10 04:25:46 +00:00
def identify_transients (
2021-02-10 04:53:56 +00:00
packages : PackageMap , dep_name : str , allow_missing : Sequence [ str ] = ( )
2021-02-10 04:25:46 +00:00
) - > List [ PoetryPackage ] :
2021-02-10 04:53:56 +00:00
""" Using a pool of packages, identify all transient dependencies of a given package name
2020-11-12 02:26:59 +00:00
2021-01-22 23:48:15 +00:00
: param packages : All packages from the lockfile to use for identifying dependency relationships .
2021-02-10 04:53:56 +00:00
: param dep_name : Bare name ( without version ) of the dependency to fetch the transient
2020-11-12 02:26:59 +00:00
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 .
2021-02-10 04:53:56 +00:00
. . note : : The package corresponding to the dependency named by ` ` dep_name ` ` is included
2020-11-12 02:26:59 +00:00
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
2021-02-10 04:25:46 +00:00
transients : List [ PoetryPackage ] = [ ]
searched : Set [ PoetryPackage ] = set ( )
def find_deps_of_deps ( name : str ) :
2020-12-16 23:47:27 +00:00
searched . add ( name )
if name in _poetry . Provider . UNSAFE_PACKAGES :
2021-02-10 01:38:53 +00:00
tox . 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
)
2021-02-10 01:38:53 +00:00
tox . 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
)
2021-02-10 04:25:46 +00:00
return
2020-12-17 00:01:20 +00:00
2021-01-22 23:48:15 +00:00
try :
package = packages [ name ]
except KeyError as err :
if name in allow_missing :
2021-02-10 01:38:53 +00:00
tox . reporter . verbosity2 (
2021-01-22 23:48:15 +00:00
f " { constants . REPORTER_PREFIX } Skip { name } : package is not in lockfile but designated as allowed to be missing "
)
2021-02-10 04:25:46 +00:00
return
2021-01-22 23:48:15 +00:00
raise err
2020-12-17 00:01:20 +00:00
if not package . python_constraint . allows ( constants . PLATFORM_VERSION ) :
2021-02-10 01:38:53 +00:00
tox . reporter . verbosity2 (
2020-12-16 23:47:27 +00:00
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 :
2021-02-10 01:38:53 +00:00
tox . reporter . verbosity2 (
2020-12-16 23:47:27 +00:00
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
for index , dep in enumerate ( package . requires ) :
2021-02-10 01:38:53 +00:00
tox . reporter . verbosity2 (
2021-02-10 05:06:33 +00:00
f " { constants . REPORTER_PREFIX } Processing { package } dependency { index + 1 } / { len ( package . requires ) } : { dep . name } "
2020-12-17 00:14:03 +00:00
)
2020-12-16 23:47:27 +00:00
if dep . name not in searched :
2021-02-10 04:25:46 +00:00
find_deps_of_deps ( dep . name )
2020-12-17 00:14:03 +00:00
else :
2021-02-10 01:38:53 +00:00
tox . reporter . verbosity2 (
2021-02-10 05:06:33 +00:00
f " { constants . REPORTER_PREFIX } Skip { package } : already included for installation "
2020-12-17 00:14:03 +00:00
)
2021-02-10 04:25:46 +00:00
tox . reporter . verbosity2 (
f " { constants . REPORTER_PREFIX } Including { package } for installation "
)
transients . append ( package )
2020-11-12 02:26:59 +00:00
2020-12-16 23:47:27 +00:00
try :
2021-02-10 04:53:56 +00:00
find_deps_of_deps ( packages [ dep_name ] . name )
2020-11-12 02:26:59 +00:00
except KeyError :
2021-02-10 04:53:56 +00:00
if dep_name in _poetry . Provider . UNSAFE_PACKAGES :
2021-02-10 01:38:53 +00:00
tox . reporter . warning (
2021-02-10 04:53:56 +00:00
f " { constants . REPORTER_PREFIX } Installing package ' { dep_name } ' using Poetry is not supported and will be skipped "
2020-12-17 01:31:41 +00:00
)
2021-02-10 04:25:46 +00:00
return [ ]
2020-12-17 01:31:41 +00:00
2020-11-12 02:26:59 +00:00
if any (
2021-02-10 04:53:56 +00:00
delimiter in dep_name for delimiter in constants . PEP508_VERSION_DELIMITERS
2020-11-12 02:26:59 +00:00
) :
raise exceptions . LockedDepVersionConflictError (
2021-02-10 04:53:56 +00:00
f " Locked dependency ' { dep_name } ' cannot include version specifier "
2020-11-12 02:26:59 +00:00
) from None
2020-12-17 01:31:41 +00:00
2020-11-12 02:26:59 +00:00
raise exceptions . LockedDepNotFoundError (
2021-02-10 04:53:56 +00:00
f " No version of locked dependency ' { dep_name } ' found in the project lockfile "
2020-11-12 02:26:59 +00:00
) from None
2020-11-13 03:14:49 +00:00
2021-02-10 04:25:46 +00:00
return transients
2020-12-16 23:47:27 +00:00
2020-11-13 03:14:49 +00:00
2021-02-10 04:53:56 +00:00
def find_project_deps (
packages : PackageMap , poetry : " _poetry.Poetry " , extras : Sequence [ str ] = ( )
2020-12-04 21:05:04 +00:00
) - > List [ PoetryPackage ] :
2021-02-10 04:53:56 +00:00
""" Find the root project dependencies
2020-12-04 21:05:04 +00:00
2021-02-10 04:53:56 +00:00
Recursively identify the dependencies of the root project package
2020-12-04 21:05:04 +00:00
2021-02-10 04:53:56 +00:00
: param packages : Mapping of all locked package names to their corresponding package object
: param poetry : Poetry object for the current project
: param extras : Sequence of extra names to include the dependencies of
2020-12-04 21:05:04 +00:00
"""
2021-02-10 04:53:56 +00:00
base_deps : List [ PoetryPackage ] = [
2020-12-04 21:05:04 +00:00
packages [ item . name ]
for item in poetry . package . requires
if not item . is_optional ( )
]
2021-02-10 04:53:56 +00:00
extra_deps : List [ PoetryPackage ] = [ ]
for extra in extras :
2021-02-10 01:38:53 +00:00
tox . reporter . verbosity1 (
2020-12-05 03:28:27 +00:00
f " { constants . REPORTER_PREFIX } Processing project extra ' { extra } ' "
)
2020-12-04 21:05:04 +00:00
try :
2021-02-10 04:53:56 +00:00
extra_deps + = [ packages [ item . name ] for item in poetry . package . extras [ extra ] ]
2020-12-04 21:05:04 +00:00
except KeyError :
raise exceptions . ExtraNotFoundError (
2021-02-10 04:53:56 +00:00
f " Environment specifies project extra ' { extra } ' which was not found in the lockfile "
2020-12-04 21:05:04 +00:00
) from None
dependencies : List [ PoetryPackage ] = [ ]
2021-02-10 04:53:56 +00:00
for dep in base_deps + extra_deps :
2021-02-10 04:25:46 +00:00
dependencies + = identify_transients (
2021-01-22 23:48:15 +00:00
packages , dep . name . lower ( ) , allow_missing = [ poetry . package . name ]
)
2020-12-04 21:05:04 +00:00
return dependencies
2021-02-10 04:25:46 +00:00
2021-02-10 04:53:56 +00:00
def find_additional_deps (
packages : PackageMap , poetry : " _poetry.Poetry " , dep_names : Sequence [ str ]
2021-02-10 04:25:46 +00:00
) - > List [ PoetryPackage ] :
2021-02-10 04:53:56 +00:00
""" Find additional dependencies
2021-02-10 04:25:46 +00:00
2021-02-10 04:53:56 +00:00
Recursively identify the dependencies of an arbitrary list of package names
2021-02-10 04:25:46 +00:00
2021-02-10 04:53:56 +00:00
: param packages : Mapping of all locked package names to their corresponding package object
: param poetry : Poetry object for the current project
: param dep_names : Sequence of additional dependency names to recursively find the transient
dependencies for
2021-02-10 04:25:46 +00:00
"""
2021-02-10 04:53:56 +00:00
deps : List [ PoetryPackage ] = [ ]
for dep_name in dep_names :
deps + = identify_transients (
packages , dep_name . lower ( ) , allow_missing = [ poetry . package . name ]
2021-02-10 04:25:46 +00:00
)
2021-02-10 04:53:56 +00:00
return deps
2021-02-10 04:25:46 +00:00
2021-02-10 04:53:56 +00:00
def find_dev_deps (
packages : PackageMap , poetry : " _poetry.Poetry "
2021-02-10 04:25:46 +00:00
) - > List [ PoetryPackage ] :
2021-02-10 04:53:56 +00:00
""" Find the dev dependencies
2021-02-10 04:25:46 +00:00
2021-02-10 04:53:56 +00:00
Recursively identify the Poetry dev dependencies
2021-02-10 04:25:46 +00:00
2021-02-10 04:53:56 +00:00
: param packages : Mapping of all locked package names to their corresponding package object
: param poetry : Poetry object for the current project
2021-02-10 04:25:46 +00:00
"""
2021-02-10 04:53:56 +00:00
return find_additional_deps (
packages ,
poetry ,
poetry . pyproject . data [ " tool " ] [ " poetry " ] . get ( " dev-dependencies " , { } ) . keys ( ) ,
)