diff mbox series

[RFC,1/4] classes/pypi: change PYPI_PACKAGE to be normalised

Message ID 20260928173158.781301-1-ross.burton@arm.com
State New
Headers show
Series [RFC,1/4] classes/pypi: change PYPI_PACKAGE to be normalised | expand

Commit Message

Ross Burton Sept. 28, 2026, 5:31 p.m. UTC
Currently the semantics of the PYPI_PACKAGE variable are slightly
unexpected, as it is the name of the source distribution (tarball), not
the logical package itself.

This meant many recipes had to set PYPI_PACKAGE and there were problems
when we wanted to use PyPI APIs that expect the normalised package name,
not whatever the sdist was called.

This commit changes the definition of PYPI_PACKAGE to be the normalised
package name[1], so PySocks would be pysocks and boolean.py would be
boolean-py.

This means we can use the correct package name in any PyPI API calls,
which all expect a normalised name.

Some uploaded tarballs do not have normalised names, so add a new variable
PYPI_PACKAGE_SDIST for the name of the sdist tarball.  This defaults to
the source distribution normalised[2] PYPI_PACKAGE, but can be overriden
if needed.

Whilst this causes some churn in recipes, it mostly lets recipes remove
lines.

[1] https://packaging.python.org/en/latest/specifications/name-normalization/
[2] https://packaging.python.org/en/latest/specifications/source-distribution-format/,

Signed-off-by: Ross Burton <ross.burton@arm.com>
---
 meta/classes-recipe/pypi.bbclass | 59 +++++++++++++++++---------------
 1 file changed, 32 insertions(+), 27 deletions(-)
diff mbox series

Patch

diff --git a/meta/classes-recipe/pypi.bbclass b/meta/classes-recipe/pypi.bbclass
index 08788fcb9a9..e45e3115305 100644
--- a/meta/classes-recipe/pypi.bbclass
+++ b/meta/classes-recipe/pypi.bbclass
@@ -7,28 +7,37 @@ 
 def pypi_default_package(d):
     """
     Return a reasonable guess for the PyPI package name by
-    stripping any python- prefix from PN.
+    stripping any python- prefix from PN and normalising it.
     """
     bpn = d.getVar('BPN')
     if bpn.startswith('python-'):
-        return bpn[7:]
+        return pypi_package_normalise(bpn[7:])
     elif bpn.startswith('python3-'):
-        return bpn[8:]
-    return bpn
+        return pypi_package_normalise(bpn[8:])
+    return pypi_package_normalise(bpn)
 
-# The PyPi package name (defaults to PN without the python3- prefix)
+# The PyPI package name, normalised as per
+# https://packaging.python.org/en/latest/specifications/name-normalization/.
+# Defaults to BPN without a python3- prefix.
 PYPI_PACKAGE ?= "${@pypi_default_package(d)}"
+
+# The name of the sdist. This defaults to the normalised name with underscores as per
+# https://packaging.python.org/en/latest/specifications/source-distribution-format/,
+# but can be overridden if needed.
+PYPI_PACKAGE_SDIST ?= "${@d.getVar("PYPI_PACKAGE").replace("-", "_")}"
+
 # The file extension of the source archive
 PYPI_PACKAGE_EXT ?= "tar.gz"
+
 # An optional prefix for the download file in the case of name collisions
 PYPI_ARCHIVE_NAME_PREFIX ?= ""
 
 def pypi_src_uri(d):
     """
-    Construct a source URL as per https://warehouse.pypa.io/api-reference/integration-guide.html#predictable-urls.
+    Construct a source URL as per https://docs.pypi.org/api/#predictable-urls.
     """
-    package = d.getVar('PYPI_PACKAGE')
-    archive_name = d.expand('${PYPI_PACKAGE}-${PV}.${PYPI_PACKAGE_EXT}')
+    package = d.getVar('PYPI_PACKAGE_SDIST')
+    archive_name = d.expand('${PYPI_PACKAGE_SDIST}-${PV}.${PYPI_PACKAGE_EXT}')
     url = 'https://files.pythonhosted.org/packages/source/%s/%s/%s' % (package[0], package, archive_name)
 
     download_prefix = d.getVar("PYPI_ARCHIVE_NAME_PREFIX")
@@ -37,34 +46,30 @@  def pypi_src_uri(d):
 
     return url
 
-def pypi_normalize(d):
-    """"
-    Normalize the package names to match PEP625 (https://peps.python.org/pep-0625/).
-    """
-    import re
-    return re.sub(r"[-_.]+", "-", d.getVar('PYPI_PACKAGE')).lower()
-
 PYPI_SRC_URI ?= "${@pypi_src_uri(d)}"
 
 HOMEPAGE ?= "https://pypi.python.org/pypi/${PYPI_PACKAGE}/"
 SECTION = "devel/python"
 SRC_URI:prepend = "${PYPI_SRC_URI} "
-S = "${UNPACKDIR}/${PYPI_PACKAGE}-${PV}"
+S = "${UNPACKDIR}/${PYPI_PACKAGE_SDIST}-${PV}"
 
-def pypi_normalize_regex(d):
-    # Use a regex wildcard instead of hyphen as the filenames
-    # may or may not have been normalised properly.
-    return pypi_normalize(d).replace("-", "[_-]")
-
-# Use the simple repository API rather than the potentially unstable project URL
-# More information on the pypi API specification is avaialble here:
+# More information on the PyPI API specification is available here:
 # https://packaging.python.org/en/latest/specifications/simple-repository-api/
 #
-# NOTE: All URLs for the simple API MUST request canonical normalized URLs per the spec
-UPSTREAM_CHECK_URI ?= "https://pypi.org/simple/${@pypi_normalize(d)}/"
-UPSTREAM_CHECK_REGEX ?= "(?i)${@pypi_normalize_regex(d)}-(?P<pver>(\d+(\.[\d\-]+)*(\.post\d+)?))\.(tar\.gz|tgz|zip|tar\.bz2)"
+# Use a case-insensitive regex wildcard instead of hyphen as the filenames may
+# or may not have been normalised.
+UPSTREAM_CHECK_URI ?= "https://pypi.org/simple/${PYPI_PACKAGE}/"
+UPSTREAM_CHECK_REGEX ?= "(?i)${@d.getVar("PYPI_PACKAGE").replace("-", "[_-]")}-(?P<pver>(\d+(\.[\d\-]+)*(\.post\d+)?))\.(tar\.gz|tgz|zip|tar\.bz2)"
 
 CVE_PRODUCT ?= "python:${PYPI_PACKAGE}"
 
 # Generate ecosystem-specific Package URL for SPDX
-SPDX_PACKAGE_URLS =+ "pkg:pypi/${@pypi_normalize(d)}@${PV} "
+SPDX_PACKAGE_URLS =+ "pkg:pypi/${PYPI_PACKAGE}@${PV} "
+
+def pypi_package_normalise(s):
+    """
+    Normalise the passed package name as per
+    https://packaging.python.org/en/latest/specifications/name-normalization/
+    """
+    import re
+    return re.sub(r"[-_.]+", "-", s).lower()