Skip to content
Open
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
22 changes: 12 additions & 10 deletions conda/env/specs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from ...exceptions import (
EnvironmentFileExtensionNotValid,
EnvironmentFileNotFound,
EnvSpecPluginNotDetected,
EnvironmentSpecPluginNotDetected,
SpecNotFound,
)
from ...gateways.connection.session import CONDA_SESSION_SCHEMES
Expand All @@ -26,7 +26,7 @@
@deprecated(
"25.9",
"26.3",
addendum="Use conda.base.context.plugin_manager.get_environment_specifer_handler.",
addendum="Use conda.base.context.plugin_manager.get_environment_specifiers.",
)
def get_spec_class_from_file(filename: str) -> FileSpecTypes:
"""
Expand All @@ -50,7 +50,9 @@ def get_spec_class_from_file(filename: str) -> FileSpecTypes:
)
if file_exists:
if ext == "" or ext not in all_valid_exts:
raise EnvironmentFileExtensionNotValid(filename)
raise EnvironmentFileExtensionNotValid(
filename, extensions=list(all_valid_exts)
)
elif ext in YamlFileSpec.extensions:
return YamlFileSpec
elif ext in RequirementsSpec.extensions:
Expand All @@ -62,19 +64,19 @@ def get_spec_class_from_file(filename: str) -> FileSpecTypes:
@deprecated.argument(
"25.9", "26.3", "directory", addendum="Specify the full path in filename"
)
def detect(
filename: str | None = None,
) -> SpecTypes:
def detect(filename: str | None = None) -> SpecTypes:
"""
Return the appropriate spec type to use.

:raises SpecNotFound: Raised if no suitable spec class could be found given the input
"""
if filename is None:
raise SpecNotFound("No filename provided")

try:
spec_hook = context.plugin_manager.get_environment_specifier_handler(
spec_hook = context.plugin_manager.get_environment_specifiers(
filename=filename,
)
except EnvSpecPluginNotDetected as e:
return spec_hook.environment_spec(filename)
except EnvironmentSpecPluginNotDetected as e:
raise SpecNotFound(e.message)

return spec_hook.environment_spec(filename)
29 changes: 17 additions & 12 deletions conda/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1251,8 +1251,13 @@ def __init__(self, filename: os.PathLike, *args, **kwargs):


class EnvironmentFileExtensionNotValid(CondaEnvException):
def __init__(self, filename: os.PathLike, *args, **kwargs):
msg = f"'{filename}' file extension must be one of '.txt', '.yaml' or '.yml'"
def __init__(
self, filename: os.PathLike, extensions: list | None = None, *args, **kwargs
):
if extensions is None:
extensions = [".txt", ".yaml", ".yml"]
Comment on lines +1257 to +1258
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for backward compatibility

extensions_str = ", ".join(extensions)
msg = f"'{filename}' file extension must be one of: {extensions_str}"
self.filename = filename
super().__init__(msg, *args, **kwargs)

Expand All @@ -1272,16 +1277,7 @@ def __init__(self, username: str, packagename: str, *args, **kwargs):
super().__init__(msg, *args, **kwargs)


class SpecNotFound(CondaError):
def __init__(self, msg: str, *args, **kwargs):
super().__init__(msg, *args, **kwargs)


class PluginError(CondaError):
pass


class EnvSpecPluginNotDetected(CondaError):
class EnvironmentSpecPluginNotDetected(CondaError):
def __init__(self, name, plugin_names, *args, **kwargs):
self.name = name
msg = dals(
Expand All @@ -1295,6 +1291,15 @@ def __init__(self, name, plugin_names, *args, **kwargs):
super().__init__(msg, *args, **kwargs)


class SpecNotFound(CondaError):
def __init__(self, msg: str, *args, **kwargs):
super().__init__(msg, *args, **kwargs)


class PluginError(CondaError):
pass


def maybe_raise(error: BaseException, context: Context):
if isinstance(error, CondaMultiError):
groups = groupby(lambda e: isinstance(e, ClobberError), error.errors)
Expand Down
47 changes: 37 additions & 10 deletions conda/plugins/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import functools
import logging
import os
from importlib.metadata import distributions
from inspect import getmodule, isclass
from typing import TYPE_CHECKING, overload
Expand All @@ -24,7 +25,8 @@
from ..deprecations import deprecated
from ..exceptions import (
CondaValueError,
EnvSpecPluginNotDetected,
EnvironmentSpecPluginNotDetected,
EnvironmentFileExtensionNotValid,
PluginError,
)
from . import (
Expand Down Expand Up @@ -487,17 +489,42 @@ def load_settings(self) -> None:
for name, (parameter, aliases) in self.get_settings().items():
add_plugin_setting(name, parameter, aliases)

def get_environment_specifier_handler(
self, filename: str
) -> CondaEnvironmentSpecifier:
def get_environment_specifiers(self, filename: str) -> CondaEnvironmentSpecifier:
hooks = self.get_hook_results("environment_specifiers")
for hook in hooks:
if hook.environment_spec(filename).can_handle():
return hook
if filename.startswith("file://"):
filename = filename[len("file://") :]

# raise error if no plugins found that can read the environment file
hook_names = [h.name for h in hooks]
raise EnvSpecPluginNotDetected(name=filename, plugin_names=hook_names)
# Check extensions
hook_extensions = set().union(
*(hook.environment_spec.extensions for hook in hooks)
)
_, ext = os.path.splitext(filename)
if ext == "" or ext not in hook_extensions:
raise EnvironmentFileExtensionNotValid(filename, extensions=hook_extensions)

# Find a spec that can handle the filename
capable_hooks = [
hook for hook in hooks if hook.environment_spec(filename).can_handle()
]
if len(capable_hooks) == 1:
return capable_hooks[0]
elif len(capable_hooks) > 1:
raise PluginError(
dals(
f"""
Multiple plugins found that can handle the environment file '{filename}':

{", ".join([hook.name for hook in capable_hooks])}

Please make sure that you don't have any incompatible plugins installed.
"""
)
)
else:
# raise error if no plugins found that can read the environment file
raise EnvironmentSpecPluginNotDetected(
name=filename, plugin_names=[hook.name for hook in hooks]
)


@functools.cache
Expand Down
4 changes: 2 additions & 2 deletions tests/plugins/test_env_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from conda import plugins
from conda.env.env import Environment
from conda.exceptions import EnvSpecPluginNotDetected
from conda.exceptions import EnvironmentSpecPluginNotDetected
from conda.plugins.types import CondaEnvironmentSpecifier, EnvironmentSpecBase


Expand Down Expand Up @@ -57,5 +57,5 @@ def test_raises_an_error_if_file_is_unhandleable(dummy_random_spec_plugin):
"""
Ensures that our dummy random spec does not recognize non-".random" files
"""
with pytest.raises(EnvSpecPluginNotDetected):
with pytest.raises(EnvironmentSpecPluginNotDetected):
dummy_random_spec_plugin.get_environment_specifier_handler("test.random-not")