Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor #12

Draft
wants to merge 5 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/conx.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
134 changes: 134 additions & 0 deletions assets/conx.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions condax/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@


if sys.version_info >= (3, 8):
import importlib.metadata as metadata
import importlib.metadata as _metadata
else:
import importlib_metadata as metadata
import importlib_metadata as _metadata

__version__ = metadata.version(__package__)
__version__ = _metadata.version(__package__)
4 changes: 3 additions & 1 deletion condax/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import click
from click_aliases import ClickAliasedGroup

import condax.config as config
from condax import __version__
Expand All @@ -11,7 +12,8 @@

Conda environment location is {config.DEFAULT_PREFIX_DIR}\n
Links to apps are placed in {config.DEFAULT_BIN_DIR}
"""
""",
cls=ClickAliasedGroup,
)
@click.version_option(
__version__,
Expand Down
8 changes: 1 addition & 7 deletions condax/cli/__main__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import logging
import sys
from urllib.error import HTTPError
from condax import config
from condax.exceptions import CondaxError
from .install import install
from .remove import remove, uninstall
from .remove import remove
from .update import update
from .list import run_list
from .ensure_path import ensure_path
Expand All @@ -19,7 +18,6 @@ def main():
for subcommand in (
install,
remove,
uninstall,
update,
run_list,
ensure_path,
Expand All @@ -34,10 +32,6 @@ def main():
logger = logging.getLogger(__package__)

try:
try:
config.set_via_file(config.DEFAULT_CONFIG)
except config.MissingConfigFileError:
pass
cli()
except CondaxError as e:
if e.exit_code:
Expand Down
20 changes: 10 additions & 10 deletions condax/cli/install.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import logging
from typing import List
from typing import Iterable, List

import click

import condax.config as config
import condax.core as core
from condax import __version__
from condax import __version__, consts, core
from condax.condax import Condax

from . import cli, options

Expand All @@ -15,7 +12,7 @@
Install a package with condax.

This will install a package into a new conda environment and link the executable
provided by it to `{config.DEFAULT_BIN_DIR}`.
provided by it to `{consts.DEFAULT_PATHS.bin_dir}`.
"""
)
@options.channels
Expand All @@ -25,10 +22,13 @@
def install(
packages: List[str],
is_forcing: bool,
log_level: int,
channels: Iterable[str],
condax: Condax,
**_,
):
for pkg in packages:
core.install_package(
pkg, is_forcing=is_forcing, conda_stdout=log_level <= logging.INFO
condax.install_package(
pkg,
is_forcing=is_forcing,
channels=channels,
)
103 changes: 89 additions & 14 deletions condax/cli/options.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
import logging
import subprocess
import rainbowlog
import yaml
from statistics import median
from typing import Callable, Sequence
from typing import Any, Callable, Mapping, Optional, Sequence
from pathlib import Path
from functools import wraps

from condax import config
from condax import consts
from condax.condax import Condax
from condax.conda import Conda

import click

from condax.utils import FullPath


def common(f: Callable) -> Callable:
"""
This decorator adds common options to the CLI.
"""
options: Sequence[Callable] = (
config_file,
log_level,
condax,
setup_logging,
click.help_option("-h", "--help"),
)

Expand All @@ -28,22 +34,35 @@ def common(f: Callable) -> Callable:

packages = click.argument("packages", nargs=-1, required=True)

config_file = click.option(

def _config_file_callback(_, __, config_file: Path) -> Mapping[str, Any]:
try:
with (config_file or consts.DEFAULT_PATHS.conf_file).open() as cf:
config = yaml.safe_load(cf) or {}
except FileNotFoundError:
config = {}

if not isinstance(config, dict):
raise click.BadParameter(
f"Config file {config_file} must contain a dict as its root."
)

return config


config = click.option(
"--config",
"config_file",
type=click.Path(exists=True, path_type=Path),
help=f"Custom path to a condax config file in YAML. Default: {config.DEFAULT_CONFIG}",
callback=lambda _, __, f: (f and config.set_via_file(f)) or f,
help=f"Custom path to a condax config file in YAML. Default: {consts.DEFAULT_PATHS.conf_file}",
callback=_config_file_callback,
)

channels = click.option(
"--channel",
"-c",
"channels",
multiple=True,
help=f"""Use the channels specified to install. If not specified condax will
default to using {config.DEFAULT_CHANNELS}, or 'channels' in the config file.""",
callback=lambda _, __, c: (c and config.set_via_value(channels=c)) or c,
help="Use the channels specified in addition to those in the configuration files of condax, conda, and/or mamba.",
)

envname = click.option(
Expand Down Expand Up @@ -80,11 +99,67 @@ def common(f: Callable) -> Callable:
help="Decrease verbosity level.",
)

bin_dir = click.option(
"-b",
"--bin-dir",
type=click.Path(exists=True, path_type=Path),
help=f"Custom path to the condax bin directory. Default: {consts.DEFAULT_PATHS.bin_dir}",
)


def conda(f: Callable) -> Callable:
"""
This click option decorator adds the --config option to the CLI.
It constructs a `Conda` object and passes it to the decorated function as `conda`.
It reads the config file and passes it as a dict to the decorated function as `config`.
"""

@config
@wraps(f)
def construct_conda_hook(config: Mapping[str, Any], **kwargs):
return f(
conda=Conda(config.get("channels", [])),
config=config,
**kwargs,
)

return construct_conda_hook


def condax(f: Callable) -> Callable:
"""
This click option decorator adds the --bin-dir option as well as all those added by `options.conda` to the CLI.
It then constructs a `Condax` object and passes it to the decorated function as `condax`.
"""

@conda
@bin_dir
@wraps(f)
def construct_condax_hook(
conda: Conda, config: Mapping[str, Any], bin_dir: Optional[Path], **kwargs
):
return f(
condax=Condax(
conda,
bin_dir
or config.get("bin_dir", None)
or config.get("target_destination", None) # Compatibility <=0.0.5
or consts.DEFAULT_PATHS.bin_dir,
FullPath(
config.get("prefix_dir", None)
or config.get("prefix_path", None) # Compatibility <=0.0.5
or consts.DEFAULT_PATHS.prefix_dir
),
),
**kwargs,
)

return construct_condax_hook


def log_level(f: Callable) -> Callable:
def setup_logging(f: Callable) -> Callable:
"""
This click option decorator adds -v and -q options to the CLI, then sets up logging with the specified level.
It passes the level to the decorated function as `log_level`.
"""

@verbose
Expand All @@ -101,6 +176,6 @@ def setup_logging_hook(verbose: int, quiet: int, **kwargs):
)
)
logger.setLevel(level)
return f(log_level=level, **kwargs)
return f(**kwargs)

return setup_logging_hook
Loading