PEP: 794
Title: Import Name Metadata
Author: Brett Cannon <brett@python.org>
Discussions-To: https://discuss.python.org/t/94567
Status: Accepted
Type: Standards Track
Topic: Packaging
Created: 05-Jun-2025
Post-History: `02-May-2025 <https://discuss.python.org/t/90506>`__,
              `05-Jun-2025 <https://discuss.python.org/t/94567>`__
Resolution: `05-Sep-2025 <https://discuss.python.org/t/94567/85>`__

.. canonical-pypa-spec:: :ref:`core-metadata`

Abstract
========

This PEP proposes extending the core metadata specification for Python
packaging to include two new, repeatable fields named ``Import-Name`` and
``Import-Namespace`` to record the import names that a project provides once
installed. New keys named ``import-names`` and ``import-namespaces`` will be
added to the ``[project]`` table in :file:`pyproject.toml` for providing the
values for the new core metadata fields. This also leads to the introduction of
core metadata version 2.5.


Motivation
==========

In Python packaging there is no requirement that a project name match the
name(s) that you can import for that project. As such, there is no clean,
easy, accurate way to go from import name to project name and vice versa.
This can make it difficult for tools that try to help people in discovering
the right project to install when they know the import name or knowing what
import names a project will provide once installed.

As an example, a code editor may detect a user has an unsatisfied import in a
selected virtual environment. But with no way to reliably know what import
names various projects provide, the code editor cannot accurately
provide a user with a list of potential projects to install to satisfy that
import requirement (e.g. it is not obvious that ``import PIL`` very likely
implies the user wants the `Pillow project
<https://pypi.org/project/pillow/>`__ installed). This also applies to when a
user vaguely remembers the project name but does not remember the import
name(s) and would have their memory jogged when seeing a list of import names
a package provides. Finally, tools would be able to notify users what import
names will become available once they install a project.

There is also no easy way to know whether installing two projects will conflict
with one another based on the import names they provide. For instance, if two
different projects have a ``_utils`` module, installing both projects will lead
to a clash as one project's ``_utils`` module would take precedence over the
other's, by overwriting the other project's file; this issue
has been `seen in the wild <https://github.com/astral-sh/uv/pull/13437>`__.

It may also help with spam detection. If a project specifies the same import
names as a very popular project it can act as a signal to take a closer look
at the validity of the less popular project. A project found to be lying
about what import names it provides would be another signal.


Rationale
=========

This PEP proposes extending the packaging :ref:`packaging:core-metadata` so
that project owners can specify the highest-level import names that a project
provides if installed on some platform.

Putting this metadata in the core metadata means the data is (potentially)
served by an index server, independent of any sdist or wheel. That negates
needing to come up with a way to expose the metadata to tools to avoid
having to download an entire e.g. wheel.

Having this metadata be the same across all release artifacts would allow for
projects to only have to check a single file's core metadata to get all
possible import names instead of checking all the released files. This also
means one does not need to worry if a file is missing when reading the core
metadata or one can work solely from an sdist if the metadata is provided. As
well, it simplifies having ``project.import-names`` and
``project.import-namespaces`` keys in :file:`pyproject.toml` by having it be
consistent for the entire project version and not unique per released file for
the same version.

A distribution file containing modules and packages can have any combination of
public and private APIs at the module/package level. Distribution files can also
contain no modules or packages of any kind. Being able to distinguish between
the situations all have various tool uses that could be beneficial to users. For
instance, knowing all import names regardless of whether they are public or
private helps detect clashes at install time. But knowing what is explicitly
public or private allows tools such as editors to not suggest private import
names as part of auto-complete.

This PEP is not overly strict on what to (not) list in the proposed metadata on
purpose. Having build back-ends verify that a project is accurately following
a specification that is somehow strict about what can be listed would be nearly
impossible to get right due to how flexible Python's import system is. As such,
this PEP only requires that valid import names be used and that projects don't
lie (and it is acknowledged the latter requirement cannot be validated
programmatically). Projects do, though, need to account for all levels of the
names they list (e.g. you can't list ``a.b.c`` and not account for ``a`` and
``a.b``).

Various other attempts have been made to solve this, but they all have to
make trade-offs. For instance, one could download every wheel for
every project release and look at what files are provided via the
:ref:`packaging:binary-distribution-format`, but that's a lot of CPU and
bandwidth for something that is static information (although tricks can be
used to lessen the data requests such as using HTTP range requests to only
read the table of contents of the zip file). This sort of calculation is also
currently repeated by everyone independently instead of having the metadata
hosted by a central index server like PyPI. It also doesn't work for sdists
as the structure of the wheel isn't known yet, and so inferring the structure
of the code installed isn't possible. As well, these solutions are not
necessarily accurate as they are based on inference instead of being explicitly
provided by the project owners. All of these accuracy issues affect even having
an index host the information to avoid the compute costs of gathering it.


Specification
=============

Because this PEP introduces a new field to the core metadata, it bumps the
latest core metadata version to 2.5.

The ``Import-Name`` and ``Import-Namespace`` fields are "multiple uses" fields.
Each entry of both fields MUST be a valid import name or can be empty in the
case of ``Import-Name``. Any names specified MUST be importable when the project
is installed on *some* platform for the same version of the project (e.g. the
metadata MUST be consistent across all sdists and wheels for a project release).
This does imply that the information isn't specific to the distribution artifact
it is found in, but to the release version the distribution artifact belongs to.

An import name MAY be followed by a semicolon and the term "private" (e.g.
``; private``). This signals to tools that the import name is not part of the
public API for the project. Any number of spaces surrounding the ``;`` is
allowed.

``Import-Name`` lists import names which a project, when installed, would
*exclusively* provide (i.e. if two projects were installed with the same import
names listed in ``Import-Name``, then one of the projects would shadow the
name for the other). ``Import-Namespace`` lists import names that, when
installed, would be provided by the project, but not exclusively (i.e.
projects all listing the same import name in ``Import-Namespace`` being
installed together would not shadow those shared names).

The :ref:`declaring-project-metadata` will gain an ``import-names`` key. It
will be an array of strings that stores what will be written out to
``Import-Name``. Build back-ends MAY support dynamically calculating the
value on the user's behalf if desired, if the user declares the key in
``project.dynamic``. The same applies to ``import-namespaces`` for
``Import-Namespace``.

Projects SHOULD list all the shortest import names that are exclusively provided
by a project which would cover all import name scenarios. If any of the shortest
names are dotted names, all intervening names from that name to the top-level
name should also be listed appropriately in ``Import-Namespace`` and/or
``Import-Name``. For instance, a project which is a single package named
``spam`` with multiple submodules would only list
``project.import-names = ["spam"]``. A project that lists ``spam.bacon.eggs``
would also need to account for ``spam`` and ``spam.bacon`` appropriately in
``import-names`` and ``import-namespaces``. Listing all names acts as a check
that the intent of the import names is as expected. As well, projects SHOULD
list all import names, public or private, using the ``; private`` modifier
as appropriate.

If a project lists the same name in both ``Import-Name`` and
``Import-Namespace``, then tools MUST raise an error due to ambiguity; this also
applies to ``import-names`` and ``import-namespaces``, respectively.

Tools SHOULD raise an error when two projects that are about to be installed by
a tool list names that overlap in each other's ``Import-Name`` entries (i.e.
installed in the same command/action). This is to avoid projects unexpectedly
shadowing another project's code. The same applies to when a project has an
entry in ``Import-Name`` that overlaps with another project's
``Import-Namespace`` entries. This does not apply to overlapping
``Import-Namespace`` entries as that's the purpose of namespace packages. Tools
MAY warn or raise an error when installing a project into a preexisting
environment where there is import name overlap with a project that is already
installed. This is a "MAY" and not a "SHOULD" due to some users purposefully
overwriting import names when installation is done in multiple steps (e.g.
using different installers with the same environment).

Projects MAY set ``import-names`` an empty array and not set
``import-namespaces`` at all in a :file:`pyproject.toml` file (e.g.
``import-names = []``). To match this, projects MAY have an empty
``Import-Name`` field in their metadata. This represents a project with NO
import names, public or private (i.e. there are no Python modules of any kind
in the distribution file).

Since projects MAY have no ``Import-Name`` metadata (either because the project
uses an older metadata version, or because it didn't specify any), then tools
have no information about what names the project provides. However, in practice
the majority of projects have their project name match what their import name
would be. As such, it is a reasonable assumption to make that a project name
that is normalized in some way to an import name (e.g.
``packaging.utils.canonicalize_name(name, validate=True).replace("-", "_")``)
can be used if some answer is needed.

Projects MAY set ``import-names`` or ``import-namespaces`` -- as well as
``Import-Name`` or ``Import-Namespace``, respectively -- to an import name that
matches the project name (normalized or not) to explicitly declare that the
project's name is also the import name.



Examples
--------

For `scikit-learn 1.7.0
<https://pypi-browser.org/package/scikit-learn/scikit_learn-1.7.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl>`__:

.. code-block:: TOML

    [project]
    import-names = ["sklearn"]

For `pytest 8.3.5
<https://pypi-browser.org/package/pytest/pytest-8.3.5-py3-none-any.whl>`__
there would be 3 expected entries:

.. code-block:: TOML

    [project]
    # The pytest docs list code out of all of these modules, so it isn't
    # obvious whether they would mark any as private.
    import-names = ["_pytest", "py", "pytest"]


For `azure-mgmt-search 9.1.0
<https://pypi-browser.org/package/azure-mgmt-search/azure_mgmt_search-9.1.0-py3-none-any.whl>`__,
there should be two namespace entries and one name entry for
``azure.mgmt.search``:

.. code-block:: TOML

    [project]
    import-names = ["azure.mgmt.search"]
    import-namespaces = ["azure", "azure.mgmt"]


Backwards Compatibility
=======================

As this is a new field for the core metadata and a new core metadata version,
there should be no backwards compatibility concerns.


Security Implications
=====================

Tools should treat the metadata as potentially inaccurate. As such, any
decisions made based on the provided metadata should be assumed to be
malicious in some way.


How to Teach This
=================

Project owners should be taught that they can now record what names their
projects provide for importing. If their project name matches the module or
package name their project provides they don't have to do anything. If there is
a difference, though, they should record all the import names their project
provides, using the shortest names possible. If any of the names are implicit
namespaces, those go into ``project.import-namespaces`` in
:file:`pyproject.toml`, otherwise the name goes into ``project.import-names``.

Users of projects don't necessarily need to know about this new metadata.
While they may be exposed to it via tooling, the details of where that data
came from isn't critical. It's possible they may come across it if an index
server exposes it (e.g., lists the values from ``Import-Name`` and marks
whether the file structure backs up the claims the metadata makes), but that
still wouldn't require users to know the technical details of this PEP.


Reference Implementation
========================

https://github.com/brettcannon/packaging/tree/pep-794 is a branch to update
'packaging' to support this PEP.


Rejected Ideas
==============

Infer the value for ``Import-Namespace``
----------------------------------------

A previous version of this PEP inferred what would have been the values for
``Import-Namespace`` based on dotted names in ``Import-Name``. It was decided
that it would be better to be explicit not only to avoid mistakes by accidentally
listing something that would be interpreted as an implicit namespace, but it
also made the data more self-documenting.


Require that names listed in ``Import-Namespace`` never be contained by a name in ``Import-Name``
-------------------------------------------------------------------------------------------------

The way Python's import system works by default means that it isn't possible to
have an import name contain a namespace. But Python's import system is flexible
enough that user code could make that possible. As such, the requirement that
tools error out if an import name contained a namespace name --
``import-names = ["spam"]`` and ``import-namespaces = ["spam.bacon"]`` -- was
removed.


Re-purpose the ``Provides`` field
----------------------------------

Introduced in metadata version 1.1 and deprecated in 1.2, the ``Provides``
field was meant to provide similar information, except for **all** names
provided by a project instead of the distinguishing namespaces as this PEP
proposes. Based on that difference and the fact that ``Provides`` is
deprecated and thus could be ignored by preexisting code, the decision was
made to go with a new field.


Name the field ``Namespace``
----------------------------

While the term "namespace" is technically accurate from an import
perspective, it could be confused with implicit namespace packages.


Serving the ``RECORD`` file
---------------------------

During `discussions about a pre-PEP version
<https://discuss.python.org/t/90506/>`__ of this
PEP, it was suggested that the ``RECORD`` file from wheels be served from
index servers instead of this new metadata. That would have the benefit of
being implementable immediately. But in order to provide the equivalent
information, inference would be necessary based on the file structure of
what would be installed by the wheel. That could lead to inaccurate
information. It also doesn't support sdists.

In the end a `poll
<https://discuss.python.org/t/90506/46>`__ was
held and the approach this PEP takes won out.


Be more prescriptive in what projects specify
---------------------------------------------

An earlier version of this PEP was much more strict in what could be put into
``Import-Name``. This included turning some "SHOULD" guidelines into "MUST"
requirements and being specific about how to calculate what a project "owned".
In the end it was decided that was too restrictive and risked being implemented
incorrectly or the spec being unexpectedly too strict.

Since the metadata was never expected to be exhaustive as it can't be verified
to be, the looser spec that is currently in this PEP was chosen instead.


Open Issues
===========

N/A


Acknowledgments
===============

Thanks to HeeJae Chang for ~~complaining about~~ regularly bringing up the
usefulness that this metadata would provide. Thanks to Josh Cannon (no
relation) for reviewing drafts of this PEP and providing feedback. Also,
thanks to everyone who participated in a `previous discussion
<https://discuss.python.org/t/29494>`__
on this topic.


Copyright
=========

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.
