Environment Exporters#

The environment exporter plugin hook allows you to create custom export formats for conda environments. This feature was introduced to enhance the conda export command with a plugin-based architecture for extending conda's environment export capabilities.

Overview#

Environment exporters transform conda environments into various file formats that can be shared, stored, or used to recreate environments. The plugin system supports both structured formats (like YAML/JSON) and text-based formats (like explicit URLs or requirement specs).

Built-in exporters include:

  • environment-yaml - YAML format for cross-platform environments

  • environment-json - JSON format for programmatic processing

  • explicit - CEP 23 compliant explicit URLs format

  • requirements - MatchSpec -based requirements format

Plugin Architecture#

Environment exporters are registered through the conda_environment_exporters plugin hook. Each exporter defines:

  • A unique name and optional aliases

  • Supported filename patterns for auto-detection

  • An export function that transforms environments to the target format

class CondaEnvironmentExporter#

EXPERIMENTAL

Return type to use when defining a conda environment exporter plugin hook.

Parameters:
  • name -- name of the exporter (e.g., environment-yaml)

  • aliases -- user-friendly format aliases (e.g., ("yaml",))

  • default_filenames -- default filenames this exporter handles (e.g., ("environment.yml", "environment.yaml"))

  • export -- callable that exports an Environment to string format

aliases#
default_filenames#
export#
name#
conda_environment_exporters()#

Register new conda environment exporter

Environment exporters serialize conda Environment objects to different output formats (JSON, TOML, XML, etc.) for the 'conda export' command. This is separate from environment specifiers which parse input files.

Example:

import tomlkit
from conda import plugins
from conda.exceptions import CondaValueError
from conda.models.environment import Environment
from conda.plugins.types import CondaEnvironmentExporter


def export_toml(env: Environment) -> str:
    # Export Environment to TOML format
    # For formats that use the standard dictionary structure,
    # you can use the shared utility:
    from conda.plugins.environment_exporters.standard import to_dict

    env_dict = to_dict(env)

    # Create TOML document
    toml_doc = tomlkit.document()

    if env_dict.get("name"):
        toml_doc["name"] = env_dict["name"]

    if env_dict.get("channels"):
        toml_doc["channels"] = env_dict["channels"]

    if env_dict.get("dependencies"):
        toml_doc["dependencies"] = env_dict["dependencies"]

    if env_dict.get("variables"):
        toml_doc["variables"] = env_dict["variables"]

    return tomlkit.dumps(toml_doc)


@plugins.hookimpl
def conda_environment_exporters():
    yield CondaEnvironmentExporter(
        name="environment-toml",
        aliases=("toml",),
        default_filenames=("environment.toml",),
        export=export_toml,
    )

Creating an Environment Exporter Plugin#

What follows are several examples showing how to create new export formats using the environment exporters plugin hook. Please check out our Quick start guide for more detailed instructions on how to create a conda plugin.

Basic Plugin Structure#

Here's a minimal example of an environment exporter plugin:

import conda.plugins
from conda.models.environment import Environment


def export_simple_text(environment: Environment) -> str:
    """Export environment as a simple text list."""
    lines = [f"# Environment: {environment.name}"]

    if environment.dependencies:
        lines.append("# Dependencies:")
        for dep in environment.dependencies:
            lines.append(str(dep))

    return "\n".join(lines)


@conda.plugins.hookimpl
def conda_environment_exporters():
    yield conda.plugins.CondaEnvironmentExporter(
        name="simple-text",
        aliases=("simple", "txt-simple"),
        default_filenames=("environment.txt",),
        export=export_simple_text,
    )

See also

For a general introduction and examples of how to distribute conda plugins, see the ../plugins quick start guide.

Plugin Components#

Below, we explain how to use the plugin you've created above with conda export.

Name and Aliases#

The name field defines the canonical format name used with --format:

conda export --format=simple-text

The aliases tuple provides alternative names for convenience:

conda export --format=simple
conda export --format=txt-simple

Note

Aliases are automatically normalized to lowercase and stripped of whitespace. The plugin system will detect and prevent name collisions.

Default Filenames#

The default_filenames tuple specifies filename patterns for automatic format detection:

# These would auto-detect the simple-text format
conda export --file=environment.txt

Export Function#

The export function receives an Environment object and returns a string representation:

def export_function(environment: Environment) -> str:
    # Access environment properties:
    # - environment.name: environment name
    # - environment.channels: configured channels
    # - environment.dependencies: requested packages (MatchSpec objects)
    # - environment.explicit_packages: all installed packages (PackageRecord objects)
    # - environment.variables: environment variables

    return "formatted content"

Advanced Example: JSON Exporter#

Here's a more sophisticated example that creates a custom JSON format:

import json
from typing import Any, Dict

import conda.plugins
from conda.models.environment import Environment


def export_custom_json(environment: Environment) -> str:
    """Export environment as custom JSON format."""
    data: Dict[str, Any] = {
        "format_version": "1.0",
        "environment": {
            "name": environment.name,
            "channels": [str(channel) for channel in environment.channels],
        },
    }

    # Add dependencies as MatchSpec strings
    if environment.dependencies:
        data["environment"]["dependencies"] = [
            str(dep) for dep in environment.dependencies
        ]

    # Add explicit packages with full metadata
    if environment.explicit_packages:
        data["environment"]["explicit_packages"] = [
            {
                "name": pkg.name,
                "version": pkg.version,
                "build": pkg.build,
                "channel": str(pkg.channel),
                "url": pkg.url,
                "md5": pkg.md5,
            }
            for pkg in environment.explicit_packages
        ]

    # Add environment variables
    if environment.variables:
        data["environment"]["variables"] = dict(environment.variables)

    return json.dumps(data, indent=2, sort_keys=True)


@conda.plugins.hookimpl
def conda_environment_exporters():
    yield conda.plugins.CondaEnvironmentExporter(
        name="custom-json",
        aliases=("cjson",),
        default_filenames=("environment.cjson", "env.cjson"),
        export=export_custom_json,
    )

Error Handling#

Your export function should handle error cases appropriately:

from conda.exceptions import CondaValueError


def export_strict_format(environment: Environment) -> str:
    """Export that requires specific conditions."""
    if not environment.dependencies:
        raise CondaValueError(
            "Cannot export strict format: no dependencies found. "
            "This format requires at least one dependency."
        )

    if not environment.name:
        raise CondaValueError(
            "Cannot export strict format: environment name is required."
        )

    # Continue with export...
    return formatted_content

Working with Different Package Types#

Understanding Package Collections#

The Environment model provides different package collections for different use cases:

dependencies (MatchSpec objects)

Represents user-requested packages. These are the packages the user explicitly asked for, either from history (when using --from-history) or converted from installed packages.

explicit_packages (PackageRecord objects)

Represents all installed packages with full metadata including URLs, checksums, and build information. Used for exact reproduction.

Example usage patterns:

def export_user_requested(environment: Environment) -> str:
    """Export only what the user explicitly requested."""
    if not environment.dependencies:
        raise CondaValueError("No requested packages found")

    lines = []
    for dep in environment.dependencies:
        lines.append(str(dep))  # e.g., "numpy=1.21.0"
    return "\n".join(lines)


def export_exact_reproduction(environment: Environment) -> str:
    """Export for exact environment reproduction."""
    if not environment.explicit_packages:
        raise CondaValueError("No installed packages found")

    lines = ["@EXPLICIT"]
    for pkg in environment.explicit_packages:
        lines.append(pkg.url)  # Full package URL
    return "\n".join(lines)

Plugin Detection and Conflicts#

Automatic Format Detection#

When users run conda export --file=filename.ext, conda:

  1. Checks all registered exporters for matching default_filenames

  2. If exactly one match is found, uses that exporter

  3. If no matches or multiple matches, raises an appropriate error

The detection system is case-insensitive and supports glob-like patterns.

Collision Prevention#

The plugin system automatically prevents naming conflicts:

  • Format names and aliases are normalized (lowercase, stripped)

  • Duplicate format names or aliases raise PluginError

  • This ensures deterministic behavior and clear error messages

Testing Your Plugin#

Here's a basic test structure for your exporter plugin:

import pytest
from conda.models.environment import Environment
from conda.testing.fixtures import tmp_env
from my_export_plugin.exporters import export_custom_json


def test_custom_json_exporter(tmp_env):
    """Test the custom JSON exporter."""
    environment = Environment.from_prefix(tmp_env.prefix)
    result = export_custom_json(environment)

    # Verify the output format
    import json

    data = json.loads(result)
    assert data["format_version"] == "1.0"
    assert "environment" in data
    assert "name" in data["environment"]


def test_empty_environment_handling(tmp_env):
    """Test exporter with empty environment."""
    environment = Environment(name="test-empty")

    # Should handle gracefully or raise appropriate error
    result = export_custom_json(environment)
    data = json.loads(result)
    assert data["environment"]["name"] == "test-empty"

Best Practices#

  1. Validation: Always validate inputs and provide clear error messages

  2. Documentation: Include format specifications and examples in your plugin

  3. Backwards compatibility: Consider versioning your format for future changes

  4. Performance: Optimize for large environments with many packages

  5. Cross-platform: Consider platform differences in your format design

Example Use Cases#

Some ideas for custom environment exporters:

  • Docker integration: Export as Dockerfile or Docker Compose

  • Language-specific: Export as language package files (package.json, Gemfile, etc.)

  • Cloud deployment: Export as cloud infrastructure templates

  • Version control: Export in formats optimized for VCS tracking

  • Documentation: Export as formatted documentation or reports

Further Reading#

For more information about conda plugin development: