pypi — helper eclass for PyPI archives

The pypi eclass is a small eclass to facilitate fetching sources from PyPI. It abstracts away the complexity of PyPI URLs, and makes it easier to adapt SRC_URI to their future changes.

Please note that PyPI archives are not always the best choice for distfiles. In particular, they frequently are missing tests and other files important to Gentoo packaging. Should that be the case, other archives should be used. Read the Source archives section for more information.

Eclass reference: pypi.eclass(5)

PyPI URLs

Modern and legacy URLs

The modern form of PyPI URLs include a hash of the distribution file, e.g.:

https://files.pythonhosted.org/packages/20/2e/36e46173a288c1c40853ffdb712c67e0e022df0e1ce50b7b1b50066b74d4/gpep517-13.tar.gz

This makes them inconvenient for use in ebuilds, as the hash would have to be updated manually on every version bump. For this reason, Gentoo settled on using the legacy form of URLs instead, e.g.:

https://files.pythonhosted.org/packages/source/g/gpep517/gpep517-13.tar.gz

It should be noted that legacy URLs are no longer supported and may stop working at an arbitrary time. Using the eclass is going to make the migration to an alternative provider easier than inline URLs.

The path part of a legacy URL for a source package has the general form of:

/packages/source/${project::1}/${project}/${filename}

whereas the path for a binary package (wheel) is:

/packages/${pytag}/${project::1}/${project}/${filename}

In both cases, ${project} refers to the PyPI project name. Technically, PyPI accepts any name that matches the PyPI project name after package name normalization. However, this behavior is not guaranteed and using the canonical project name is recommended.

The filenames and ${pytag} are described in the subsequent sections.

Source distribution filenames

The filename of a source distribution (sdist) has the general form of:

${name}-${version}.tar.gz

where ${name} is the project name normalized according to PEP 625 and ${version} is the version following PEP 440. The project name normalization transforms all uppercase letters to lowercase, and replaces all contiguous runs of ._- characters with a single underscore.

For example, package Flask-BabelEx version 1.2.3 would use the following filename:

flask_babelex-1.2.3.tar.gz

However, note that source distributions predating PEP 625 are still commonly found, and they use non-normalized project names, e.g.:

Flask-BabelEx-1.2.3.tar.gz

In both instances, the top directory inside the archive has the same name as the archive filename, minus .tar.gz suffix. Historically, .zip distributions were used as well.

Binary distribution filenames

The filename of a binary distribution (wheel) has the general form of:

${name}-${version}-${pytag}-${abitag}-${platformtag}.whl

Similarly to source distributions, ${name} and ${version} specify the normalized project name and version (normalization follows the same rules and is specified in binary distribution format specification).

${pytag} is the tag specifying Python version that the wheel was built for. This can be py2.py3 or py3 for pure Python wheels, or e.g. cp39 for CPython 3.9 wheels. ${abitag} specifies the appropriate ABI (none for pure Python wheels), ${platformtag} the platform (any for pure Python wheels).

For example, the modern wheel for the aforementioned package would be named:

flask_babelex-1.2.3-py3-none-any.whl

However, some distributions use older normalization rules specified in PEP 427. In this case, runs of characters other than alphanumeric characters and dots ([^\w\d.]+) are replaced by a single underscore. Notably, this means that uppercase letters and dots are left in place. In this normalization, the example wheel is named:

Flask_BabelEx-1.2.3-py3-none-any.whl

Packages with matching name and version

In the most common case, the upstream package will have exactly the same name as the Gentoo package, and the version numbers will be entirely compatible. In this case, it is sufficient to inherit the eclass, and it will automatically set a suitable default SRC_URI. The result will be roughly equivalent to:

SRC_URI="
    https://files.pythonhosted.org/packages/source/${PN::1}/${PN}/${P}.tar.gz
"
S=${WORKDIR}/${P}

with ${P} actually using the normalized project name and the Gentoo version being translated to its PyPI equivalent. The version translation performs the following replacements:

Gentoo suffix

PyPI suffix

_alpha

a

_beta

b

_rc

rc

_p

.post

If the project in question uses a build system that is not compliant with PEP 625 and has uppercase letters or dots in its name, you may need to set PYPI_NO_NORMALIZE to a non-empty value to disable name normalization, e.g.:

PYPI_NO_NORMALIZE=1

inherit distutils-r1 pypi

Note that SRC_URI is not combined between eclasses and ebuilds. Should you need to fetch additional files, you need to explicitly append to the variable or the default will be overwritten, e.g.:

inherit distutils-r1 pypi

SRC_URI+="
    https://github.com/pytest-dev/execnet/commit/c0459b92bc4a42b08281e69b8802d24c5d3415d4.patch
        -> ${P}-pytest-7.2.patch
"

Package with a different name

If the project name used on PyPI differs from the Gentoo package name, the PYPI_PN variable can be used to use another name. This is especially useful for project that use uppercase letters or dots in their names.

For example, a package using lowercase package name in Gentoo and title case on PyPI could use:

PYPI_PN=${PN^}

inherit distutils-r1 pypi

Note that projects whose distfiles do not conform to PEP 625 will also need to explicitly disable filename normalization. For example, a package using dot in its filename and setuptools would use:

PYPI_NO_NORMALIZE=1
PYPI_PN=${PN/-/.}

inherit distutils-r1 pypi

Customizing the generated URL

The default value may not be suitable for your package if it uses a different project name than the Gentoo package name, a version number that needs to be translated differently or the legacy .zip sdist format. The pypi_sdist_url function can be used to generate URLs in that case. Its usage is:

pypi_sdist_url [--no-normalize] [<project> [<version> [<suffix>]]]

with package defaulting to ${PYPI_PN}, version to translated ${PV} and suffix to .tar.gz. The generated filename uses PEP 625 normalization, unless --no-normalize is provided (PYPI_NO_NORMALIZE does not affect explicit function calls). For example, the Gentoo dev-python/markups package uses title-case Markups project name and legacy filename, and so the ebuild needs to use:

inherit distutils-r1 pypi

SRC_URI="$(pypi_sdist_url --no-normalize "${PN^}")"
S=${WORKDIR}/${P^}

Should the package start using source distributions with normalized filenames, then only project name would need to be overriden and the default S would be correct (Markups and markups normalize the same):

inherit distutils-r1 pypi

SRC_URI="$(pypi_sdist_url "${PN^}")"

Note that due to project name normalization, the ebuild would also work without SRC_URI override. However, it is recommended to pass the canonical project name, as normalization is not guaranteed.

Fetching wheels

In very specific cases, it may be necessary to fetch wheels (i.e. prebuilt Python packages) instead. The pypi_wheel_url function is provided to aid this purpose. Its usage is:

pypi_wheel_url [--unpack] [<project> [<version> [<python-tag> [<abi-platform-tag>]]]]

with package defaulting to ${PYPI_PN}, version to translated ${PV}, python-tag to py3 and abi-platform-tag to none-any (i.e. indicating a pure Python package). For example, dev-python/ensurepip-setuptools does:

inherit pypi
SRC_URI="$(pypi_wheel_url "${PN#ensurepip-}")"

Note that wheels are ZIP archives suffixed .whl, and they are not unpacked by the package manager automatically. Should you need them unpacked, you can pass --unpack option to include a -> operator that renames the wheel to use .whl.zip suffix, causing it to be unpacked. Remember to add an explicit dependency on app-arch/unzip in that case.

The pypi_wheel_filename function is provided to aid getting the wheel filename. It has a matching synopsis:

pypi_wheel_filename [<project> [<version> [<python-tag> [<abi-platform-tag>]]]]