Restructure config options to support tox's native change detection

Remove custom handling of deps option
Add locked_deps option
Stop modifying the envconfig.deps option at runtime
This commit is contained in:
Ethan Paul 2020-11-12 00:24:15 -05:00
parent b6415888d9
commit b32a212e82
No known key found for this signature in database
GPG Key ID: D0E2CBF1245E92BF
5 changed files with 18 additions and 78 deletions

View File

@ -17,10 +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 = f"[{__about__.__title__}]:"
# Suffix that indicates an env dependency should be treated as a locked dependency and thus be
# installed from the lockfile. Will be automatically stripped off of a dependency name during
# sorting so that the resulting string is just the valid package name. This becomes optional when
# the "require_locked_deps" option is true for an environment; in that case a bare dependency like
# 'foo' is treated the same as an explicitly locked dependency like 'foo@poetry'
MAGIC_SUFFIX_MARKER = "@poetry"

View File

@ -1,18 +1,8 @@
"""Definitions for typehints/containers used by the plugin"""
from typing import Dict
from typing import List
from typing import NamedTuple
from poetry.core.packages import Package as PoetryPackage
from tox.config import DepConfig as ToxDepConfig
# Map of package names to the package object
PackageMap = Dict[str, PoetryPackage]
class SortedEnvDeps(NamedTuple):
"""Container for the two types of environment dependencies"""
unlocked_deps: List[ToxDepConfig]
locked_deps: List[ToxDepConfig]

View File

@ -26,3 +26,7 @@ class LockedDepNotFoundError(ToxPoetryInstallerException):
class ExtraNotFoundError(ToxPoetryInstallerException):
"""Project package extra not defined in project's pyproject.toml"""
class LockedDepsRequiredError(ToxPoetryInstallerException):
"""Environment cannot specify unlocked dependencies when locked dependencies are required"""

View File

@ -43,6 +43,12 @@ def tox_addoption(parser: ToxParser):
help="Require all dependencies in the environment be installed using the Poetry lockfile",
)
parser.add_testenv_attribute(
name="locked_deps",
type="line-list",
help="List of locked dependencies to install to the environment using the Poetry lockfile",
)
@hookimpl
def tox_testenv_install_deps(venv: ToxVirtualEnv, action: ToxAction):
@ -87,6 +93,11 @@ def tox_testenv_install_deps(venv: ToxVirtualEnv, action: ToxAction):
for package in poetry.locker.locked_repository(True).packages
}
if venv.envconfig.require_locked_deps and venv.envconfig.deps:
raise exceptions.LockedDepsRequiredError(
f"Unlocked dependencies '{venv.envconfig.deps}' specified for environment '{venv.name}' which requires locked dependencies"
)
# Handle the installation of any locked env dependencies from the lockfile
_install_env_dependencies(venv, poetry, package_map)
@ -119,12 +130,11 @@ def _install_env_dependencies(
:param poetry: Poetry object the packages were sourced from
:param packages: Mapping of package names to the corresponding package object
"""
env_deps = utilities.sort_env_deps(venv)
dependencies: List[PoetryPackage] = []
for dep in env_deps.locked_deps:
for dep in venv.envconfig.locked_deps:
try:
dependencies += utilities.find_transients(packages, dep.name.lower())
dependencies += utilities.find_transients(packages, dep.lower())
except exceptions.ToxPoetryInstallerException as err:
venv.status = "lockfile installation failed"
reporter.error(f"{constants.REPORTER_PREFIX} {err}")
@ -148,14 +158,9 @@ def _install_env_dependencies(
dependencies = list(set(dev_dependencies + dependencies))
reporter.verbosity1(
f"{constants.REPORTER_PREFIX} identified {len(dependencies)} total dependencies from {len(env_deps.locked_deps)} locked env dependencies"
f"{constants.REPORTER_PREFIX} identified {len(dependencies)} total dependencies from {len(venv.envconfig.locked_deps)} locked env dependencies"
)
reporter.verbosity1(
f"{constants.REPORTER_PREFIX} updating env config with {len(env_deps.unlocked_deps)} unlocked env dependencies for installation using the default backend"
)
venv.envconfig.deps = env_deps.unlocked_deps
reporter.verbosity0(
f"{constants.REPORTER_PREFIX} ({venv.name}) installing {len(dependencies)} env dependencies from lockfile"
)

View File

@ -15,58 +15,6 @@ 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
from tox_poetry_installer.datatypes import SortedEnvDeps
def sort_env_deps(venv: ToxVirtualEnv) -> SortedEnvDeps:
"""Sorts the environment dependencies by lock status
Lock status determines whether a given environment dependency will be installed from the
lockfile using the Poetry backend, or whether this plugin will skip it and allow it to be
installed using the default pip-based backend (an unlocked dependency).
.. note:: A locked dependency must follow a required format. To avoid reinventing the wheel
(no pun intended) this module does not have any infrastructure for parsing PEP-508
version specifiers, and so requires locked dependencies to be specified with no
version (the installed version being taken from the lockfile). If a dependency is
specified as locked and its name is also a PEP-508 string then an error will be
raised.
"""
reporter.verbosity1(
f"{constants.REPORTER_PREFIX} sorting {len(venv.envconfig.deps)} env dependencies by lock requirement"
)
unlocked_deps = []
locked_deps = []
for dep in venv.envconfig.deps:
if venv.envconfig.require_locked_deps:
reporter.verbosity1(
f"{constants.REPORTER_PREFIX} lock required for env, treating '{dep.name}' as locked env dependency"
)
dep.name = dep.name.replace(constants.MAGIC_SUFFIX_MARKER, "")
locked_deps.append(dep)
else:
if dep.name.endswith(constants.MAGIC_SUFFIX_MARKER):
reporter.verbosity1(
f"{constants.REPORTER_PREFIX} specification includes marker '{constants.MAGIC_SUFFIX_MARKER}', treating '{dep.name}' as locked env dependency"
)
dep.name = dep.name.replace(constants.MAGIC_SUFFIX_MARKER, "")
locked_deps.append(dep)
else:
reporter.verbosity1(
f"{constants.REPORTER_PREFIX} specification does not include marker '{constants.MAGIC_SUFFIX_MARKER}', treating '{dep.name}' as unlocked env dependency"
)
unlocked_deps.append(dep)
reporter.verbosity1(
f"{constants.REPORTER_PREFIX} identified {len(locked_deps)} locked env dependencies: {[item.name for item in locked_deps]}"
)
reporter.verbosity1(
f"{constants.REPORTER_PREFIX} identified {len(unlocked_deps)} unlocked env dependencies: {[item.name for item in unlocked_deps]}"
)
return SortedEnvDeps(locked_deps=locked_deps, unlocked_deps=unlocked_deps)
def install_to_venv(