Skip to content
168 changes: 151 additions & 17 deletions conda_lock/conda_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
Dict,
Iterator,
List,
NamedTuple,
Optional,
Sequence,
Set,
Expand Down Expand Up @@ -151,6 +152,13 @@ class UnknownLockfileKind(ValueError):
pass


class DevDependenciesDeprecationInfo(NamedTuple):
dev_dependencies: Optional[bool]
filter_categories: bool
original_extras: List[str]
override_dev_dependency_deprecation: bool


def _extract_platform(line: str) -> Optional[str]:
search = PLATFORM_PATTERN.search(line)
if search:
Expand Down Expand Up @@ -271,6 +279,7 @@ def make_lock_files( # noqa: C901
with_cuda: Optional[str] = None,
strip_auth: bool = False,
mapping_url: str,
dev_dependencies_deprecation_info: Optional[DevDependenciesDeprecationInfo] = None,
) -> None:
"""
Generate a lock file from the src files provided
Expand Down Expand Up @@ -456,6 +465,7 @@ def make_lock_files( # noqa: C901
filename_template=filename_template,
extras=extras,
check_input_hash=check_input_hash,
dev_dependencies_deprecation_info=dev_dependencies_deprecation_info,
)


Expand All @@ -466,6 +476,7 @@ def do_render(
extras: Optional[AbstractSet[str]] = None,
check_input_hash: bool = False,
override_platform: Optional[Sequence[str]] = None,
dev_dependencies_deprecation_info: Optional[DevDependenciesDeprecationInfo] = None,
) -> None:
"""Render the lock content for each platform in lockfile

Expand Down Expand Up @@ -505,6 +516,11 @@ def do_render(
)
sys.exit(1)

deprecated_dev_dependencies = handle_dev_dependencies_deprecation(
dev_dependencies_deprecation_info,
lockfile,
filename_template,
)
for plat in platforms:
for kind in kinds:
if filename_template:
Expand All @@ -515,6 +531,7 @@ def do_render(
"timestamp": datetime.datetime.now(datetime.timezone.utc).strftime(
"%Y%m%dT%H%M%SZ"
),
"dev-dependencies": str(deprecated_dev_dependencies).lower(),
}

filename = filename_template.format(**context)
Expand Down Expand Up @@ -562,6 +579,69 @@ def do_render(
)


def handle_dev_dependencies_deprecation(
dev_dependencies_deprecation_info: Optional[DevDependenciesDeprecationInfo],
lockfile: Lockfile,
filename_template: Optional[str],
) -> bool:
"""Handle the deprecation of the dev-dependencies template variable.

The return value is the boolean value that should be used for the
dev-dependencies template variable.

It should not be common that dev-dependencies is used as a template variable,
so in these cases we can ignore the deprecation.

The previous behavior was pretty screwy. It was based solely on the value
of "--dev-dependencies", which defaulted to True, even if there were no
dev dependencies present.

The desired new behavior is use the presence of dev dependencies in the
lockfile to determine the value of the dev-dependencies template variable.
"""
filename_template_depends_on_dev_dependencies = (
filename_template is not None and "{dev-dependencies}" in filename_template
)

# Whether or not there are dependencies in the "dev" category.
dev_is_a_category = any("dev" in package.categories for package in lockfile.package)
new_dev_dependencies = dev_is_a_category

# The previous behavior.
deprecated_dev_dependencies = (
True
if dev_dependencies_deprecation_info is None
or dev_dependencies_deprecation_info.dev_dependencies is None
else dev_dependencies_deprecation_info.dev_dependencies
)

# Intervene in the case of an actual discrepancy between the current
# behavior and the deprecated behavior.
if (
filename_template_depends_on_dev_dependencies
and deprecated_dev_dependencies != new_dev_dependencies
and dev_dependencies_deprecation_info is not None
):
if new_dev_dependencies and not deprecated_dev_dependencies:
if dev_dependencies_deprecation_info.dev_dependencies is False:
_error_msg = (
"There are dev dependencies present, despite having "
"specified --no-dev-dependencies. Consequently, the "
"{dev-dependencies} template variable has been set to 'false'. "
"The value of {dev-dependencies} will change to 'true' in a "
"future version of conda-lock."
)
else:
_error_msg = (
"There is a discrepancy between the current behavior and the "
"deprecated behavior. The current case is unexpected. Please "
"report this as a bug to ..."
)
else:
raise click.UsageError("x")
return deprecated_dev_dependencies


def render_lockfile_for_platform( # noqa: C901
*,
lockfile: Lockfile,
Expand Down Expand Up @@ -1056,15 +1136,16 @@ def _detect_lockfile_kind(path: pathlib.Path) -> TKindAll:
)


def _deprecated_dev_cli(ctx: click.Context, param: click.Parameter, value: Any) -> Any:
"""A click callback function raising a deprecation error."""
if value:
raise click.BadParameter(
def _deprecated_dev_cli_callback(
ctx: click.Context, param: click.Parameter, value: Any
) -> Any:
"""Raise a deprecation warning and inject `dev` into categories."""
if value is not None:
warn(
"--dev-dependencies/--no-dev-dependencies (lock, render) and --dev/--no-dev (install) "
"switches are deprecated. Use `--extra dev` instead."
"switches are deprecated. Use `--category dev` instead."
)
else:
return value
return value


def handle_no_specified_source_files(
Expand Down Expand Up @@ -1142,6 +1223,7 @@ def run_lock(
metadata_yamls: Sequence[pathlib.Path] = (),
strip_auth: bool = False,
mapping_url: str,
dev_dependencies_deprecation_info: Optional[DevDependenciesDeprecationInfo] = None,
) -> None:
if len(environment_files) == 0:
environment_files = handle_no_specified_source_files(lockfile_path)
Expand All @@ -1168,6 +1250,7 @@ def run_lock(
metadata_yamls=metadata_yamls,
strip_auth=strip_auth,
mapping_url=mapping_url,
dev_dependencies_deprecation_info=dev_dependencies_deprecation_info,
)


Expand Down Expand Up @@ -1222,16 +1305,20 @@ def main() -> None:
help="""Override the channels to use when solving the environment. These will replace the channels as listed in the various source files.""",
)
@click.option(
"--dev-dependencies",
"--no-dev-dependencies",
"--dev-dependencies/--no-dev-dependencies",
"dev_dependencies",
is_flag=True,
flag_value=True,
default=False,
default=None,
help=_deprecated_dev_help,
hidden=False,
is_eager=True,
callback=_deprecated_dev_cli,
callback=_deprecated_dev_cli_callback,
)
@click.option(
"--override-dev-dependency-deprecation",
is_flag=True,
default=False,
help="Restore the deprecated dev dependency behavior.",
hidden=True,
)
@click.option(
"-f",
Expand Down Expand Up @@ -1368,7 +1455,8 @@ def lock(
update: Optional[Sequence[str]] = None,
metadata_choices: Sequence[str] = (),
metadata_yamls: Sequence[PathLike] = (),
dev_dependencies: bool = False, # DEPRECATED
dev_dependencies: Optional[bool] = None, # DEPRECATED
override_dev_dependency_deprecation: bool = False,
) -> None:
"""Generate fully reproducible lock files for conda environments.

Expand Down Expand Up @@ -1413,7 +1501,39 @@ def lock(
else:
virtual_package_spec = pathlib.Path(virtual_package_spec)

dev_dependencies_deprecation_info = DevDependenciesDeprecationInfo(
dev_dependencies=dev_dependencies,
filter_categories=filter_categories,
original_extras=list(extras),
override_dev_dependency_deprecation=override_dev_dependency_deprecation,
)

extras_ = set(extras)
if dev_dependencies is None:
extras_.add("dev")
elif dev_dependencies is True:
extras_.add("dev")
warn(
"The --dev-dependencies option is deprecated. Instead, please use "
"--category dev to include the dev category."
)
elif dev_dependencies is False:
warn(
"The --no-dev-dependencies option is deprecated. Instead, please use "
"'--filter-categories' to exclude the dev category."
)
filter_categories = True
if "dev" in extras_:
error_msg = (
"Contradictory options: --no-dev-dependencies and --category=dev or "
"--extras=dev have been specified. Please specify only one of these. "
"To temporarily override this error for now, use "
"--override-dev-dependency-deprecation."
)
if override_dev_dependency_deprecation:
warn(error_msg)
else:
raise click.UsageError(error_msg)
lock_func = partial(
run_lock,
environment_files=environment_files,
Expand All @@ -1433,6 +1553,7 @@ def lock(
metadata_yamls=[pathlib.Path(path) for path in metadata_yamls],
strip_auth=strip_auth,
mapping_url=mapping_url,
dev_dependencies_deprecation_info=dev_dependencies_deprecation_info,
)
if strip_auth:
with tempfile.TemporaryDirectory() as tempdir:
Expand All @@ -1457,6 +1578,15 @@ def lock(
DEFAULT_INSTALL_OPT_LOCK_FILE = pathlib.Path(DEFAULT_LOCKFILE_NAME)


def _deprecated_capital_e_callback(
ctx: click.Context, param: click.Parameter, value: Any
) -> Any:
"""A click callback function raising a deprecation warning for -E."""
if "-E" in sys.argv and value:
warn("The -E option is deprecated. Use --category or -e instead.")
return value


@main.command("install", context_settings=CONTEXT_SETTINGS)
@click.option(
"--conda",
Expand Down Expand Up @@ -1514,14 +1644,17 @@ def lock(
help=_deprecated_dev_help,
hidden=False,
is_eager=True,
callback=_deprecated_dev_cli,
callback=_deprecated_dev_cli_callback,
)
@click.option(
"-E",
"-e",
"--extras",
"--category",
multiple=True,
default=[],
help="include extra dependencies from the lockfile (where applicable)",
callback=_deprecated_capital_e_callback,
)
@click.option(
"--force-platform",
Expand Down Expand Up @@ -1625,7 +1758,7 @@ def install(
help=_deprecated_dev_help,
hidden=False,
is_eager=True,
callback=_deprecated_dev_cli,
callback=_deprecated_dev_cli_callback,
)
@click.option(
"-k",
Expand All @@ -1643,6 +1776,7 @@ def install(
@click.option(
"-e",
"--extras",
"--category",
default=[],
type=str,
multiple=True,
Expand Down Expand Up @@ -1742,7 +1876,7 @@ def render(
help=_deprecated_dev_help,
hidden=False,
is_eager=True,
callback=_deprecated_dev_cli,
callback=_deprecated_dev_cli_callback,
)
@click.option(
"-f",
Expand Down
4 changes: 1 addition & 3 deletions tests/test_conda_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -2176,9 +2176,7 @@ def test_install(
package = "tzcode"
platform = "linux-64"

lock_filename_template = (
request.node.name + "conda-{platform}.lock"
)
lock_filename_template = request.node.name + "conda-{platform}.lock"
if kind == "env":
lock_filename = request.node.name + "conda-linux-64.lock.yml"
elif kind == "explicit":
Expand Down
Loading