Source code for conda_project.cli.main

# -*- coding: utf-8 -*-
# Copyright (C) 2022-2024 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause

# Copyright (C) 2022 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations

import typing
from argparse import REMAINDER, ArgumentParser

from conda_project import __version__

from ..project import DEFAULT_PLATFORMS
from . import commands

if typing.TYPE_CHECKING:
    # This is here to prevent potential future breaking API changes
    # in argparse from affecting at runtime
    from argparse import _SubParsersAction


[docs] def cli() -> ArgumentParser: """Construct the command-line argument parser.""" common = ArgumentParser(add_help=False) common.add_argument( "--directory", metavar="PROJECT_DIR", default=".", help="Project directory (defaults to current directory)", ) extras = ArgumentParser(add_help=False) extras.add_argument( "--project-archive", metavar="PROJECT_ARCHIVE_FILE_OR_URL", help=( "EXPERIMENTAL: Extract and run directly from a project archive. The archive can be a local file " "or a fsspec compatible URL. You may need to install appropriate driver packages to work with " "remote archives. Optionally, use --directory to set the destination directory of the extracted " "project." ), ) extras.add_argument( "--archive-storage-options", help=( "EXPERIMENTAL: Comma separated list of fsspec storage_options for accessing a remote archive " "For example --archive-storage-options username=<user>,password=<pass>" ), action="store", default=None, ) p = ArgumentParser( description="Tool for encapsulating, running, and reproducing projects with conda environments", conflict_handler="resolve", ) p.add_argument( "-V", "--version", action="version", help="Show the conda-prefix-replacement version number and exit.", version="conda_project %s" % __version__, ) subparsers = p.add_subparsers(metavar="command", required=True) _create_init_parser(subparsers, common) _create_lock_parser(subparsers, common, extras) _create_check_parser(subparsers, common, extras) _create_install_parser(subparsers, common, extras) _create_add_parser(subparsers, common, extras) _create_remove_parser(subparsers, common, extras) _create_activate_parser(subparsers, common, extras) _create_clean_parser(subparsers, common) _create_run_parser(subparsers, common, extras) return p
def _create_init_parser( subparsers: "_SubParsersAction", *parent_parsers: ArgumentParser ) -> None: """Add a subparser for the "init" and "create" subcommands. Args: subparsers: The existing subparsers corresponding to the "command" meta-variable. parent_parsers: The parent parsers, which are used to pass common arguments into the subcommands. """ desc = "Initialize a new project" # TODO: If we deprecate "create", this loop can go away. for subcommand_name in ["init", "create"]: p = subparsers.add_parser( subcommand_name, description=desc, help=desc, parents=parent_parsers ) p.add_argument( "-n", "--name", help="Name for the project.", action="store", default=None ) p.add_argument( "-c", "--channel", help=( "Additional channel to search for packages. The default channel is 'defaults'. " "Multiple channels are added with repeated use of this argument." ), action="append", ) p.add_argument( "--platforms", help=( f"Comma separated list of platforms for which to lock dependencies. " f"The default is {','.join(DEFAULT_PLATFORMS)}" ), action="store", default=",".join(DEFAULT_PLATFORMS), ) p.add_argument( "--conda-configs", help=( "Comma separated list of conda configuration parameters to write into the " ".condarc file in the project directory. The format for each config is key=value. " "For example --conda-configs experimental_solver=libmamba,channel_priority=strict" ), action="store", default=None, ) p.add_argument( "--lock", help="Create the conda-lock.<env>.yml file(s)", action="store_true", ) p.add_argument( "--install", help="Create the local conda environment for the current platform.", action="store_true", ) p.add_argument( "--from-environment", help=( "Initialize the default environment spec and lock from an existing " "conda environment by name or prefix." ), action="store", default=None, ) p.add_argument( "dependencies", help=( "Packages to add to the environment.yml. The format for each package is '<name>[<op><version>]' " "where <op> can be =, <, >, <=, or >=." ), action="store", nargs="*", metavar="PACKAGE_SPECIFICATION", ) p.set_defaults(func=getattr(commands, subcommand_name)) def _create_lock_parser( subparsers: "_SubParsersAction", *parent_parsers: ArgumentParser ) -> None: """Add a subparser for the "lock" subcommand. Args: subparsers: The existing subparsers corresponding to the "command" meta-variable. parent_parsers: The parent parsers, which are used to pass common arguments into the subcommands. """ desc = "Lock all conda environments or a specific one by creating conda-lock.<env>.yml file(s)." p = subparsers.add_parser( "lock", description=desc, help=desc, parents=parent_parsers ) p.add_argument( "environment", help="Optional: Lock the selected environment. If no environment name is selected " "all environments are locked.", nargs="?", ) p.add_argument( "--force", help="Remove and recreate existing conda-lock.<env>.yml file(s).", action="store_true", ) p.set_defaults(func=commands.lock) def _create_check_parser( subparsers: "_SubParsersAction", *parent_parsers: ArgumentParser ) -> None: """Add a subparser for the "lock" subcommand. Args: subparsers: The existing subparsers corresponding to the "command" meta-variable. parent_parsers: The parent parsers, which are used to pass common arguments into the subcommands. """ desc = ( "Check the project for inconsistencies or errors. This will check that conda-lock.<env>.yml file(s) " "have been created for each environment and are up-to-date with the source environment specifications. " "If the project is fully locked this command will not print anything and return status code 0. If any " "environment is not fully locked details are printed to stderr and the command returns status code 1." ) p = subparsers.add_parser( "check", description=desc, help=desc, parents=parent_parsers ) p.set_defaults(func=commands.check) def _create_install_parser( subparsers: "_SubParsersAction", *parent_parsers: ArgumentParser ) -> None: """Add a subparser for the "install" and "prepare" subcommands. Args: subparsers: The existing subparsers corresponding to the "command" meta-variable. parent_parsers: The parent parsers, which are used to pass common arguments into the subcommands. """ desc = "Install the packages into the conda environments" for subcommand_name in ["install", "prepare"]: p = subparsers.add_parser( subcommand_name, description=desc, help=desc, parents=parent_parsers ) group = p.add_mutually_exclusive_group(required=False) group.add_argument( "environment", help="Prepare the selected environment. If no environment name is selected " "the first environment defined in the conda-project.yml file is prepared.", nargs="?", ) group.add_argument( "--as-platform", help="Prepare the conda environment assuming a different platform/subdir name.", action="store", metavar="PLATFORM", ) group.add_argument( "--all", help="Check or prepare all defined environments.", action="store_true", ) p.add_argument( "--check-only", help="Check that the prepared conda environment exists and is up-to-date with the " "source environment and files and lockfile and then exit. If the environment is up-to-date " "nothing is printed and the command exists with 0. If the environment is missing or out-of-date " "details are printed to stderr and the command exits with 1.", action="store_true", ) p.add_argument( "--force", help="Remove and recreate an existing environment.", action="store_true", ) p.set_defaults(func=getattr(commands, subcommand_name)) def _create_add_parser( subparsers: "_SubParsersAction", *parent_parsers: ArgumentParser ) -> None: """Add a subparser for the "add" subcommand. Args: subparsers: The existing subparsers corresponding to the "command" meta-variable. parent_parsers: The parent parsers, which are used to pass common arguments into the subcommands. """ desc = "Add packages to an environment" # TODO: If we deprecate "create", this loop can go away. p = subparsers.add_parser( "add", description=desc, help=desc, parents=parent_parsers ) p.add_argument("--environment", default=None) p.add_argument( "-c", "--channel", help=( "Additional channel to search for packages. The default channel is 'defaults'. " "Multiple channels are added with repeated use of this argument." ), action="append", ) p.add_argument( "dependencies", help=( "Packages to add to the environment.yml. The format for each package is " "'[<prefix>::]<name>[<op><version>]' where <op> can be =, <, >, <=, or >=. " "Most commonly `<prefix>::` declares the conda channel from which to install packages. Use the " "prefix `@pip::` to add pypi package dependencies with support for full pypi package specification " "syntax." ), action="store", nargs="*", metavar="PACKAGE_SPECIFICATION", ) p.set_defaults(func=commands.add) def _create_remove_parser( subparsers: "_SubParsersAction", *parent_parsers: ArgumentParser ) -> None: """Add a subparser for the "remove" subcommand. Args: subparsers: The existing subparsers corresponding to the "command" meta-variable. parent_parsers: The parent parsers, which are used to pass common arguments into the subcommands. """ desc = "Remove packages to an environment" # TODO: If we deprecate "create", this loop can go away. p = subparsers.add_parser( "remove", description=desc, help=desc, parents=parent_parsers ) p.add_argument("--environment", default=None) p.add_argument( "dependencies", help=( "Packages to remove from the environment.yml. Only the name of the package is required here. To remove " "a pip package use the pypyi:: prefix." ), action="store", nargs="*", metavar="PACKAGE_SPECIFICATION", ) p.set_defaults(func=commands.remove) def _create_clean_parser( subparsers: "_SubParsersAction", *parent_parsers: ArgumentParser ) -> None: """Add a subparser for the "clean" subcommand. Args: subparsers: The existing subparsers corresponding to the "command" meta-variable. parent_parsers: The parent parsers, which are used to pass common arguments into the subcommands. """ desc = "Clean the conda environments" p = subparsers.add_parser( "clean", description=desc, help=desc, parents=parent_parsers ) p.add_argument( "environment", help="Remove environment prefix for selected environment. If no environment name is selected " "the first environment defined in the conda-project.yml file is removed.", nargs="?", ) p.add_argument( "--all", help="Prepare all defined environments.", action="store_true" ) p.set_defaults(func=commands.clean) def _create_run_parser( subparsers: "_SubParsersAction", *parent_parsers: ArgumentParser ) -> None: """Add a subparser for the "run" subcommand. Args: subparsers: The existing subparsers corresponding to the "command" meta-variable. parent_parsers: The parent parsers, which are used to pass common arguments into the subcommands. """ desc = "Run commands in project environments." p = subparsers.add_parser( "run", description=desc, help=desc, parents=parent_parsers ) p.add_argument( "--environment", help=( "Specify the environment in which to run the command. The default environment for the command is either " "what was specified in the command definition or the first environment defined in the project." ), ) p.add_argument( "--external-environment", metavar="ENV_NAME_OR_PREFIX", help=( "EXPERIMENTAL: Specify the name or prefix path to a conda environment not declared in this project." ), ) p.add_argument( "command", help="Optional: Run a command in the conda environment. The full command can be provided on the CLI " "or the name of a command defined in the conda-project.yml file. If no command is provided " "the first command in the conda-project.yml file is run if there is one.", nargs="?", ) p.add_argument( "extra_args", help="Optional arguments passed to the command", nargs=REMAINDER, default=None, ) p.set_defaults(func=commands.run) def _create_activate_parser( subparsers: "_SubParsersAction", *parent_parsers: ArgumentParser ) -> None: """Add a subparser for the "run" subcommand. Args: subparsers: The existing subparsers corresponding to the "command" meta-variable. parent_parsers: The parent parsers, which are used to pass common arguments into the subcommands. """ desc = "Activate a conda environment defined in the environment.yml or conda-project.yml files." p = subparsers.add_parser( "activate", description=desc, help=desc, parents=parent_parsers ) p.add_argument( "environment", help="Activate selected environment in a new shell. If no environment name is selected " "the first environment defined in the conda-project.yml file is activated.", nargs="?", ) p.set_defaults(func=commands.activate)
[docs] def parse_and_run(args: list[str] | None = None) -> int: """Parse the command-line arguments and run the appropriate sub-command. Args: args: Command-line arguments. Defaults to system arguments. Returns: The return code to pass to the operating system. """ p = cli() parsed_args, _ = p.parse_known_args(args) return parsed_args.func(parsed_args)
[docs] def main() -> int: """Main entry-point into the `conda-project` command-line interface.""" import sys if len(sys.argv) == 1: args = ["-h"] else: args = sys.argv[1:] retcode = parse_and_run(args) return retcode