Manifests#

Manifest parsers and the detection/registry system.

Each parser handles both workspace configuration and task definitions for its file format. The manifests/ package is conda-workspaces’ internal substrate; the package-root modules env_spec.py, lockfile.py and export.py sit on top and expose the public plugin API.

Manifest detection and parser registry (workspaces and tasks).

Search order (same for workspaces and tasks)#

  1. conda.toml – conda-native manifest format

  2. pixi.toml – pixi-native format (compatibility)

  3. pyproject.toml – pixi or conda tables embedded

The first file that exists and contains the relevant configuration wins.

conda_workspaces.manifests.detect_and_parse(start_dir: str | Path | None = None) tuple[Path, WorkspaceConfig][source]#

Detect the workspace manifest and parse it.

Returns (manifest_path, workspace_config).

conda_workspaces.manifests.detect_and_parse_tasks(file_path: Path | None = None, start_dir: Path | None = None) tuple[Path, dict[str, Task]][source]#

Detect (or use file_path) a task file and parse it.

Returns (resolved_path, {task_name: Task}). Raises NoTaskFileError when no file is found.

conda_workspaces.manifests.detect_task_file(start_dir: Path | None = None) Path | None[source]#

Walk up from start_dir looking for a file that contains tasks.

Returns the first match according to _SEARCH_FILES, or None.

conda_workspaces.manifests.detect_workspace_file(start_dir: str | Path | None = None) Path[source]#

Walk up from start_dir to find a workspace manifest.

Returns the path to the first matching file. Raises WorkspaceNotFoundError if none is found.

conda_workspaces.manifests.find_parser(path: Path) ManifestParser[source]#

Return the parser that can handle path.

Raises WorkspaceParseError if no parser matches.

Abstract base class for manifest parsers (workspaces and tasks).

class conda_workspaces.manifests.base.ManifestParser[source]#

Interface that every manifest parser must implement.

Each parser handles one file format (conda.toml, pixi.toml, or pyproject.toml). Subclasses declare which files they can handle via filenames and a short format_alias ("conda" / "pixi" / "pyproject") that the CLI uses for --format values. The registry in conda_workspaces.manifests uses these to auto-detect the right parser and to resolve --format aliases to the parser that owns the matching filename.

A single parser instance handles both workspace configuration and task definitions from the same file.

add_task(path: Path, name: str, task: Task) None[source]#

Persist a new task definition into path.

abstractmethod can_handle(path: Path) bool[source]#

Return True if this parser can read path.

classmethod copy_manifest(source: Path, dest_dir: Path) Path[source]#

Copy the manifest at source into dest_dir; return the target path.

source may be a directory (walked via resolve_source()) or a manifest file. Raises FileNotFoundError, conda_workspaces.exceptions.WorkspaceNotFoundError, or conda_workspaces.exceptions.ManifestExistsError as appropriate; callers layer their own dry-run / console policy on top.

export(envs: Iterable[Environment]) str[source]#

Serialize envs to this parser’s manifest format.

Produces a manifest that, when written to disk and parsed by parse(), describes the same requested dependencies, channels, and declared platforms that envs carry. Each Environment is one (name, platform) pair; envs must all share the same name (conda’s CondaEnvironmentExporter hook calls multiplatform_export with per-platform copies of the same logical environment).

The default implementation writes top-level [workspace], [dependencies], [pypi-dependencies], and [target.<platform>.*] tables — the shape conda.toml and pixi.toml share. PyprojectTomlParser overrides it to nest the same content under [tool.conda] without disturbing the rest of the pyproject. Used as the multiplatform_export callable on the exporter plugins registered from conda_workspaces.plugin.

exporter_aliases: ClassVar[tuple[str, ...]] = ()#

Optional user-friendly aliases for the exporter plugin (e.g. ("conda",) for conda-toml). Empty tuple is fine.

exporter_format: ClassVar[str] = ''#

Canonical conda_environment_exporters plugin name. Empty disables exporter registration for that parser (see conda_workspaces.plugin).

filenames: ClassVar[tuple[str, ...]] = ()#
classmethod for_exporter_format(name: str) ManifestParser | None[source]#

Return the registered parser whose exporter_format matches name.

Companion to for_format_alias() for the conda_environment_exporters plugin side: conda workspace export --format <name> uses the exporter plugin name (e.g. pyproject-toml), which is stored on exporter_format rather than format_alias. Returns None when name is not a manifest-format exporter — the CLI uses this to decide whether to route writes through merge_export(), and a None result simply means “not one of ours, write verbatim”.

classmethod for_format_alias(alias: str) ManifestParser[source]#

Return the registered parser whose format_alias matches alias.

Used by conda workspace init / quickstart to turn a --format value like "pyproject" / "conda" / "pixi" into the parser (and therefore the filename) it implies. Raises ValueError when no parser claims alias. The companion lookup for conda workspace export --format is for_exporter_format() — that side matches the longer conda_environment_exporters plugin name ("pyproject-toml", "conda-toml", "pixi-toml") which is stored on exporter_format. The registry is conda_workspaces.manifests._PARSERS.

format_alias: ClassVar[str] = ''#
has_tasks(path: Path) bool[source]#

Return True if path contains task definitions.

abstractmethod has_workspace(path: Path) bool[source]#

Return True if path contains workspace configuration.

classmethod manifest_data(envs: Iterable[Environment]) dict[str, Any][source]#

Fold one or more Environment objects into a manifest-shaped dict.

Returns the data that export() writers need, with the format-agnostic parts decided once:

  • name / platforms / channels describe the [workspace] table (platforms are the sorted union across envs; channels are taken from the first env — exporter callers pass the same channel list on every platform).

  • conda_deps / pypi_deps are the intersection across envs — specs that match by name and value on every platform, the ones a round-trip parse would put under the top-level [dependencies] / [pypi-dependencies] tables.

  • target[<platform>]["conda"|"pypi"] holds the per-platform delta — specs that appear on some platforms but not others, or whose value differs across platforms. A round-trip parse restores these under [target.<platform>.dependencies] / [target.<platform>.pypi-dependencies].

Used by export() (via _emit_manifest()) and exposed as a classmethod so individual parsers and exporter plugin shims can drive the same folding logic without duplicating it.

property manifest_filename: str#

Canonical filename this parser reads and writes.

The first entry in filenames — e.g. "conda.toml" for CondaTomlParser. Used by manifest_path() and the conda workspace init / quickstart CLI paths so the format-to-filename mapping lives in exactly one place.

manifest_path(root: Path) Path[source]#

Return the manifest path this parser would (or did) write inside root.

merge_export(existing_path: Path, exported: str) str[source]#

Return exported ready to write into an existing existing_path.

The default implementation returns exported unchanged — conda.toml and pixi.toml are manifests we own end-to-end, so regenerating them from an environment is a full replacement (same as conda export -f environment.yaml overwriting an existing environment.yaml).

PyprojectTomlParser overrides this to splice the exporter’s [tool.conda] subtree into the existing pyproject.toml document without disturbing peer tables ([project], [build-system], [tool.ruff] etc.), because pyproject.toml is a shared manifest owned by the Python ecosystem. Called from conda_workspaces.cli.workspace.export only when --file points to an existing file, so a fresh export still writes the exporter output verbatim.

abstractmethod parse(path: Path) WorkspaceConfig[source]#

Parse path and return a WorkspaceConfig.

parse_tasks(path: Path) dict[str, Task][source]#

Parse path and return a mapping of task-name to Task.

remove_target_overrides(container: Container, name: str) None[source]#

Remove name from every [target.<platform>.tasks] under container.

remove_task(path: Path, name: str) None[source]#

Remove the task named name from path.

classmethod resolve_source(source: Path) Path[source]#

Resolve source (directory or file) to a concrete manifest path.

Directories are walked via conda_workspaces.manifests.detect_workspace_file(); files are returned as-is. Raises FileNotFoundError when source does not exist and conda_workspaces.exceptions.WorkspaceNotFoundError when the directory contains no recognisable manifest.

task_to_toml_inline(task: Task) str | InlineTable[source]#

Convert a task to a TOML-serializable value (string or inline table).

write_workspace_stub(base_dir: Path, name: str, channels: list[str], platforms: list[str]) tuple[Path, str][source]#

Create a minimal workspace manifest under base_dir.

Writes a fresh TOML document with [workspace] and an empty [dependencies] table at manifest_path() and returns (path, "Created"). Raises ManifestExistsError if the target file is already present — subclasses that share their file with other tooling (see PyprojectTomlParser) override this method to append their configuration under a nested table instead of refusing outright, and report "Updated" when they did so.

Parser for conda.toml manifests and shared TOML helpers.

The CondaTomlParser handles conda.toml — the conda-native manifest format for both workspace configuration and task definitions.

Helper functions for parsing channels, dependencies, environments, and target overrides are shared with pixi_toml.py and pyproject_toml.py.

class conda_workspaces.manifests.toml.CondaTomlParser[source]#

Parse conda.toml manifests (workspace and tasks).

This is the conda-native format that mirrors pixi.toml structure but uses [workspace] exclusively (no [project] fallback).

add_task(path: Path, name: str, task: Task) None[source]#

Persist a new task definition into path.

can_handle(path: Path) bool[source]#

Return True if this parser can read path.

exporter_format = 'conda-toml'#

Canonical conda_environment_exporters plugin name. Empty disables exporter registration for that parser (see conda_workspaces.plugin).

filenames = ('conda.toml',)#
format_alias = 'conda'#
has_tasks(path: Path) bool[source]#

Return True if path contains task definitions.

has_workspace(path: Path) bool[source]#

Return True if path contains workspace configuration.

parse(path: Path) WorkspaceConfig[source]#

Parse path and return a WorkspaceConfig.

parse_tasks(path: Path) dict[str, Task][source]#

Parse path and return a mapping of task-name to Task.

remove_task(path: Path, name: str) None[source]#

Remove the task named name from path.

conda_workspaces.manifests.toml.tasks_to_toml(tasks: dict[str, Task]) str[source]#

Serialize a full task dict to conda.toml TOML string.

Parser for pixi.toml workspace manifests.

Reads [workspace] (or [project] for legacy manifests), [dependencies], [pypi-dependencies], [feature.*], [environments], and [target.*] tables from pixi.toml.

class conda_workspaces.manifests.pixi_toml.PixiTomlParser[source]#

Parse pixi.toml manifests (workspace and tasks).

add_task(path: Path, name: str, task: Task) None[source]#

Persist a new task definition into path.

can_handle(path: Path) bool[source]#

Return True if this parser can read path.

exporter_format = 'pixi-toml'#

Canonical conda_environment_exporters plugin name. Empty disables exporter registration for that parser (see conda_workspaces.plugin).

filenames = ('pixi.toml',)#
format_alias = 'pixi'#
has_tasks(path: Path) bool[source]#

Return True if path contains task definitions.

has_workspace(path: Path) bool[source]#

Return True if path contains workspace configuration.

parse(path: Path) WorkspaceConfig[source]#

Parse path and return a WorkspaceConfig.

parse_tasks(path: Path) dict[str, Task][source]#

Parse path and return a mapping of task-name to Task.

remove_task(path: Path, name: str) None[source]#

Remove the task named name from path.

Parser for pyproject.toml workspace manifests.

Reads workspace configuration from pyproject.toml, trying these tables in order:

  1. [tool.conda.workspace] – conda-native table

  2. [tool.pixi.workspace] – pixi compatibility

class conda_workspaces.manifests.pyproject_toml.PyprojectTomlParser[source]#

Parse workspace and task config from pyproject.toml.

Tries these tool tables in priority order:

  1. [tool.conda.*] – conda-native tables

  2. [tool.pixi.*] – pixi compatibility

add_task(path: Path, name: str, task: Task) None[source]#

Persist a new task definition into path.

can_handle(path: Path) bool[source]#

Return True if this parser can read path.

export(envs: Iterable[Environment]) str[source]#

Serialize envs as a pyproject.toml with [tool.conda.*].

Same content as ManifestParser.export() — workspace table, dependencies, optional pypi-dependencies, optional per-platform overrides — but wrapped under [tool.conda] so the output drops straight into PEP 621 / pyproject.toml alongside [project], [build-system], and peer tables.

exporter_format = 'pyproject-toml'#

Canonical conda_environment_exporters plugin name. Empty disables exporter registration for that parser (see conda_workspaces.plugin).

filenames = ('pyproject.toml',)#
format_alias = 'pyproject'#
has_tasks(path: Path) bool[source]#

Return True if path contains task definitions.

has_workspace(path: Path) bool[source]#

Return True if path contains workspace configuration.

merge_export(existing_path: Path, exported: str) str[source]#

Splice exported’s [tool.conda] into existing_path.

pyproject.toml is a shared packaging manifest owned by the Python ecosystem; the default “overwrite the file wholesale” behaviour of ManifestParser.merge_export() would silently destroy [project] / [build-system] / [tool.ruff] / etc. Instead we parse the existing document, replace its [tool.conda] subtree with the one export() just produced, and serialise the result.

This is the export-side companion to write_workspace_stub(), which does the same kind of nested-table merge for conda workspace init. Existing [tool.pixi] content is preserved untouched — users who mix both tools stay functional.

parse(path: Path) WorkspaceConfig[source]#

Parse path and return a WorkspaceConfig.

parse_tasks(path: Path) dict[str, Task][source]#

Parse path and return a mapping of task-name to Task.

remove_task(path: Path, name: str) None[source]#

Remove the task named name from path.

tool_section_for_tasks(doc: tomlkit.TOMLDocument) Table[source]#

Return the tool sub-table that owns tasks.

Uses the same precedence as parse_tasks: non-empty tool.conda wins, then non-empty tool.pixi, then falls back to tool.conda for new manifests.

write_workspace_stub(base_dir: Path, name: str, channels: list[str], platforms: list[str]) tuple[Path, str][source]#

Add [tool.conda.workspace] to base_dir/pyproject.toml.

Unlike the default ManifestParser.write_workspace_stub() (which refuses to touch an existing file), pyproject.toml is a shared packaging manifest owned by the Python ecosystem — PEP 621 [project], [build-system], and other tooling tables routinely coexist with ours. We read the existing document if any, add our configuration under the nested [tool.conda] table, and report "Updated" so the CLI can distinguish an append from a create. An existing [tool.conda] or [tool.pixi] raises ManifestExistsError.

Shared logic for normalizing raw task dicts into Task model objects.

conda_workspaces.manifests.normalize.normalize_args(raw: list[Any] | None) list[TaskArg][source]#

Convert raw arg definitions into TaskArg objects.

Accepted shapes: - ["name"] (required arg, no default) - [{"arg": "name", "default": "value"}] - [{"arg": "name", "default": "value", "choices": ["a", "b"]}]

conda_workspaces.manifests.normalize.normalize_depends_on(raw: list[Any] | str | None) list[TaskDependency][source]#

Convert the various depends-on formats into TaskDependency objects.

Accepted shapes: - ["foo", "bar"] (simple list of task names) - [{"task": "foo", "args": ["x"]}, ...] (full dict form) - [{"task": "foo"}, {"task": "bar"}] (pixi alias shorthand)

conda_workspaces.manifests.normalize.normalize_override(raw: dict[str, Any]) TaskOverride[source]#

Parse a raw dict into a TaskOverride.

conda_workspaces.manifests.normalize.normalize_task(name: str, raw: str | list[Any] | dict[str, Any]) Task[source]#

Convert a single raw task value into a Task object.

Handles all the shorthand forms: - "command string" (simple string command) - ["dep1", "dep2"] or [{"task": ...}] (alias / dependency-only) - {cmd: ..., depends-on: ..., ...} (full dict definition)

conda_workspaces.manifests.normalize.parse_feature_tasks(data: dict[str, Any], tasks: dict[str, Task]) None[source]#

Parse [feature.<name>.tasks] and their target overrides.

Merges feature-scoped tasks into tasks in place. Shared by PixiTomlParser and PyprojectTomlParser.

conda_workspaces.manifests.normalize.parse_tasks_and_targets(data: dict[str, Any]) dict[str, Task][source]#

Parse [tasks] and [target.<platform>.tasks] from a data dict.

Shared by CondaTomlParser, PixiTomlParser, and PyprojectTomlParser — the core parsing logic is identical across all three formats once the root data dict is resolved.