"""Command-line interface."""
from pathlib import Path
from typing import List
import click
import pluggy
from tomlkit import dumps
from . import hookspecs, lib
from .core import TestPackage
from .logger import get_logger
from .report import gen_report
from .schema import EdgetestValidator, Schema
from .utils import (
gen_requirements_config,
parse_cfg,
parse_toml,
upgrade_pyproject_toml,
upgrade_requirements,
upgrade_setup_cfg,
)
LOG = get_logger(__name__)
[docs]def get_plugin_manager() -> pluggy.PluginManager:
"""Get the plugin manager.
Registers the default ``venv`` plugin.
Returns
-------
PluginManager
The plugin manager.
"""
pm = pluggy.PluginManager("edgetest")
pm.add_hookspecs(hookspecs)
pm.load_setuptools_entrypoints("edgetest")
pm.register(lib)
return pm
@click.command()
@click.option(
"--config",
"-c",
default=None,
type=click.Path(exists=True),
help="Path to the test configuration file",
)
@click.option(
"--requirements",
"-r",
default="requirements.txt",
type=click.Path(),
help="Path to a requirements file",
)
@click.option(
"--environment",
"-e",
default=None,
help="Name of a specific environment to run",
)
@click.option(
"--notest",
is_flag=True,
help="Whether or not to run the test command for each environment",
)
@click.option(
"--nosetup",
is_flag=True,
help="Whether or not to only set up the conda environment(s)",
)
@click.option(
"--extras",
type=str,
multiple=True,
default=None,
help="List of extra installations for the local package. Only used if using ``requirements``",
)
@click.option(
"--deps",
"-d",
type=str,
multiple=True,
default=None,
help="Additional `pip` dependencies to install. Only used if using ``requirements``.",
)
@click.option(
"--command",
type=str,
default="pytest",
help="The test command to use in each environment. Only used if using ``requirements``.",
)
@click.option(
"--export",
is_flag=True,
help="Whether or not to export the updated requirements file. Overwrites input requirements.",
)
def cli(
config,
requirements,
environment,
notest,
nosetup,
extras,
deps,
command,
export,
):
"""Create the environments and test.
If you do not supply a configuration file, this package will search for a
``requirements.txt`` file and create a conda environment for each package in that file.
"""
# Get the hooks
pm = get_plugin_manager()
if config and Path(config).suffix == ".cfg":
conf = parse_cfg(filename=config, requirements=requirements)
elif config and Path(config).suffix == ".toml":
conf = parse_toml(filename=config, requirements=requirements)
else:
# Find the path to the local directory using the requirements file
conf = gen_requirements_config(
fname_or_buf=requirements,
extras=extras,
deps=deps,
command=command,
package_dir=str(Path(requirements).parent),
)
# Validate the configuration file
docstructure = Schema()
pm.hook.addoption(schema=docstructure)
validator = EdgetestValidator(schema=docstructure.schema)
if not validator.validate(conf):
click.echo(f"Unable to validate configuration file. Error: {validator.errors}")
raise ValueError("Unable to validate configuration file.")
conf = validator.document
if environment:
conf["envs"] = [env for env in conf["envs"] if env["name"] == environment]
# Run the pre-test hook
pm.hook.pre_run_hook(conf=conf)
testers: List[TestPackage] = []
for env in conf["envs"]:
testers.append(
TestPackage(
hook=pm.hook,
envname=env["name"],
upgrade=env.get("upgrade"),
lower=env.get("lower"),
package_dir=env["package_dir"],
)
)
# Set up the test environment
if nosetup:
click.echo(f"Using existing environment for {env['name']}...")
testers[-1].setup(skip=True, **env)
else:
testers[-1].setup(**env)
# Run the tests
if notest or not testers[-1].setup_status:
click.echo(f"Skipping tests for {env['name']}")
else:
testers[-1].run_tests(env["command"])
report = gen_report(testers)
click.echo(f"\n\n{report}")
if export and testers[-1].status:
if config is not None and Path(config).name == "setup.cfg":
parser = upgrade_setup_cfg(
upgraded_packages=testers[-1].upgraded_packages(),
filename=config,
)
with open(config, "w") as outfile:
parser.write(outfile)
if "options" not in parser or not parser.get("options", "install_requires"):
click.echo(
"No PEP-517 style requirements in ``setup.cfg`` to update. Updating "
f"{requirements}"
)
upgraded = upgrade_requirements(
fname_or_buf=requirements,
upgraded_packages=testers[-1].upgraded_packages(),
)
with open(requirements, "w") as outfile:
outfile.write(upgraded)
elif config is not None and Path(config).name == "pyproject.toml":
parser = upgrade_pyproject_toml(
upgraded_packages=testers[-1].upgraded_packages(),
filename=config,
)
with open(config, "w") as outfile:
outfile.write(dumps(parser))
if "project" not in parser or not parser.get("project").get("dependencies"):
click.echo(
"No dependencies in ``pyproject.toml`` to update. Updating "
f"{requirements}"
)
upgraded = upgrade_requirements(
fname_or_buf=requirements,
upgraded_packages=testers[-1].upgraded_packages(),
)
with open(requirements, "w") as outfile:
outfile.write(upgraded)
else:
click.echo(f"Overwriting the requirements file {requirements}...")
upgraded = upgrade_requirements(
fname_or_buf=requirements,
upgraded_packages=testers[-1].upgraded_packages(),
)
with open(requirements, "w") as outfile:
outfile.write(upgraded)
# Run the post-test hook
pm.hook.post_run_hook(testers=testers, conf=conf)