# conda-pypi


conda-pypi is a [conda](https://pydevtools.com/handbook/reference/conda.md) plugin that installs [PyPI](https://pydevtools.com/handbook/explanation/what-is-pypi.md) packages into a conda environment where the conda solver can track them. It closes the long-standing gap where [pip](https://pydevtools.com/handbook/reference/pip.md)-installed packages were invisible to conda and the two views of the environment would drift apart. With the conda-pypi channel configured, the standard `conda install` command resolves conda-forge and PyPI packages in a single solve, with no separate pip step.

The plugin offers two mechanisms, and they behave differently:

- **The channel path.** The conda-pypi channel serves [wheel](https://pydevtools.com/handbook/reference/wheel.md) metadata directly in its repodata. Conda extracts those wheels natively into its package cache and links them into the environment, with no conversion step. This is the recommended path.
- **The conversion path.** `conda pypi install` and `conda pypi convert` build a [`.conda` artifact](https://pydevtools.com/handbook/reference/conda-package.md) from a wheel, sdist, or local project. Conversion loses dependency conditions, so the two paths do not produce identical environments.

Sections below name the path they describe.

> [!WARNING]
> conda-pypi is in public beta. The plugin is suitable for experimentation and non-critical workloads; its maintainers advise against production use.

## Why conda-pypi Exists

Mixing conda and pip in a single environment is a recurring source of broken installs. Conda's dependency solver only sees packages it installed; pip's only sees packages it installed. When the two install overlapping dependencies, conda can overwrite pip-installed files, pip can leave conda's metadata stale, and `conda update` afterward can produce an environment that no longer matches either tool's record of it. The conventional workaround is the pip-last pattern: install everything available on conda channels first, run `pip install` for whatever is left, and never run a conda command in that environment again. The pattern works but is fragile and undocumented in the environment itself.

conda-pypi replaces the pattern with a single tool that records PyPI installs the same way conda records its own. Every PyPI package is registered in the environment's `conda-meta` directory, so the solver sees it on subsequent operations.

## Installation

conda-pypi requires conda 26.5.3 or newer and the rattler solver. On a recent [Miniforge](https://github.com/conda-forge/miniforge), one command pulls in both:

```bash
conda install -n base "conda>=26.5.3"
```

The upgrade transaction pulls `conda-pypi` and `conda-rattler-solver` in as dependencies. After the upgrade, select the rattler solver and add both channels:

```bash
conda config --set solver rattler
conda config --append channels conda-pypi
conda config --append channels conda-forge
```

The channel's name mapping targets conda-forge, so conda-forge supplies the dependencies conda-pypi cannot serve itself.

Conda's default channel priority, `flexible`, is the tested setting. Environments configured for conda-forge often set `strict` instead, which the conda-pypi channel does not support; reset it for this workflow:

```bash
conda config --set channel_priority flexible
```

This is a global setting. Changing it affects every environment on the machine, not just those using conda-pypi. It also lets a PyPI prerelease outrank a stable conda-forge build, described in [Limits](#limits).

`conda --version` should report `26.5.3` or newer when the upgrade completes. No shell restart is required. The plugin and its subcommands work on conda 26.5, but the channel needs 26.5.3, which fixed conda caching a not-found response for channels that serve only sharded repodata.

## The conda-pypi Channel

The `conda-pypi` channel hosted by Anaconda translates the metadata of approximately 600,000 pure Python wheels into conda repodata. The wheel files themselves are still fetched from `pypi.org` at install time. The channel is free for all use and is not subject to the Anaconda commercial-license terms that apply to the default channel.

Appending the channel globally (the `conda config --append channels conda-pypi` step in [Installation](#installation)) enables the native `conda install` integration.

Package names are mapped to [conda-forge](https://pydevtools.com/handbook/reference/conda-forge.md) conventions, so PyPI's `typing-extensions` resolves as `typing_extensions`. This makes conda-forge the compatible pairing; against Anaconda's `defaults` channel, some names will not line up.

Three beta limitations affect the channel:

- The channel may not appear in the Anaconda.org web UI.
- `conda search` fails against it, because that command requests classic `repodata.json` metadata rather than the sharded format the channel serves. Use `conda install --dry-run` or `conda create --dry-run` with the rattler solver to check availability instead.
- Extras cannot be requested from the command line. `conda install package[extras=name]` fails. Extras declared inside a package's own metadata still resolve during the solve.

### Check how current the channel is

The channel is rebuilt in batches rather than synced continuously. Its shard index reports when the current build was published:

```bash
curl -sI https://conda.anaconda.org/conda-pypi/noarch/repodata_shards.msgpack.zst | grep last-modified
```

```
last-modified: Tue, 04 Aug 2026 14:01:14 GMT
```

Nothing uploaded to PyPI after that timestamp is available, and some releases published well before it are missing too. That build carries `fastapi` 0.136.1 as its newest entry, though 0.137.0 through 0.141.1 reached PyPI in the months beforehand.

The lag stays invisible for any package conda-forge also carries, because the newer conda-forge build wins the solve: `conda install fastapi` resolves 0.141.1 from conda-forge rather than the channel's 0.136.1. It bites on the PyPI-only packages the channel exists to serve, where nothing else can supply a newer version.

## Usage

### Native `conda install`

With the `conda-pypi` channel appended to the global config (see [Installation](#installation)), the standard `conda install` command resolves conda-forge and PyPI packages together:

```bash
conda install scipy requests
```

`scipy` resolves from conda-forge; `requests`, if not available on any conda channel, resolves from PyPI and its wheel is extracted natively. Packages from both sources are tracked in `conda-meta` on equal footing.

Lockfiles cover both sources the same way. `conda export` writes one, and repeating `--platform` produces a single lockfile covering every listed platform:

```bash
conda export --file conda-lock.yml --platform linux-64 --platform osx-arm64
```

## Subcommands

### `conda pypi install`

> [!WARNING]
> `conda pypi install` is pending deprecation and is scheduled for removal in conda 27.9. The deprecation is registered against conda's version, not the plugin's, so upgrading conda is what triggers it. Prefer the channel-based `conda install` workflow for new work.

Installs PyPI packages into the active or named environment using a hybrid resolution strategy. Explicitly requested packages always come from PyPI and are converted to `.conda` format on the fly. Dependencies follow a conda-first preference: if a dependency exists on a configured conda channel, conda installs that build; otherwise the dependency is also pulled from PyPI and converted.

Use this subcommand when you need flags specific to PyPI installs (editable installs, custom index URLs) that the standard `conda install` command does not expose.

Common flags:

- `-n, --name ENV` targets a named environment without activating it.
- `-p, --prefix PATH` targets an environment by full path.
- `-i, --index-url URL` adds a PyPI index URL; can be repeated for multiple indexes.
- `--ignore-channels` forces every package, including dependencies, to be resolved from PyPI rather than the configured conda channels.
- `-e, --editable PATH` installs a local project in editable mode by building a `.conda` artifact from the source tree and linking it into the environment.
- `-d, --dry-run` reports the resolved install plan without writing files.

The `conda pypi` parent command accepts `-n`, `-p`, and `-d` as well, so `conda pypi --dry-run install <pkg>` and `conda pypi install --dry-run <pkg>` are both valid.

Package names are translated between PyPI and conda conventions using the [Grayskull](https://github.com/conda/grayskull) name-mapping tables.

### `conda pypi convert`

Builds a local Python project, sdist, or wheel into a `.conda` artifact without installing it. Useful for preparing offline installs, building internal mirrors, or inspecting how the plugin would translate a package before committing to an install.

The positional argument is a path on the local filesystem, not a PyPI package name. Convert a remote package by downloading the wheel first (for example with `pip download`) and pointing `conda pypi convert` at the local file.

Common flags:

- `--output-folder DIR` writes the converted artifacts to a directory rather than the conda package cache.
- `-e, --editable` builds the project as an editable package.
- `-t, --test-dir DIR` injects a test-files directory into the converted conda package.
- `--name-mapping FILE` points at a JSON file containing a custom PyPI-to-conda name mapping.

## Environment Protection

conda-pypi is designed to write an `EXTERNALLY-MANAGED` file into the environment's standard library location, the [PEP 668](https://pydevtools.com/handbook/explanation/what-is-pep-668.md) mechanism Debian uses to protect the system Python. `pip install` against such an environment is refused rather than allowed to corrupt it. See [How to fix the externally-managed-environment error](https://pydevtools.com/handbook/how-to/how-to-fix-the-externally-managed-environment-error.md) for the underlying mechanism.

> [!WARNING]
> During the beta, conda-pypi does not add this file to conda environments. Nothing currently blocks `pip install` inside an environment the plugin manages, so the drift this page describes is still possible.

What the plugin does instead is print an informational message when it detects pip in an environment. Silence it with `conda config --set plugins.conda_pypi_pip_warning false`. The setting controls only that message; no protection is attached to it.

## Limits

- **The channel is a rebuilt snapshot, not a live mirror.** Releases reach it in batches, so a package's newest version can be weeks behind PyPI and some versions are skipped entirely. [Check how current the channel is](#check-how-current-the-channel-is) before depending on a specific version.
- **Prereleases are served alongside stable releases and can win the solve.** The channel carries every pure Python wheel a project has published, dev and release-candidate builds included. Under the `flexible` channel priority this page recommends, the highest version wins regardless of which channel supplies it, so appending conda-pypi changes what a plain `conda install httpx` resolves: 0.28.1 from conda-forge without the channel, 1.0.dev3 from conda-pypi with it. The substitution also happens transitively, reaching packages the install never named.
- **Pure Python wheels only.** Packages with platform-specific compiled extensions are not supported. Compiled scientific stacks (NumPy, SciPy, PyTorch, CuPy) must come from conda-forge or another conda channel.
- **[PEP 508](https://peps.python.org/pep-0508/) marker handling depends on which path installs the package, and neither is lossless.** Through the channel, markers with a conda equivalent are translated into conditional syntax the solver reads: `python_version` becomes a constraint on `python`, and platform markers become conda [virtual packages](https://docs.conda.io/projects/conda/en/stable/user-guide/tasks/manage-virtual.html) such as `__win` and `__unix`. Markers with no equivalent, including `platform_machine`, are dropped and the dependency is recorded unconditionally, so a channel install pulls dependencies pip would skip. Through the conversion path, conditions are not attached to the artifact at all: `extra == "..."` splits into per-extra tables and every other conditional dependency is omitted, producing a thinner graph than pip installs from the same wheel.
- **The wheel-channels feature is experimental.** [CEP 43](https://github.com/conda/ceps/blob/main/cep-0043.md) (conditional dependencies) and [CEP 44](https://github.com/conda/ceps/blob/main/cep-0044.md) (optional dependency groups) are accepted and supply the metadata vocabulary the translation depends on, but the [Conda Enhancement Proposal for wheel repodata](https://github.com/conda/ceps/pull/145) that defines the `v3.whl` section itself is still an open pull request. The on-disk format is not guaranteed to remain stable.
- **Security inherits PyPI's model.** The channel only republishes metadata; the wheel files themselves come from `pypi.org` without independent vetting. Supply-chain controls that apply to PyPI (trusted publishing, attestations, package signing) also apply here. Conda-forge's screened feedstock process does not.

## Related Approaches

- **[pixi](https://pydevtools.com/handbook/reference/pixi.md)** resolves conda-forge and PyPI packages together in a single lockfile, using uv internally for the PyPI side. The model is integrated from the start rather than bolted onto conda after the fact; pixi is the alternative to evaluate when both PyPI and compiled-wheel interop matter and a project-local workflow is acceptable. See [uv vs pixi vs conda for scientific Python](https://pydevtools.com/handbook/explanation/uv-vs-pixi-vs-conda-for-scientific-python.md).
- **The pip-last convention** is the long-standing workaround that conda-pypi replaces. Install every available dependency via conda first, run `pip install` once for the remainder, and avoid further conda operations in the environment. It still works and is appropriate for teams that cannot adopt beta tooling, but it leaves no record in the environment that pip was used.

## Pros

- Standard `conda install` resolves conda-forge and PyPI packages in one operation; no pip step, no separate tool.
- The conda solver tracks PyPI packages on the same footing as conda packages, eliminating the silent-drift failure mode of mixed environments.
- Hybrid resolution prefers conda-channel builds for dependencies, which keeps compiled libraries on a tested build path even when the requested package is PyPI-only.
- Editable installs work the same way as `pip install -e`, supporting standard development workflows.
- The conda-pypi channel covers approximately 600,000 pure Python wheels and is free for all use without Anaconda commercial-license obligations.

## Cons

- Beta software; not recommended for production by its maintainers.
- The channel is rebuilt in batches, so recent releases can be missing for weeks.
- Prereleases on the channel outrank stable conda-forge builds under the `flexible` channel priority the workflow requires.
- Pure Python wheels only; compiled extensions require conda-forge or another conda channel.
- Requires conda 26.5.3 plus the rattler solver, which is newer than many existing installations.
- Neither path reproduces a wheel's dependency conditions exactly. The channel over-installs where a marker has no conda equivalent; the conversion path drops conditions entirely.
- `EXTERNALLY-MANAGED` protection is not active during the beta, so nothing stops `pip install` from reintroducing the drift the plugin exists to prevent.
- Editable installs and custom index URLs are only available through `conda pypi install`, which is pending deprecation.
- `conda search` does not work against the channel, and extras cannot be requested from the command line.
- The wheel repodata format depends on a CEP still under discussion.
