Source code for conda_pypi.name_mapping

"""PyPI ↔ conda package name mapping.

The default table is ``grayskull_pypi_mapping.json`` (regro/grayskull). Keys are
canonical PyPI names, resolved the same way as the table via
:func:`packaging.utils.canonicalize_name`.

When a name is missing from the table, the conda name is derived from the
original string: lowercase, underscores become hyphens, dots are unchanged.
That often matches conda-forge dotted packages; canonical form alone would map
``jaraco.tidelift`` to ``jaraco-tidelift``.

Grayskull (or a custom dict for :func:`pypi_to_conda_name`) is still required
when the conda package name does not follow that rule, for example
``typing-extensions`` → ``typing_extensions``.
"""

from __future__ import annotations

import json
import pkgutil

from packaging.utils import canonicalize_name

grayskull_pypi_mapping: dict[str, dict] = json.loads(
    pkgutil.get_data("conda_pypi", "grayskull_pypi_mapping.json") or "{}"
)

default_pypi_mapping: dict[str, dict] = dict(grayskull_pypi_mapping)

_to_pypi_name_map: dict[str, dict] = {}


def _unmapped_conda_name(pypi_name: str) -> str:
    return pypi_name.strip().lower().replace("_", "-")


[docs] def pypi_to_conda_name(pypi_name: str, pypi_to_conda_name_mapping: dict | None = None) -> str: raw = pypi_name.strip() key = canonicalize_name(raw) table = ( pypi_to_conda_name_mapping if pypi_to_conda_name_mapping is not None else default_pypi_mapping ) entry = table.get(key) if entry is not None: return entry["conda_name"] return _unmapped_conda_name(raw)
[docs] def conda_to_pypi_name(name: str) -> str: if not _to_pypi_name_map: for value in default_pypi_mapping.values(): conda_name = value["conda_name"] # XXX sometimes conda:pypi is n:1 _to_pypi_name_map[conda_name] = value found = _to_pypi_name_map.get(name) if found: name = found["pypi_name"] return canonicalize_name(name)