Workspace export support#
Anaconda Project ships tooling aimed at converting existing
anaconda-project.yml projects into forms that can be managed by
pixi or by
conda-workspaces.
Both are independently-writable export targets: export-pixi
writes a pixi.toml, and export-conda writes a conda.toml.
The two formats share almost all of their TOML shape — the same
[workspace], [dependencies], [feature.*],
[environments], and [tasks] tables — so a single conversion
pipeline backs both commands; conda.toml is not merely something
this repository reads because it happens to also consume
pixi.toml files, it is a first-class output of its own.
These are the focus of recent maintenance on this repository:
The
anaconda-project export-pixicommand, which writes apixi.toml(and a siblingap_download.pyhelper when needed) alongside an existinganaconda-project.yml.The
anaconda-project export-condacommand, which writes aconda.toml(and the same siblingap_download.pyhelper when needed) alongside an existinganaconda-project.yml, using the identical conversion rules asexport-pixi.A
publication_infofunction inanaconda_project.project_infothat can readanaconda-project.yml,pixi.toml,conda.toml, or apyproject.tomlembedding either format ([tool.pixi]/[tool.conda]) and return a uniform dictionary describing the project’s commands, environments, and variables. Downstream deployment tooling that ingests any of these formats uses this as its single integration point.The
anaconda-project infocommand, a human-readable view of the same data — modeled onpixi info— that works on any of the supported manifest formats.
The rest of this page documents the conventions the conversion uses
to preserve as much of the original project’s behavior as possible.
Except where noted, everything below applies equally to
export-pixi/pixi.toml and export-conda/conda.toml — the
two exporters produce identical content for the same project and
flags.
Export goals#
The conversion is best-effort but tries to satisfy three contracts:
The resulting
pixi.tomlshould be installable and runnable without further manual editing for the common project shapes (unix:commands,notebook:,bokeh_app:,downloads:,variables:, single or multi-environment).Tasks invoked under pixi should mirror the runtime behavior of
anaconda-project runas closely as the underlying tools allow, including HTTP options, environment selection, and download fetching.The converted manifest should be readable for a maintainer who inspects it later — comments mark anything that couldn’t be translated faithfully, and warnings appear at the top of the file when a downstream consumer needs to act.
Environment layout#
When the source yml has a single
env_specs:entry with a non-default name (for examplesampleproj:), packages live in the top-level[dependencies]table (pixi’s default feature) and the named env inherits viafeatures = ["sampleproj"]. Pixi’s mandatory implicitdefaultenv carries the same packages, sopixi install(without-e) still does something useful.Multi-environment projects emit each
env_specs:entry under[environments]in source order. The first uncommented entry is the project’s intended default; downstream tools can extract it with one line ofawk.If one of multiple env_specs is literally named
default, its[environments]slot is rendered as a comment (# default (pixi creates this implicitly...)) and its packages are folded into top-level[dependencies]. Pixi auto-createsdefaultfrom the default feature, so redeclaring it would be redundant.solve-groupis intentionally not emitted. Anaconda Project does not assume environments solve together, and pixi shouldn’t be told otherwise on import.
The --use-default flag#
Projects that have no env_spec literally named default route every
dependency, task, and prepare body through [feature.{name}.*]
indirection — clean for projects that need explicit fan-out, but verbose
for the common case of a single env (or a default command bound to one
specific env in a multi-env project). Passing --use-default to
export-pixi collapses that:
The exporter picks one env_spec to promote to
default: the env_spec attached to the project’s default command (the one a bareanaconda-project runwould invoke), or — if there are no commands — the firstenv_specs:entry declared.That env_spec’s packages, tasks, and prepare body land in top-level
[dependencies]/[tasks.X]/[tasks.prepare]instead of inside[feature.{name}.*]blocks. Other env_specs (in a multi-env project) keep their[feature.{name}.*]scoping unchanged.The flag is a no-op when an env_spec literally named
defaultalready exists — there’s nothing to rename.Under
--use-defaultthe no-oppreparetask is dropped. When there are nodownloads:to fetch, the unflagged export emits anechoplaceholder in[feature.{name}.tasks.prepare]sopixi run prepareresolves to the project’s intended env even when that env’s name isn’tdefault. Under--use-defaultthe promoted env is the implicit default, so the placeholder loses its purpose;prepareis only emitted when there’s real work to do.
When the user runs export-pixi without the flag and the project would
benefit, the CLI prints a recommendation naming the env that would be
promoted. With the flag on, the CLI confirms which env was renamed.
The renaming applies only to the exported pixi.toml; the source
anaconda-project.yml is not modified.
The --add-current-platform flag#
Pixi rejects an env that doesn’t list the host platform; anaconda-project
is more forgiving, so it’s common to find a ported project with a
platforms: list that worked under conda but breaks at pixi install
time on the developer’s machine. Passing --add-current-platform to
export-pixi widens the converted manifest’s platforms list to
include the host’s conda subdir (e.g. osx-arm64) when it isn’t
already declared.
Behavior mirrors --use-default:
Off by default. The exporter does not silently mutate the user’s platforms list — the user explicitly chose what to support.
When omitted, the CLI prints a recommendation if the host platform is missing.
When passed, the CLI prints a confirmation listing the platform that was added — or stays quiet when the platform was already present.
The change applies only to the exported pixi.toml; the source
anaconda-project.yml is not modified.
Channel handling#
Pixi has no defaults meta-channel and no equivalent of conda’s
default_channels configuration. Two cases are handled explicitly:
If the source yml lists no channels at all, the converted manifest is populated from
conda config --show default_channelsrather than a hard-codedconda-forgefallback. This preserves enterprise users’.condarc-configured mirrors.If the source yml includes
defaultsalongside other channels, only thedefaultsentry is expanded; the rest are preserved in source order with duplicate URLs removed.
If conda is not on PATH (or fails to invoke) and defaults
expansion is required, the conversion fails fast — no partial output
is written.
Tasks and command translation#
unix:command lines are translated to a form compatible with pixi’sdeno_task_shelltask runner.${VAR}and%VAR%references are normalized to$VAR;${PROJECT_DIR}becomes$PIXI_PROJECT_ROOT.When both
unix:andwindows:command lines are present, the converter normalizes the windows form (path separators, env vars) and compares to the unix form. Matching forms collapse to a single task; divergent forms emit the unix variant plus a comment noting what the windows form would have rendered.${CONDA_PREFIX}/bin/<name>,${CONDA_PREFIX}/Scripts/<name>, and similar prefix-rooted paths are stripped to the bare command name. Pixi’s task activation already prepends the env’s executable directories toPATH, so explicit prefix paths are redundant and hurt cross-platform portability.
The prepare task#
Every converted project gets a prepare task. It does double duty:
When the source yml declared
downloads:,prepareruns a helper script (ap_download.py, written next topixi.toml) once per download. The helper is pure stdlib and usespython3, so it works whether or not the env declares its own python.Even when there are no downloads,
prepareis emitted as a no-opecho. Its presence serves two purposes:Acts as a marker that downstream deployment tooling can use to detect that this
pixi.tomlwas converted fromanaconda-project.yml.When scoped to a non-default env’s feature (e.g.
[feature.sampleproj.tasks.prepare]), forcespixi run prepareto resolve to that env automatically — useful when the project’s default env_spec is named something other thandefault.
If a download-needing env doesn’t declare python, the converted
pixi.toml carries a # WARNING: comment block at the top
listing the affected envs, and the same warning is printed to stderr
at conversion time. The conversion does not silently mutate the
user’s package list.
HTTP options#
Anaconda Project’s supports_http_options: true (implicit on
notebook: and bokeh_app: commands) tells the underlying tool
to expect --anaconda-project-X flags for host, port, address,
url-prefix, iframe-hosts, no-browser, and use-xheaders. The exporter
translates this contract into pixi args and templated cmd
strings, dispatching by command type:
notebook:commands becomejupyter notebook <file>plus Jupyter-native flags (--port,--ip,--NotebookApp.base_url=...,--NotebookApp.tornado_settings={...}for iframe hosts,--no-browser,--NotebookApp.trust_xheaders=True).hostis dropped because Jupyter has no host-restrict equivalent.bokeh_app:commands becomebokeh serve <app>plus bokeh’s bare flags (--host,--port,--address,--prefix,--use-xheaders).--showis rendered as the inverse of--no-browser.iframe_hostsis dropped because bokeh has no Content-Security-Policy equivalent.Plain
unix:commands withsupports_http_options: truekeep their--anaconda-project-Xflags verbatim. Generic tools that opt in are expected to recognize the canonical names themselves (panel servedoes).unix:commands withsupports_http_options: falseare scanned for HTTP Jinja vars ({{ port }},{{ host }}, etc.) in their templates; pixiargsare declared only for the vars the cmd actually references.
Each flag is wrapped in a Jinja conditional, so a blank pixi arg omits the flag entirely (rather than passing an empty value the underlying tool might reject).
The {% if var %}...{% endif %} and {{ var }} template syntax
inside task cmd strings is intentional, not accidental — pixi
renders task commands through MiniJinja against the positional values
passed to pixi run <task> <values...> (with args defaults
filling in the rest), and only then hands the rendered command to
deno_task_shell for execution. The syntax is supported but not
prominently documented in pixi’s own docs; the converted pixi.toml
is exercising a real pixi feature, and editing the gates by hand is
fine.
The info command#
anaconda-project info prints a human-readable summary of the
project, modeled on pixi info:
$ anaconda-project info --directory .
Project
------------
Type: pixi
Name: Attractors
Description: A panel dashboard using datashader ...
Directory: /path/to/project
Commands
------------
Command: dashboard [default, http]
: env_spec: sampleproj
: cmd: panel serve attractors.ipynb {% if host %}...
: args: host, port, address, url_prefix, ...
Environments
------------
Environment: default
Channels: https://repo.anaconda.com/pkgs/main, ...
Dependency count: 12
Dependencies: colorcet, datashader, fiona, geoviews, ...
Locked: yes
The command works on any supported manifest format. When no format is
forced, detection follows this precedence: conda.toml, then
pixi.toml, then anaconda-project.yml (or its .yaml /
kapsel.* aliases), then pyproject.toml (checking
[tool.conda.workspace] before [tool.pixi.workspace]).
Flags:
--jsonemits the underlyingpublication_infodict as indented JSON, mirroringpixi info --json.--env-pathsincludes the on-disk prefix path for each environment. For pixi projects (top-levelpixi.tomlor apyproject.toml[tool.pixi]embedding) this shells out topixi info --json; the full pixi payload is also stashed under an_pixikey in--jsonmode so callers don’t have to repeat the subprocess. conda-workspaces projects have no equivalent path-resolution mechanism yet, so this flag has no effect for them.--project-type {conda-workspaces,pixi,anaconda-project}forces a manifest format when more than one is present in the directory (e.g. mid-conversion). Forcing a type recognizes that format whether it is a top-level manifest (conda.tomlorpixi.toml) or apyproject.tomlembedding ([tool.conda.workspace]or[tool.pixi.workspace]), and it is checked independently of auto-detect precedence — so a coexisting higher-precedence manifest of a different format never masks the one you asked for. It is an error only when the requested format is present in neither form.
publication_info#
anaconda_project.project_info.publication_info(project_dir)
returns a uniform dictionary regardless of whether the project is
managed by anaconda-project.yml, a converted pixi.toml, a
converted conda.toml, or a pyproject.toml embedding either
format under [tool.pixi] / [tool.conda]. The shape mirrors
Project.publication_info() from the anaconda-project side;
relevant additions for the pixi and conda-workspaces sides (field
population is identical between the two — the only differences are
the manifest filename and lockfile filename each reads):
commands[name]['args']— ordered list of task arg names declared for the task. Empty for tasks without args. Lets downstream tooling drivepixi run <task> *args(or the conda-workspaces equivalent) to supply positional values.commands[name]['env_spec']— resolves to the name of an env that supports the task. Top-level tasks resolve to the project’s default env (literaldefaultif declared, otherwise the first declared env). Feature-scoped tasks resolve to whichever env actually includes the feature.env_specs[name]['locked']—Truewhen the sibling lockfile (pixi.lockorconda.lock) has anenvironments[<name>]entry. Best-effort: any read or parse failure silently falls back toFalse.
For the pyproject.toml-embedded case, name/description
resolution has one documented gap: upstream conda-workspaces itself
falls back to PEP 621 [project].name / [project].description
when [tool.conda.workspace] (or [tool.pixi.workspace]) omits
those fields. This plan’s read-side summary does not replicate that
PEP 621 fallback — if workspace.name is absent, the directory
basename is used instead, matching the top-level-manifest behavior.
This is a deliberate scope limitation (a uniform read-side summary,
not full-fidelity mirroring of every upstream fallback path), not a
bug.
Optional arguments:
project_type='conda-workspaces' | 'pixi' | 'anaconda-project'forces a specific manifest format. Default behavior is auto-detect, withconda.tomlwinning overpixi.toml, which wins overanaconda-project.yml(or its aliases), which wins over apyproject.tomlembedding. Forcing a type recognizes that format whether it is the top-level manifest or apyproject.tomlembedding, checked independently of that precedence order, so a coexisting higher-precedence manifest of a different format never masks it. It is an error only when the requested format is present in neither form in the directory.env_paths=Truepopulatesenv_specs[name]['path']with the on-disk prefix for each declared environment. For anaconda-project this is derived from eachEnvSpec; for pixi (top-level or pyproject.toml-embedded) it requires apixi info --jsonsubprocess (so it is opt-in). Failure to invoke pixi or parse its output raisesRuntimeError. When the source is pixi, the fullpixi info --jsonpayload is also stored under the top-level_pixikey so callers that want richer pixi-specific data don’t pay for a second subprocess. conda-workspaces projects have no equivalent mechanism yet, soenv_paths=Truehas no effect on conda-workspaces results.
Programmatic export API#
For tools that wrap the conversion (launchers, IDE plugins, custom CLIs), the export pipeline is exposed so downstream code never has to scrape generated TOML or re-implement the conversion rules.
project_ops.export_pixi(project, filename, use_default=False, add_current_platform=False)writespixi.tomlto disk and returns aPixiExportStatus. On success the status carries:default_rename_from— the env_spec promoted todefaultbyuse_default, orNonewhen the flag was off, no-op’d (becausedefaultalready existed), or the export failed.current_platform_added— the platform string added to theplatformslist byadd_current_platform, orNonewhen the flag was off, no-op’d (the platform was already declared), or the export failed.
Callers use these to surface “Renamed env X → default” or “Added platform Y” in their success notification without re-querying the project.
project_ops.preview_pixi_export(project, use_default=False, add_current_platform=False)runs the conversion in memory and returns a dict{pixi_toml, default_rename_from, current_platform_addition_target, warnings}. Thepixi_tomlentry is the would-be file content; the rename / platform-addition fields report the candidates (whether or not their flag was passed), so a confirmation dialog can offer “re-render with –use-default” or “re-render with –add-current-platform” as actions;warningsis the leading# WARNING:block already extracted from the rendered TOML, so callers don’t have to grep for it themselves. RaisesCondaNotAvailableErrorif the conversion needsconda config --show default_channelsand conda isn’t reachable.project_ops.export_conda(project, filename, use_default=False, add_current_platform=False)writesconda.tomlto disk and returns aPixiExportStatus— the exact same status classexport_pixireturns, sincedefault_rename_fromandcurrent_platform_addedare generic export concepts with nothing pixi-specific baked into the class. Semantics of both fields are identical toexport_pixi’s.project_ops.preview_conda_export(project, use_default=False, add_current_platform=False)runs the conversion in memory and returns a dict{conda_toml, default_rename_from, current_platform_addition_target, warnings}— the same four concepts aspreview_pixi_export, with only the file-content key renamed (conda_tomlinstead ofpixi_toml) to match the format it describes.anaconda_project.internal.pixi_export.export_conda_toml(project, use_default=False, add_current_platform=False, default_channels=None)is the module-level functionexport_condacalls to produceconda.tomlcontent; it delegates directly toexport_pixi_tomland returns its result unchanged. conda-workspaces 0.7.0’s TOML shape is field-for-field identical to pixi.toml’s for every field this exporter emits, so no translation step exists between the two — the same function produces both outputs, distinguished only by which file the caller writes it to.anaconda_project.internal.pixi_export.default_rename_target(project)returns the env_spec name that--use-defaultwould promote, orNonewhen promotion is a no-op (project already has adefaultenv_spec, or has no env_specs at all). Stable contract; the underlying selection rule (default-command’s env, else first declared env_spec) lives in this one function. Shared by both the pixi and conda-workspaces export paths.anaconda_project.internal.pixi_export.current_platform_addition_target(project)returns the host’s conda subdir if it would be added by--add-current-platform, orNonewhen it’s already in the union of declared platforms. Same shape asdefault_rename_targetso callers can decide both flags symmetrically. Shared by both the pixi and conda-workspaces export paths.
Limitations#
A few aspects of anaconda-project.yml cannot be translated
faithfully. The exporter surfaces them as comments rather than
silently dropping them:
services:(e.g. Redis) — pixi has no equivalent service-launch primitive. The converted manifest carries a comment listing the required services so a maintainer can wire them up out of band.Variables without defaults — anaconda-project would prompt; pixi cannot. The converted manifest emits a comment listing variables that must be set in the environment before running.