Source code for conda_workspaces.exceptions

"""Exception hierarchy for conda-workspaces."""

from __future__ import annotations

from typing import TYPE_CHECKING

from conda.exceptions import CondaError

if TYPE_CHECKING:
    from pathlib import Path


[docs] class CondaWorkspacesError(CondaError): """Base exception for all conda-workspaces errors. Subclasses provide *hints* — actionable suggestions shown below the main error message. The full ``str(exc)`` still contains everything (for conda's fallback handler), but the Rich renderer in ``main.py`` uses ``error_message`` and ``hints`` separately. """ error_message: str hints: list[str] def __init__( self, message: str, *, hints: list[str] | None = None, ) -> None: self.error_message = message self.hints = hints or [] full = message if self.hints: full += "\n" + "\n".join(self.hints) super().__init__(full)
[docs] class WorkspaceNotFoundError(CondaWorkspacesError): """No workspace manifest was found in *search_dir* or its parents.""" def __init__(self, search_dir: str | Path) -> None: self.search_dir = search_dir super().__init__( f"No workspace manifest found in '{search_dir}' or any parent directory.", hints=[ "Create a conda.toml, pixi.toml, or pyproject.toml" " (with [tool.conda.workspace]) to define a workspace.", ], )
[docs] class WorkspaceParseError(CondaWorkspacesError): """The workspace manifest could not be parsed.""" def __init__(self, path: str | Path, reason: str) -> None: self.path = path self.reason = reason super().__init__( f"Failed to parse workspace manifest '{path}': {reason}", hints=["Check the file syntax and try again."], )
[docs] class EnvironmentNotFoundError(CondaWorkspacesError): """The requested environment is not defined in the workspace.""" def __init__(self, name: str, available: list[str]) -> None: self.name = name self.available = available hints = [] if available: hints.append(f"Available environments: {', '.join(sorted(available))}") super().__init__( f"Environment '{name}' is not defined in the workspace.", hints=hints, )
[docs] class EnvironmentNotInstalledError(CondaWorkspacesError): """The requested environment exists but is not installed.""" def __init__(self, name: str) -> None: self.name = name super().__init__( f"Environment '{name}' is not installed.", hints=[f"Run 'conda workspace install -e {name}' first."], )
[docs] class ManifestExistsError(CondaWorkspacesError): """A workspace manifest already exists at the target path.""" def __init__(self, path: str | Path) -> None: self.path = path super().__init__( f"'{path}' already exists.", hints=["Use a different format or location."], )
[docs] class QuickstartCopyError(CondaWorkspacesError): """``conda workspace quickstart --copy`` / ``--clone`` cannot use the source. Raised by :func:`conda_workspaces.cli.workspace.quickstart` when ``--copy`` / ``--clone`` points at something we can't turn into a manifest: a missing path, a directory without a recognisable manifest, or a destination where one already exists. Lives here (not in the CLI module) so callers outside the CLI — tests, downstream tooling — can catch it without importing the command module itself. """
[docs] class FeatureNotFoundError(CondaWorkspacesError): """A feature referenced by an environment does not exist.""" def __init__(self, feature: str, environment: str) -> None: self.feature = feature self.environment = environment super().__init__( f"Feature '{feature}' referenced by environment '{environment}'" " is not defined in the workspace.", hints=[ f"Add [feature.{feature}.dependencies] to your manifest.", ], )
[docs] class PlatformError(CondaWorkspacesError): """Platform configuration error.""" def __init__(self, platform: str, available: list[str]) -> None: self.platform = platform self.available = available super().__init__( f"Platform '{platform}' is not supported by this workspace.", hints=[ f"Supported platforms: {', '.join(sorted(available))}", ], )
[docs] class SolveError(CondaWorkspacesError): """Dependency solving failed for an environment, optionally scoped to a platform.""" def __init__( self, environment: str, reason: str, *, platform: str | None = None, ) -> None: self.environment = environment self.reason = reason self.platform = platform target = ( f"environment '{environment}' for platform '{platform}'" if platform else f"environment '{environment}'" ) super().__init__( f"Failed to solve {target}: {reason}", hints=["Check your dependency specifications and channel configuration."], )
[docs] class AllTargetsUnsolvableError(CondaWorkspacesError): """Every ``(environment, platform)`` pair failed under ``--skip-unsolvable``. Raised after the loop when at least one pair failed to solve *and* no pair succeeded, so writing a lockfile would produce an empty file that silently loses every environment. """ def __init__(self, failures: list[SolveError]) -> None: self.failures = failures summary = "\n".join( f" - {failure.environment}" + (f" on {failure.platform}" if failure.platform else "") + f": {failure.reason}" for failure in failures ) super().__init__( "Every (environment, platform) pair failed to solve:\n" + summary, hints=[ "Fix at least one pair, or re-run without --skip-unsolvable" " to see a single fail-fast error.", ], )
[docs] class ActivationError(CondaWorkspacesError): """Environment activation failed.""" def __init__(self, environment: str, reason: str) -> None: self.environment = environment self.reason = reason super().__init__( f"Failed to activate environment '{environment}': {reason}", hints=[ f"Ensure the environment is installed:" f" conda workspace install -e {environment}", ], )
[docs] class LockfileNotFoundError(CondaWorkspacesError): """No lockfile or lockfile entry exists for the requested environment.""" def __init__(self, environment: str, path: str | Path) -> None: self.environment = environment self.path = path super().__init__( f"No lockfile entry found for environment '{environment}' in {path}.", hints=["Run 'conda workspace install' to generate one."], )
[docs] class LockfileMergeError(CondaWorkspacesError): """A set of ``conda.lock`` fragments cannot be merged into one file. Raised by :func:`conda_workspaces.lockfile.merge_lockfiles` when the caller-provided fragments disagree on metadata (schema version, channel list for a shared environment) or overlap on an ``(environment, platform)`` pair that would otherwise be written twice. The *reason* attribute carries the human-readable detail. """ def __init__(self, reason: str, *, hints: list[str] | None = None) -> None: self.reason = reason super().__init__( f"Cannot merge lockfile fragments: {reason}", hints=hints or [ "Regenerate the offending fragment with `conda workspace" " lock --platform <subdir> --output conda.lock.<subdir>`" " and re-run the merge.", ], )
[docs] class LockfileStaleError(CondaWorkspacesError): """The lockfile is older than the workspace manifest.""" def __init__(self, manifest: str | Path, lockfile: str | Path) -> None: self.manifest = manifest self.lockfile = lockfile super().__init__( f"Lockfile '{lockfile}' is out of date" f" (manifest '{manifest}' has been modified since the last lock).", hints=[ "Run 'conda workspace lock' to update it," " or use --frozen to install anyway.", ], )
[docs] class TaskNotFoundError(CondaWorkspacesError): """Raised when a referenced task does not exist.""" def __init__(self, task_name: str, available: list[str] | None = None) -> None: hints = [] if available: hints.append(f"Available tasks: {', '.join(sorted(available))}") super().__init__(f"Task '{task_name}' not found.", hints=hints)
[docs] class CyclicDependencyError(CondaWorkspacesError): """Raised when the task dependency graph contains a cycle.""" def __init__(self, cycle: list[str]) -> None: path = " -> ".join(cycle) super().__init__( f"Cyclic dependency detected: {path}", hints=["Remove or restructure the circular depends-on references."], )
[docs] class TaskParseError(CondaWorkspacesError): """Raised when a task definition file cannot be parsed.""" def __init__(self, path: str | Path, reason: str) -> None: super().__init__( f"Failed to parse '{path}': {reason}", hints=["Check the file syntax and try again."], )
[docs] class TaskExecutionError(CondaWorkspacesError): """Raised when a task command exits with a non-zero status.""" def __init__(self, task_name: str, exit_code: int) -> None: super().__init__( f"Task '{task_name}' failed with exit code {exit_code}.", )
[docs] class NoTaskFileError(CondaWorkspacesError): """Raised when no task definition file is found.""" def __init__(self, search_dir: str) -> None: super().__init__( f"No task file found in '{search_dir}'.", hints=[ "Create a conda.toml, pixi.toml, or pyproject.toml" " with task definitions.", ], )