From 7711db1fbeb8361a3bb2fb2b741a790289a1d146 Mon Sep 17 00:00:00 2001 From: Emma Smith Date: Tue, 9 Dec 2025 13:37:50 -0800 Subject: [PATCH 01/12] PEP 9999: JSON Package Metadata This is the first revision of a PEP to change package metadata to JSON format. --- peps/pep-9999.rst | 297 ++++++++++++++++++ .../appendix-core-metadata-json-schema.rst | 11 + peps/pep-9999/core-metadata.schema.json | 240 ++++++++++++++ 3 files changed, 548 insertions(+) create mode 100644 peps/pep-9999.rst create mode 100644 peps/pep-9999/appendix-core-metadata-json-schema.rst create mode 100644 peps/pep-9999/core-metadata.schema.json diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst new file mode 100644 index 00000000000..97da2694876 --- /dev/null +++ b/peps/pep-9999.rst @@ -0,0 +1,297 @@ +PEP: 9999 +Title: JSON Package Metadata +Author: Emma Harper Smith +PEP-Delegate: Paul Moore +Discussions-To: Pending +Status: Draft +Type: Standards Track +Topic: Packaging +Created: 2025-12-09 +Post-History: Pending + + +Abstract +======== + +Python package metadata ("core metadata") was first defined in :pep:`241` to +use :rfc:`822` email headers to encode information about packages. This was +reasonable at the time; email messages were the only widely used, standardized +text format that had a parser in the standard library at the time. However, +issues with handling different encodings, differing handling of line breaks, +and other differences between implementations have caused numerous packaging +bugs. To resolve these issues, this PEP proposes introducing a +`Javascript Object Notation (JSON) `_ +encoded file containing core metadata in Python packages. + + +Motivation +========== + +The email message format has a number of complexities and limitations which +reduce its utility as a portable textual interchange format for packaging +metadata. Due to the :mod:`email` parser requiring configuration changes to +properly generate valid core metadata, many projects do not use the +:mod:`!email` module and instead generate core metadata in a custom manner. +There are many pitfalls with generating email headers that these custom +generators can hit. First, core metadata fields may contain newlines in the +value of fields. These newlines must be handled properly to "unfolded" multiple +lines per :rfc:`822`. Improperly escaped newlines can lead to generating +invalid core metadata. Second, as discussed in the core metadata +specifications: + +.. epigraph:: + The standard file format for metadata (including in wheels and installed + projects) is based on the format of email headers. However, email formats + have been revised several times, and exactly which email RFC applies to + packaging metadata is not specified. In the absence of a precise + definition, the practical standard is set by what the standard library + :mod:`email.parser` module can parse using the + :attr:`email.policy.compat32` policy. + +Since no specific email RFC is selected, the current core metadata +specification is ambiguous whether a given core metadata document is valid. +:rfc:`822` is the only email standard to be explicitly listed in a PEP. +However, the core metadata specifications also requires that core metadata is +encoded using UTF-8 when written to a file. This de-facto makes the core +metadata follow :rfc:`6532`, which specifies internationalization of email +headers. This has practical interoperability concerns. Until a few years ago, +it was unspecified how to handle non-ASCII encoded content in core metadata, +causing confusion about how to properly encode non-ASCII emails in core +metadata. Third, the current format is difficult to properly validate and +parse. Many tools do not check for issues with the output of the :mod:`!email` +parser. If a document is malformed, it may still parse without error by the +:mod:`!email` module as a valid email message. Furthermore, due to limitations +in the email format, fields like ``Project-Url`` must create custom encodings +of nested key-value items, further complicating parsing. Finally, the lack of +a schema makes it difficult to validate the contents of email message encoded +metadata. While introducing a specification for the current format has been +`discussed previously `_, +no progress had been made, and converting to JSON was a suggested resolution +to the issues raised. + + +Rationale +========= + +Introducing a new core metadata file with a well-specified format will greatly +ease generating, parsing, and validating metadata. JSON is a natural choice for +storing package core metadata. It is easily machine readable and writable, is +understandable to humans, and is well supported across many languages. +Furthermore, :pep:`566` already specifies a canonicalization of email formatted +core metadata to JSON. JSON is also a frequently used format for data +interchange on the web. For discussion of other formats considered, please +refer to the rejected ideas section. + +To maintain backwards compatibility, the JSON metadata file MUST be generated +alongside the existing email formatted metadata file. This ensures that tools +that do not support the new format can still read package metadata for new +packages. + +The JSON formatted metadata file must be semantically equivalent to the email +encoded file. This ensures that the metadata is unambiguous between the two +formats, and tools may read either when both are present. To maintain +performance, this equivalence is not required to be verified by installers, +though other tools may do so. Some tools may choose to make the check dependent +on a configuration flag. + +Package indexes SHOULD check that the metadata files are semantically +equivalent when the package is added to the index. This is a low-cost, one-time +check that ensures users of the index are served valid packages. + + +Specification +============= + +JSON Format Core Metadata File +------------------------------ + +A new optional file ``METADATA.json`` shall be introduced as a metadata file +for Python packages. If generated, the ``METADATA.json`` file MUST be placed in +the same directory as the current email formatted ``METADATA`` or ``PKG-INFO`` +file. + +For wheels, this means that ``METADATA.json`` MUST be located in the +``.dist-info`` directory. The wheel format minor version will be incremented to +indicate the change in the format. + +For source distribution packages, the ``METADATA.json`` file MUST be located +in the root directory of the project sources. Tools that prefer the JSON +formatted metadata file MUST check for the existence of a ``METADATA.json`` +in the source distribution before reading the file. + +The semantic contents of the ``METADATA`` and ``METADATA.json`` files MUST be +equivalent if ``METADATA.json`` is present. Installers MAY verify this +information. Public package indexes SHOULD verify the files are semantically +equivalent. + +Conversion to JSON Encoding +--------------------------- + +Conversion from the current email format for core metadata to JSON should +follow the process described in :pep:`566`, with the following modification: +the ``Project-URL`` entries should be converted into an object with keys +containing the labels and values containing the URLs from the original email +value. The overall process thus becomes: + +#. The original key-value format should be read with + ``email.parser.HeaderParser``; +#. All transformed keys should be reduced to lower case. Hyphens should be + replaced with underscores, but otherwise should retain all other characters; +#. The transformed value for any field marked with "(Multiple-use") should be a + single list containing all the original values for the given key; +#. The ``Keywords`` field should be converted to a list by splitting the + original value on commas; +#. The ``Project-URL`` field should be converted into a JSON object with keys + containing the labels and values containing the URLs from the original email + value. +#. The message body, if present, should be set to the value of the + ``description`` key. +#. The result should be stored as a string-keyed dictionary. + +One edge case in the above conversion is that the ``Project-URL`` label is +"free text, with a maximum length of 32 characters." This presents a problem +when trying to decode the label. Therefore this PEP sets the requirement that +the ``Project-URL`` label be any text *except* the comma (``,``) character. +This allows for unambiguous parsing of the ``Project-URL`` entries by splitting +the text on the left-most comma (``,``) character. + +JSON Schema for Core Metadata +----------------------------- + +To enable verification of JSON encoded core metadata, a +`JSON schema `_ for core metadata has been produced. +This schema will be updated with each revision to the core metadata +specification. The schema is available in +:ref:`9999-core-metadata-json-schema`. + +TODO: where should the schema be served/what should the $id be? + +Serving METADATA.json in the Simple Repository API +-------------------------------------------------- + +:pep:`658` introduced a means of serving package metadata in the Simple +Repository API. The JSON encoded version of the package metadata may also be +served, via the following modifications to the Simple Repository API: + +A new attribute ``data-dist-info-metadata-json`` may be added to anchor tags +in the Simple API. This attribute should have a value containing the hash +information for the ``METADATA.json`` file in the same format as +``data-dist-info-metadata``. If ``data-dist-info-metadata-json`` is present, +the repository MUST serve the JSON encoded metadata file at the +distribution's path with ``.metadata.json`` appended to it. For example, if a +distribution is served at ``/simple/foo-1.0-py3-none-any.whl``, the JSON +encoded core metadata file MUST be served at +``/simple/foo-1.0-py3-none-any.whl.metadata.json``. + +Deprecation of the ``METADATA`` and ``PKG-INFO`` Files +------------------------------------------------------ + +The ``METADATA`` and ``PKG-INFO`` files are now deprecated. This means that a +future PEP may make the ``METADATA`` and ``PKG-INFO`` files optional and +require ``METADATA.json`` to be present. Please see the next section for +caveats to that change. + +Despite the ``METADATA`` and ``PKG-INFO`` files being deprecated, new core +metadata revisions should be implemented for both JSON and email to ensure that +they may remain semantically equivalent. + +Backwards Compatibility +======================= + +The specification for ``METADATA.json`` is designed such that the new format is +completely backwards compatible. Existing tools may read metadata from the +existing email formatted files, and new tools may take advantage of the new +format. + +A future major revision of the wheel specification may make the ``METADATA`` +and ``PKG-INFO`` files optional and make the ``METADATA.json`` file required. +Note that tools will need to maintain parsing of email metadata indefinitely to +support parsing metadata for old packages which only have the ``METADATA`` or +``PKG-INFO`` files. + + +Security Implications +===================== + +One attack vector with JSON encoded core metadata is if the JSON payload is +designed to consume excessive memory or CPU resources in a denial of service +attack. While this attack is not likely to affect users whom can cancel +resource-intensive operations, it may be an issue for package indexes. + +There are several mitigations that can be made to prevent this: + +#. The length of the JSON payload can be restricted to a reasonable size. +#. The reader may use a :class:`~json.JSONDecoder` to omit parsing :class:`int` + and :class:`float` values to avoid quadratic number parsing time complexity + attacks. +#. I plan to contribute a change to the :class:`~json.JSONDecoder` in Python + 3.15+ that will allow it to be configured to restrict the nesting of JSON + payloads to a reasonable depth. + +With these mitigations in place, concerns about denial of service attacks with +JSON encoded core metadata are minimal. + + +Reference Implementation +======================== + +A reference implementation of the JSON schema for JSON core metadata is +available in :ref:`9999-core-metadata-json-schema`. + +Furthermore, a reference implementation in the ``packaging`` library `is +available +`__. + + +Rejected Ideas +============== + +Using Another File Format (TOML, YAML, etc.) +-------------------------------------------- + +While TOML or another format could be used for the new core metadata file +format, JSON has been chosen for a few reasons: + +#. Core metadata is mostly meant as a machine interchange format to be used by + tools and services which wish to interoperate. Therefore the + human-readability of TOML is not an important consideration in this + selection. +#. JSON parsers are implemented in many languages' standard libraries and the + :mod:`json` module has been part of Python's standard library for a very + long time. +#. JSON is fast to parse and emit. +#. JSON schemas are JSON native and commonly used. + + +Open Issues +=========== + +Where Should the JSON Schema be Served? +--------------------------------------- + +Where should the standard JSON Schema be served? Some options would be +packaging.python.org, pypi.org, python.org, or pypa.org. + +My first choice would be packaging.python.org, but I am open to other options. + +Should we also update the ``WHEEL`` metadata file format to be JSON encoded? +---------------------------------------------------------------------------- + +The ``WHEEL`` metadata file format is also an email formatted file. This means +that it is subject to the same parsing and validation issues as the +``METADATA`` and ``PKG-INFO`` files. However, the ``WHEEL`` file is part of the +initial wheel format version check done by installers. Changing the file format +might harm backwards compatibility by making old installers unable to read new +metadata. + +I think it could make sense to introduce a ``WHEEL.json`` file. Then a future +wheel major version could remove the ``WHEEL`` file and require the +``WHEEL.json`` file instead. + + +Copyright +========= + +This document is placed in the public domain or under the +CC0-1.0-Universal license, whichever is more permissive. + diff --git a/peps/pep-9999/appendix-core-metadata-json-schema.rst b/peps/pep-9999/appendix-core-metadata-json-schema.rst new file mode 100644 index 00000000000..71c7c67217e --- /dev/null +++ b/peps/pep-9999/appendix-core-metadata-json-schema.rst @@ -0,0 +1,11 @@ +:orphan: + +.. _9999-core-metadata-json-schema: + +Appendix: JSON Schema for Core Metadata +======================================= + +.. literalinclude:: core-metadata.schema.json + :language: json + :linenos: + :name: core-metadata-schema \ No newline at end of file diff --git a/peps/pep-9999/core-metadata.schema.json b/peps/pep-9999/core-metadata.schema.json new file mode 100644 index 00000000000..f467748b760 --- /dev/null +++ b/peps/pep-9999/core-metadata.schema.json @@ -0,0 +1,240 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://peps.python.org/pep-9999/core-metadata.schema.json", + "title": "Python Packaging Core Metadata", + "description": "Core metadata for Python packages", + "type": "object", + "properties": { + "metadata_version": { + "type": "string", + "pattern": "^(\\d+(\\.\\d+)*)$", + "description": "The version of the file format." + }, + "name": { + "type": "string", + "pattern": "^([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9._-]*[A-Za-z0-9])$", + "description": "The name of the distribution." + }, + "version": { + "type": "string", + "pattern": "^v?([0-9]+!)?[0-9]+(\\.[0-9]+)*([-_\\.]?(alpha|a|beta|b|preview|pre|c|rc)[-_\\.]?[0-9]+)?((-[0-9]+)|([-_\\.]?(post|rev|r)[-_\\.]?[0-9]+))?([-_\\.]?dev[-_\\.]?[0-9]+)?$", + "description": "The distribution's version number." + }, + "dynamic": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "platform", + "supported_platform", + "summary", + "description", + "description_content_type", + "keywords", + "author", + "author_email", + "maintainer", + "maintainer_email", + "license", + "license_expression", + "license_file", + "classifier", + "requires_dist", + "requires_python", + "requires_external", + "project_url", + "provides_extra", + "import_name", + "import_namespace", + "provides_dist", + "obsoletes_dist", + "home_page", + "download_url", + "requires", + "provides", + "obsoletes" + ] + }, + "description": "A list of core metadata fields that are dynamicly calculated." + }, + "platform": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The platforms supported by the distribution." + }, + "supported_platform": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The platforms for which a binary distribution was compiled." + }, + "summary": { + "type": "string", + "description": "A one-line summary of about the distribution." + }, + "description": { + "type": "string", + "description": "A longer description of the distribution that can run to several paragraphs." + }, + "description_content_type": { + "type": "string", + "description": "The content type of the description. In the same format as the HTTP Content-Type header field." + }, + "keywords": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Keywords describing the distribution." + }, + "author": { + "type": "string", + "description": "The name of the author of the distribution. Additional contact information may be provided." + }, + "author_email": { + "type": "string", + "description": "The email address of the author or maintainer. It can contain a name and email address in the legal forms for a RFC 822 ``From:`` header." + }, + "maintainer": { + "type": "string", + "description": "The name of the maintainer. Additional contact information may be provided." + }, + "maintainer_email": { + "type": "string", + "description": "The email address of the maintainer. It can contain a name and email address in the legal forms for a RFC 822 ``From:`` header." + }, + "license": { + "type": "string", + "description": "Text indicating the license covering the distribution where the license is not a selection from the “License” Trove classifiers.", + "deprecated": true + }, + "license_expression": { + "type": "string", + "description": "A valid SPDX license expression indicating the license covering the distribution." + }, + "license_file": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Paths to license files relative to the project root directory." + }, + "classifier": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of Trove classifiers that describe the nature of the distribution." + }, + "requires_dist": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of projects required by the distribution." + }, + "requires_python": { + "type": "string", + "description": "The Python version for which the distribution is intended." + }, + "requires_external": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of external dependencies required by the distribution." + }, + "project_url": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "The label for the project URL.", + "pattern": "^.{1,32}$" + }, + "url": { + "type": "string", + "description": "The URL for the project URL." + } + }, + "additionalProperties": false + }, + "description": "A mapping of arbitrary text labels to additional URLs relevant to the project." + }, + "provides_extra": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" + }, + "description": "A list of optional features provided by the distribution." + }, + "import_name": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of exclusive import names provided by the distribution." + }, + "import_namespace": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of exclusive import namespaces provided by the distribution." + }, + "provides_dist": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of project names provided by the distribution." + }, + "obsoletes_dist": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of project names that are obsoleted by the distribution." + }, + "home_page": { + "type": "string", + "description": "The home page of the project.", + "deprecated": true + }, + "download_url": { + "type": "string", + "description": "The URL for the distribution's download page.", + "deprecated": true + }, + "requires": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of projects required by the distribution.", + "deprecated": true + }, + "provides": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of projects provided by the distribution.", + "deprecated": true + }, + "obsoletes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of projects that are obsoleted by the distribution.", + "deprecated": true + } + } +} \ No newline at end of file From 3426023b6c90d0b72a8f32e630fdab0d2d7cbacd Mon Sep 17 00:00:00 2001 From: Emma Smith Date: Tue, 9 Dec 2025 13:40:34 -0800 Subject: [PATCH 02/12] Add newline to appendix --- peps/pep-9999/appendix-core-metadata-json-schema.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peps/pep-9999/appendix-core-metadata-json-schema.rst b/peps/pep-9999/appendix-core-metadata-json-schema.rst index 71c7c67217e..51b0ade7ed8 100644 --- a/peps/pep-9999/appendix-core-metadata-json-schema.rst +++ b/peps/pep-9999/appendix-core-metadata-json-schema.rst @@ -8,4 +8,4 @@ Appendix: JSON Schema for Core Metadata .. literalinclude:: core-metadata.schema.json :language: json :linenos: - :name: core-metadata-schema \ No newline at end of file + :name: core-metadata-schema From d360af6397d3c5cb8ca25cfe64a387fd8ad6b711 Mon Sep 17 00:00:00 2001 From: Emma Smith Date: Tue, 9 Dec 2025 13:47:06 -0800 Subject: [PATCH 03/12] Remove extra newline in pep-9999.rst --- peps/pep-9999.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 97da2694876..02ab00f45ef 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -294,4 +294,3 @@ Copyright This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive. - From 13552b3c1e4506e1c28cb211fd7402090c33fc7c Mon Sep 17 00:00:00 2001 From: Emma Smith Date: Tue, 9 Dec 2025 13:48:58 -0800 Subject: [PATCH 04/12] Use data not attr for email compat --- peps/pep-9999.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 02ab00f45ef..4ebafa8b630 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -46,7 +46,7 @@ specifications: packaging metadata is not specified. In the absence of a precise definition, the practical standard is set by what the standard library :mod:`email.parser` module can parse using the - :attr:`email.policy.compat32` policy. + :data:`email.policy.compat32` policy. Since no specific email RFC is selected, the current core metadata specification is ambiguous whether a given core metadata document is valid. From b3ad6b6b25adc3631e4c8941fced47ea80ae0fc8 Mon Sep 17 00:00:00 2001 From: Emma Smith Date: Tue, 9 Dec 2025 13:50:33 -0800 Subject: [PATCH 05/12] Add more fixes for lint errors --- peps/pep-9999.rst | 2 +- peps/pep-9999/core-metadata.schema.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 4ebafa8b630..da33531b6dd 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -6,7 +6,7 @@ Discussions-To: Pending Status: Draft Type: Standards Track Topic: Packaging -Created: 2025-12-09 +Created: 09-Dec-2025 Post-History: Pending diff --git a/peps/pep-9999/core-metadata.schema.json b/peps/pep-9999/core-metadata.schema.json index f467748b760..12e9a3f987e 100644 --- a/peps/pep-9999/core-metadata.schema.json +++ b/peps/pep-9999/core-metadata.schema.json @@ -237,4 +237,4 @@ "deprecated": true } } -} \ No newline at end of file +} From c60c080ed645ab3ac11e6a72b05938a8c6300c5b Mon Sep 17 00:00:00 2001 From: Emma Smith Date: Thu, 18 Dec 2025 14:17:58 -0800 Subject: [PATCH 06/12] Format JSON Schema to 2 spaces --- peps/pep-9999/core-metadata.schema.json | 470 ++++++++++++------------ 1 file changed, 235 insertions(+), 235 deletions(-) diff --git a/peps/pep-9999/core-metadata.schema.json b/peps/pep-9999/core-metadata.schema.json index 12e9a3f987e..11871046118 100644 --- a/peps/pep-9999/core-metadata.schema.json +++ b/peps/pep-9999/core-metadata.schema.json @@ -1,240 +1,240 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://peps.python.org/pep-9999/core-metadata.schema.json", - "title": "Python Packaging Core Metadata", - "description": "Core metadata for Python packages", - "type": "object", - "properties": { - "metadata_version": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://peps.python.org/pep-9999/core-metadata.schema.json", + "title": "Python Packaging Core Metadata", + "description": "Core metadata for Python packages", + "type": "object", + "properties": { + "metadata_version": { + "type": "string", + "pattern": "^(\\d+(\\.\\d+)*)$", + "description": "The version of the file format." + }, + "name": { + "type": "string", + "pattern": "^([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9._-]*[A-Za-z0-9])$", + "description": "The name of the distribution." + }, + "version": { + "type": "string", + "pattern": "^v?([0-9]+!)?[0-9]+(\\.[0-9]+)*([-_\\.]?(alpha|a|beta|b|preview|pre|c|rc)[-_\\.]?[0-9]+)?((-[0-9]+)|([-_\\.]?(post|rev|r)[-_\\.]?[0-9]+))?([-_\\.]?dev[-_\\.]?[0-9]+)?$", + "description": "The distribution's version number." + }, + "dynamic": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "platform", + "supported_platform", + "summary", + "description", + "description_content_type", + "keywords", + "author", + "author_email", + "maintainer", + "maintainer_email", + "license", + "license_expression", + "license_file", + "classifier", + "requires_dist", + "requires_python", + "requires_external", + "project_url", + "provides_extra", + "import_name", + "import_namespace", + "provides_dist", + "obsoletes_dist", + "home_page", + "download_url", + "requires", + "provides", + "obsoletes" + ] + }, + "description": "A list of core metadata fields that are dynamicly calculated." + }, + "platform": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The platforms supported by the distribution." + }, + "supported_platform": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The platforms for which a binary distribution was compiled." + }, + "summary": { + "type": "string", + "description": "A one-line summary of about the distribution." + }, + "description": { + "type": "string", + "description": "A longer description of the distribution that can run to several paragraphs." + }, + "description_content_type": { + "type": "string", + "description": "The content type of the description. In the same format as the HTTP Content-Type header field." + }, + "keywords": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Keywords describing the distribution." + }, + "author": { + "type": "string", + "description": "The name of the author of the distribution. Additional contact information may be provided." + }, + "author_email": { + "type": "string", + "description": "The email address of the author or maintainer. It can contain a name and email address in the legal forms for a RFC 822 ``From:`` header." + }, + "maintainer": { + "type": "string", + "description": "The name of the maintainer. Additional contact information may be provided." + }, + "maintainer_email": { + "type": "string", + "description": "The email address of the maintainer. It can contain a name and email address in the legal forms for a RFC 822 ``From:`` header." + }, + "license": { + "type": "string", + "description": "Text indicating the license covering the distribution where the license is not a selection from the “License” Trove classifiers.", + "deprecated": true + }, + "license_expression": { + "type": "string", + "description": "A valid SPDX license expression indicating the license covering the distribution." + }, + "license_file": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Paths to license files relative to the project root directory." + }, + "classifier": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of Trove classifiers that describe the nature of the distribution." + }, + "requires_dist": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of projects required by the distribution." + }, + "requires_python": { + "type": "string", + "description": "The Python version for which the distribution is intended." + }, + "requires_external": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of external dependencies required by the distribution." + }, + "project_url": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { "type": "string", - "pattern": "^(\\d+(\\.\\d+)*)$", - "description": "The version of the file format." - }, - "name": { - "type": "string", - "pattern": "^([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9._-]*[A-Za-z0-9])$", - "description": "The name of the distribution." - }, - "version": { - "type": "string", - "pattern": "^v?([0-9]+!)?[0-9]+(\\.[0-9]+)*([-_\\.]?(alpha|a|beta|b|preview|pre|c|rc)[-_\\.]?[0-9]+)?((-[0-9]+)|([-_\\.]?(post|rev|r)[-_\\.]?[0-9]+))?([-_\\.]?dev[-_\\.]?[0-9]+)?$", - "description": "The distribution's version number." - }, - "dynamic": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "platform", - "supported_platform", - "summary", - "description", - "description_content_type", - "keywords", - "author", - "author_email", - "maintainer", - "maintainer_email", - "license", - "license_expression", - "license_file", - "classifier", - "requires_dist", - "requires_python", - "requires_external", - "project_url", - "provides_extra", - "import_name", - "import_namespace", - "provides_dist", - "obsoletes_dist", - "home_page", - "download_url", - "requires", - "provides", - "obsoletes" - ] - }, - "description": "A list of core metadata fields that are dynamicly calculated." - }, - "platform": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The platforms supported by the distribution." - }, - "supported_platform": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The platforms for which a binary distribution was compiled." - }, - "summary": { - "type": "string", - "description": "A one-line summary of about the distribution." - }, - "description": { - "type": "string", - "description": "A longer description of the distribution that can run to several paragraphs." - }, - "description_content_type": { - "type": "string", - "description": "The content type of the description. In the same format as the HTTP Content-Type header field." - }, - "keywords": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Keywords describing the distribution." - }, - "author": { - "type": "string", - "description": "The name of the author of the distribution. Additional contact information may be provided." - }, - "author_email": { - "type": "string", - "description": "The email address of the author or maintainer. It can contain a name and email address in the legal forms for a RFC 822 ``From:`` header." - }, - "maintainer": { - "type": "string", - "description": "The name of the maintainer. Additional contact information may be provided." - }, - "maintainer_email": { - "type": "string", - "description": "The email address of the maintainer. It can contain a name and email address in the legal forms for a RFC 822 ``From:`` header." - }, - "license": { - "type": "string", - "description": "Text indicating the license covering the distribution where the license is not a selection from the “License” Trove classifiers.", - "deprecated": true - }, - "license_expression": { - "type": "string", - "description": "A valid SPDX license expression indicating the license covering the distribution." - }, - "license_file": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Paths to license files relative to the project root directory." - }, - "classifier": { - "type": "array", - "items": { - "type": "string" - }, - "description": "A list of Trove classifiers that describe the nature of the distribution." - }, - "requires_dist": { - "type": "array", - "items": { - "type": "string" - }, - "description": "A list of projects required by the distribution." - }, - "requires_python": { - "type": "string", - "description": "The Python version for which the distribution is intended." - }, - "requires_external": { - "type": "array", - "items": { - "type": "string" - }, - "description": "A list of external dependencies required by the distribution." - }, - "project_url": { - "type": "array", - "items": { - "type": "object", - "properties": { - "label": { - "type": "string", - "description": "The label for the project URL.", - "pattern": "^.{1,32}$" - }, - "url": { - "type": "string", - "description": "The URL for the project URL." - } - }, - "additionalProperties": false - }, - "description": "A mapping of arbitrary text labels to additional URLs relevant to the project." - }, - "provides_extra": { - "type": "array", - "items": { - "type": "string", - "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" - }, - "description": "A list of optional features provided by the distribution." - }, - "import_name": { - "type": "array", - "items": { - "type": "string" - }, - "description": "A list of exclusive import names provided by the distribution." - }, - "import_namespace": { - "type": "array", - "items": { - "type": "string" - }, - "description": "A list of exclusive import namespaces provided by the distribution." - }, - "provides_dist": { - "type": "array", - "items": { - "type": "string" - }, - "description": "A list of project names provided by the distribution." - }, - "obsoletes_dist": { - "type": "array", - "items": { - "type": "string" - }, - "description": "A list of project names that are obsoleted by the distribution." - }, - "home_page": { + "description": "The label for the project URL.", + "pattern": "^.{1,32}$" + }, + "url": { "type": "string", - "description": "The home page of the project.", - "deprecated": true - }, - "download_url": { - "type": "string", - "description": "The URL for the distribution's download page.", - "deprecated": true - }, - "requires": { - "type": "array", - "items": { - "type": "string" - }, - "description": "A list of projects required by the distribution.", - "deprecated": true - }, - "provides": { - "type": "array", - "items": { - "type": "string" - }, - "description": "A list of projects provided by the distribution.", - "deprecated": true - }, - "obsoletes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "A list of projects that are obsoleted by the distribution.", - "deprecated": true - } + "description": "The URL for the project URL." + } + }, + "additionalProperties": false + }, + "description": "A mapping of arbitrary text labels to additional URLs relevant to the project." + }, + "provides_extra": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" + }, + "description": "A list of optional features provided by the distribution." + }, + "import_name": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of exclusive import names provided by the distribution." + }, + "import_namespace": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of exclusive import namespaces provided by the distribution." + }, + "provides_dist": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of project names provided by the distribution." + }, + "obsoletes_dist": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of project names that are obsoleted by the distribution." + }, + "home_page": { + "type": "string", + "description": "The home page of the project.", + "deprecated": true + }, + "download_url": { + "type": "string", + "description": "The URL for the distribution's download page.", + "deprecated": true + }, + "requires": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of projects required by the distribution.", + "deprecated": true + }, + "provides": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of projects provided by the distribution.", + "deprecated": true + }, + "obsoletes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of projects that are obsoleted by the distribution.", + "deprecated": true } + } } From 933ef4de191c3a1e77779c8913bb6bd175eb09eb Mon Sep 17 00:00:00 2001 From: Emma Smith Date: Thu, 18 Dec 2025 14:18:08 -0800 Subject: [PATCH 07/12] Respond to feedback and add ``WHEEL.json`` Introduce a ``WHEEL.json`` file as well. --- peps/pep-9999.rst | 141 ++++++++++++------ .../appendix-core-metadata-json-schema.rst | 10 ++ peps/pep-9999/wheel.schema.json | 40 +++++ 3 files changed, 146 insertions(+), 45 deletions(-) create mode 100644 peps/pep-9999/wheel.schema.json diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index da33531b6dd..1f6f00103f8 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -6,7 +6,7 @@ Discussions-To: Pending Status: Draft Type: Standards Track Topic: Packaging -Created: 09-Dec-2025 +Created: 18-Dec-2025 Post-History: Pending @@ -19,9 +19,9 @@ reasonable at the time; email messages were the only widely used, standardized text format that had a parser in the standard library at the time. However, issues with handling different encodings, differing handling of line breaks, and other differences between implementations have caused numerous packaging -bugs. To resolve these issues, this PEP proposes introducing a +bugs. To resolve these issues, this PEP proposes introducing `Javascript Object Notation (JSON) `_ -encoded file containing core metadata in Python packages. +encoded core metadata and wheel file format metadata files in Python packages. Motivation @@ -35,9 +35,14 @@ properly generate valid core metadata, many projects do not use the There are many pitfalls with generating email headers that these custom generators can hit. First, core metadata fields may contain newlines in the value of fields. These newlines must be handled properly to "unfolded" multiple -lines per :rfc:`822`. Improperly escaped newlines can lead to generating -invalid core metadata. Second, as discussed in the core metadata -specifications: +lines per :rfc:`822`. One particularly difficult to encode field is the +``Description`` field, which may contain newlines and indentation. To encode +the field in email headers, CRLF line breaks must be followed by seven (7) +spaces and a pip ("|") character. While ``Description`` may now be encoded in +the message body, similar escaping issues occur for the ``Author`` and +``Maintainer`` fields. Improperly escaped newlines can lead to missing, +partial, or invalid core metadata. Second, as discussed in the +`core metadata specifications `__: .. epigraph:: The standard file format for metadata (including in wheels and installed @@ -69,6 +74,12 @@ metadata. While introducing a specification for the current format has been no progress had been made, and converting to JSON was a suggested resolution to the issues raised. +The ``WHEEL`` file format is currently encoded in a custom key-value format. +While this format is easy to parse and write, it requires manual parsing and +validation to ensure that the contents are valid. Moving to a JSON encoded +format will allow for easier parsing and validation of the contents, and +simplify packaging tools and services. + Rationale ========= @@ -105,10 +116,10 @@ Specification JSON Format Core Metadata File ------------------------------ -A new optional file ``METADATA.json`` shall be introduced as a metadata file -for Python packages. If generated, the ``METADATA.json`` file MUST be placed in -the same directory as the current email formatted ``METADATA`` or ``PKG-INFO`` -file. +A new optional but recommended file ``METADATA.json`` shall be introduced as a +metadata file for Python packages. If generated, the ``METADATA.json`` file +MUST be placed in the same directory as the current email formatted +``METADATA`` or ``PKG-INFO`` file. For wheels, this means that ``METADATA.json`` MUST be located in the ``.dist-info`` directory. The wheel format minor version will be incremented to @@ -124,8 +135,8 @@ equivalent if ``METADATA.json`` is present. Installers MAY verify this information. Public package indexes SHOULD verify the files are semantically equivalent. -Conversion to JSON Encoding ---------------------------- +Conversion of ``METADATA`` to JSON Encoding +------------------------------------------- Conversion from the current email format for core metadata to JSON should follow the process described in :pep:`566`, with the following modification: @@ -164,8 +175,6 @@ This schema will be updated with each revision to the core metadata specification. The schema is available in :ref:`9999-core-metadata-json-schema`. -TODO: where should the schema be served/what should the $id be? - Serving METADATA.json in the Simple Repository API -------------------------------------------------- @@ -183,31 +192,74 @@ distribution is served at ``/simple/foo-1.0-py3-none-any.whl``, the JSON encoded core metadata file MUST be served at ``/simple/foo-1.0-py3-none-any.whl.metadata.json``. -Deprecation of the ``METADATA`` and ``PKG-INFO`` Files ------------------------------------------------------- +JSON Format Wheel Metadata File +------------------------------- + +A new optional but recommended file ``WHEEL.json`` shall be introduced as a +JSON encoded version of the ``WHEEL`` file. If generated, the ``WHEEL.json`` +file MUST be placed in the same directory as the current key-value formatted +``WHEEL`` file, i.e. the ``.dist-info`` directory. The semantic contents of +the ``WHEEL`` and ``WHEEL.json`` files MUST be equivalent. + +The ``WHEEL.json`` file SHOULD be preferred over the ``WHEEL`` file when both +are present. + +Conversion of ``WHEEL`` to JSON Encoding +---------------------------------------- + +Conversion from the current key-value format for wheel file format metadata to +JSON should proceed as follows: + +#. The original key-value format should be read. +#. All transformed keys should be reduced to lower case. Hyphens should be + replaced with underscores, but otherwise should retain all other characters. +#. The ``Tag`` field's entries should be converted to a list containing the + original values. +#. The result should be stored as a string-keyed dictionary. -The ``METADATA`` and ``PKG-INFO`` files are now deprecated. This means that a -future PEP may make the ``METADATA`` and ``PKG-INFO`` files optional and -require ``METADATA.json`` to be present. Please see the next section for +This follows a similar process to the conversion of ``METADATA`` to JSON +encoding. + +JSON Schema for Wheel Metadata +------------------------------ + +To enable verification of JSON encoded wheel file format metadata, a +JSON schema for wheel metadata has been produced. +This schema will be updated with each revision to the wheel metadata +specification. The schema is available in :ref:`9999-wheel-json-schema`. + +Deprecation of the ``METADATA``, ``PKG-INFO``, and ``WHEEL`` Files +------------------------------------------------------------------ + +The ``METADATA``, ``PKG-INFO``, and ``WHEEL`` files are now deprecated. This +means that a future PEP may make the ``METADATA``, ``PKG-INFO``, and ``WHEEL`` +files optional and require ``METADATA.json`` and ``WHEEL.json`` to be present. +Please see the next section for more information on backwards compatibility caveats to that change. Despite the ``METADATA`` and ``PKG-INFO`` files being deprecated, new core metadata revisions should be implemented for both JSON and email to ensure that -they may remain semantically equivalent. +they may remain semantically equivalent. Similarly, new ``WHEEL`` metadata keys +should be implemented for both JSON and key-value formats to ensure that they +may remain semantically equivalent. + Backwards Compatibility ======================= -The specification for ``METADATA.json`` is designed such that the new format is -completely backwards compatible. Existing tools may read metadata from the -existing email formatted files, and new tools may take advantage of the new -format. +The specification for ``METADATA.json`` and ``WHEEL.json`` is designed such +that the new format is completely backwards compatible. Existing tools may read +metadata from the existing email formatted files, and new tools may take +advantage of the new format. -A future major revision of the wheel specification may make the ``METADATA`` -and ``PKG-INFO`` files optional and make the ``METADATA.json`` file required. -Note that tools will need to maintain parsing of email metadata indefinitely to -support parsing metadata for old packages which only have the ``METADATA`` or -``PKG-INFO`` files. +A future major revision of the wheel specification may make the ``METADATA``, +``PKG-INFO``, and ``WHEEL`` files optional and make the ``METADATA.json`` and +``WHEEL.json`` files required. + +Note that tools will need to maintain parsing of email metadata and the +key-value formatted ``WHEEL`` file indefinitely to support parsing metadata +for old packages which only have the ``METADATA``, ``PKG-INFO``, +or ``WHEEL`` files. Security Implications @@ -215,8 +267,9 @@ Security Implications One attack vector with JSON encoded core metadata is if the JSON payload is designed to consume excessive memory or CPU resources in a denial of service -attack. While this attack is not likely to affect users whom can cancel -resource-intensive operations, it may be an issue for package indexes. +(DoS) attack. While this attack is not likely to affect users whom can cancel +resource-intensive interactive operations, it may be an issue for package +indexes. There are several mitigations that can be made to prevent this: @@ -224,9 +277,10 @@ There are several mitigations that can be made to prevent this: #. The reader may use a :class:`~json.JSONDecoder` to omit parsing :class:`int` and :class:`float` values to avoid quadratic number parsing time complexity attacks. -#. I plan to contribute a change to the :class:`~json.JSONDecoder` in Python +#. I plan to contribute a change to :class:`~json.JSONDecoder` in Python 3.15+ that will allow it to be configured to restrict the nesting of JSON - payloads to a reasonable depth. + payloads to a reasonable depth. Core metadata currently has a maximum depth + of 2 to encode mapping and list fields. With these mitigations in place, concerns about denial of service attacks with JSON encoded core metadata are minimal. @@ -242,6 +296,9 @@ Furthermore, a reference implementation in the ``packaging`` library `is available `__. +A reference implementation generating both ``METADATA.json`` and ``WHEEL.json`` +in the ``uv`` build backend `is also available `__. + Rejected Ideas ============== @@ -266,7 +323,7 @@ format, JSON has been chosen for a few reasons: Open Issues =========== -Where Should the JSON Schema be Served? +Where should the JSON schema be served? --------------------------------------- Where should the standard JSON Schema be served? Some options would be @@ -274,19 +331,13 @@ packaging.python.org, pypi.org, python.org, or pypa.org. My first choice would be packaging.python.org, but I am open to other options. -Should we also update the ``WHEEL`` metadata file format to be JSON encoded? ----------------------------------------------------------------------------- -The ``WHEEL`` metadata file format is also an email formatted file. This means -that it is subject to the same parsing and validation issues as the -``METADATA`` and ``PKG-INFO`` files. However, the ``WHEEL`` file is part of the -initial wheel format version check done by installers. Changing the file format -might harm backwards compatibility by making old installers unable to read new -metadata. +Acknowledgements +================ -I think it could make sense to introduce a ``WHEEL.json`` file. Then a future -wheel major version could remove the ``WHEEL`` file and require the -``WHEEL.json`` file instead. +Thanks to Konstantin Schütze for implementing the reference implementation of +this PEP in the ``uv`` build backend and for providing valuable feedback on the +specification. Copyright diff --git a/peps/pep-9999/appendix-core-metadata-json-schema.rst b/peps/pep-9999/appendix-core-metadata-json-schema.rst index 51b0ade7ed8..522f5b7a498 100644 --- a/peps/pep-9999/appendix-core-metadata-json-schema.rst +++ b/peps/pep-9999/appendix-core-metadata-json-schema.rst @@ -9,3 +9,13 @@ Appendix: JSON Schema for Core Metadata :language: json :linenos: :name: core-metadata-schema + +.. _9999-wheel-json-schema: + +Appendix: JSON Schema for Wheel Metadata +======================================== + +.. literalinclude:: wheel.schema.json + :language: json + :linenos: + :name: wheel-schema diff --git a/peps/pep-9999/wheel.schema.json b/peps/pep-9999/wheel.schema.json new file mode 100644 index 00000000000..ec3e9c309c6 --- /dev/null +++ b/peps/pep-9999/wheel.schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://peps.python.org/pep-9999/wheel.schema.json", + "title": "Wheel Metadata", + "description": "Metadata for the wheel file format.", + "type": "object", + "properties": { + "wheel_version": { + "type": "string", + "pattern": "^(\\d+(\\.\\d+)*)$", + "description": "The version of the wheel file format." + }, + "generator": { + "type": "string", + "description": "The name and version of the tool that generated the wheel." + }, + "root_is_purelib": { + "type": "boolean", + "description": "Whether the root of the archive should be installed into purelib." + }, + "tag": { + "type": "array", + "items": { + "type": "string", + "description": "The wheel's expanded compatibility tags." + } + }, + "build": { + "type": "string", + "pattern": "^(\\d+)$", + "description": "The build number of the wheel." + } + }, + "required": [ + "wheel_version", + "generator", + "root_is_purelib", + "tag" + ] +} \ No newline at end of file From f7adcac37d65a5f093331a7a1d7c9d84f4087d92 Mon Sep 17 00:00:00 2001 From: Emma Smith Date: Thu, 18 Dec 2025 14:20:10 -0800 Subject: [PATCH 08/12] Be more lax about the build tag --- peps/pep-9999/wheel.schema.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/peps/pep-9999/wheel.schema.json b/peps/pep-9999/wheel.schema.json index ec3e9c309c6..0b0f5b7fd05 100644 --- a/peps/pep-9999/wheel.schema.json +++ b/peps/pep-9999/wheel.schema.json @@ -27,8 +27,7 @@ }, "build": { "type": "string", - "pattern": "^(\\d+)$", - "description": "The build number of the wheel." + "description": "The build tag of the wheel." } }, "required": [ From 1b0170e2152521c5b10bb495b4b73ab27b81dc6f Mon Sep 17 00:00:00 2001 From: Emma Smith Date: Thu, 18 Dec 2025 14:27:48 -0800 Subject: [PATCH 09/12] Add newline to fix lint --- peps/pep-9999/wheel.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peps/pep-9999/wheel.schema.json b/peps/pep-9999/wheel.schema.json index 0b0f5b7fd05..97875239419 100644 --- a/peps/pep-9999/wheel.schema.json +++ b/peps/pep-9999/wheel.schema.json @@ -36,4 +36,4 @@ "root_is_purelib", "tag" ] -} \ No newline at end of file +} From f6613f15495833eea3aa4e1e2ca491856e57a324 Mon Sep 17 00:00:00 2001 From: Emma Smith Date: Thu, 18 Dec 2025 14:34:44 -0800 Subject: [PATCH 10/12] Claim PEP 819 --- .github/CODEOWNERS | 1 + peps/{pep-9999.rst => pep-0819.rst} | 2 +- .../appendix-core-metadata-json-schema.rst | 4 ++-- peps/{pep-9999 => pep-0819}/core-metadata.schema.json | 2 +- peps/{pep-9999 => pep-0819}/wheel.schema.json | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) rename peps/{pep-9999.rst => pep-0819.rst} (99%) rename peps/{pep-9999 => pep-0819}/appendix-core-metadata-json-schema.rst (85%) rename peps/{pep-9999 => pep-0819}/core-metadata.schema.json (99%) rename peps/{pep-9999 => pep-0819}/wheel.schema.json (93%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a0a6ab363d9..d4db69cd6bf 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -691,6 +691,7 @@ peps/pep-0811.rst @sethmlarson @gpshead peps/pep-0814.rst @vstinner @corona10 peps/pep-0815.rst @emmatyping peps/pep-0816.rst @brettcannon +peps/pep-0819.rst @emmatyping # ... peps/pep-2026.rst @hugovk # ... diff --git a/peps/pep-9999.rst b/peps/pep-0819.rst similarity index 99% rename from peps/pep-9999.rst rename to peps/pep-0819.rst index 1f6f00103f8..fccf1defcd3 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-0819.rst @@ -1,4 +1,4 @@ -PEP: 9999 +PEP: 819 Title: JSON Package Metadata Author: Emma Harper Smith PEP-Delegate: Paul Moore diff --git a/peps/pep-9999/appendix-core-metadata-json-schema.rst b/peps/pep-0819/appendix-core-metadata-json-schema.rst similarity index 85% rename from peps/pep-9999/appendix-core-metadata-json-schema.rst rename to peps/pep-0819/appendix-core-metadata-json-schema.rst index 522f5b7a498..2d7788f2acf 100644 --- a/peps/pep-9999/appendix-core-metadata-json-schema.rst +++ b/peps/pep-0819/appendix-core-metadata-json-schema.rst @@ -1,6 +1,6 @@ :orphan: -.. _9999-core-metadata-json-schema: +.. _0819-core-metadata-json-schema: Appendix: JSON Schema for Core Metadata ======================================= @@ -10,7 +10,7 @@ Appendix: JSON Schema for Core Metadata :linenos: :name: core-metadata-schema -.. _9999-wheel-json-schema: +.. _0819-wheel-json-schema: Appendix: JSON Schema for Wheel Metadata ======================================== diff --git a/peps/pep-9999/core-metadata.schema.json b/peps/pep-0819/core-metadata.schema.json similarity index 99% rename from peps/pep-9999/core-metadata.schema.json rename to peps/pep-0819/core-metadata.schema.json index 11871046118..303314d15db 100644 --- a/peps/pep-9999/core-metadata.schema.json +++ b/peps/pep-0819/core-metadata.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://peps.python.org/pep-9999/core-metadata.schema.json", + "$id": "https://peps.python.org/pep-0819/core-metadata.schema.json", "title": "Python Packaging Core Metadata", "description": "Core metadata for Python packages", "type": "object", diff --git a/peps/pep-9999/wheel.schema.json b/peps/pep-0819/wheel.schema.json similarity index 93% rename from peps/pep-9999/wheel.schema.json rename to peps/pep-0819/wheel.schema.json index 97875239419..ad557717721 100644 --- a/peps/pep-9999/wheel.schema.json +++ b/peps/pep-0819/wheel.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://peps.python.org/pep-9999/wheel.schema.json", + "$id": "https://peps.python.org/pep-0819/wheel.schema.json", "title": "Wheel Metadata", "description": "Metadata for the wheel file format.", "type": "object", From 4d28637c592334eec373ee6da36e1f3d3234ed5c Mon Sep 17 00:00:00 2001 From: Emma Smith Date: Thu, 18 Dec 2025 14:44:16 -0800 Subject: [PATCH 11/12] Update schema refs in PEP text --- peps/pep-0819.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/peps/pep-0819.rst b/peps/pep-0819.rst index fccf1defcd3..8599dd1c0f0 100644 --- a/peps/pep-0819.rst +++ b/peps/pep-0819.rst @@ -173,7 +173,7 @@ To enable verification of JSON encoded core metadata, a `JSON schema `_ for core metadata has been produced. This schema will be updated with each revision to the core metadata specification. The schema is available in -:ref:`9999-core-metadata-json-schema`. +:ref:`0819-core-metadata-json-schema`. Serving METADATA.json in the Simple Repository API -------------------------------------------------- @@ -226,7 +226,7 @@ JSON Schema for Wheel Metadata To enable verification of JSON encoded wheel file format metadata, a JSON schema for wheel metadata has been produced. This schema will be updated with each revision to the wheel metadata -specification. The schema is available in :ref:`9999-wheel-json-schema`. +specification. The schema is available in :ref:`0819-wheel-json-schema`. Deprecation of the ``METADATA``, ``PKG-INFO``, and ``WHEEL`` Files ------------------------------------------------------------------ @@ -290,7 +290,7 @@ Reference Implementation ======================== A reference implementation of the JSON schema for JSON core metadata is -available in :ref:`9999-core-metadata-json-schema`. +available in :ref:`0819-core-metadata-json-schema`. Furthermore, a reference implementation in the ``packaging`` library `is available From 8db80d037797e84e92ed7dfa6198ea02901a763b Mon Sep 17 00:00:00 2001 From: Emma Smith Date: Thu, 18 Dec 2025 14:50:26 -0800 Subject: [PATCH 12/12] Add spacing between under-review PEPs in CODEOWNERS Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d4db69cd6bf..45009d9dad9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -691,6 +691,7 @@ peps/pep-0811.rst @sethmlarson @gpshead peps/pep-0814.rst @vstinner @corona10 peps/pep-0815.rst @emmatyping peps/pep-0816.rst @brettcannon +# ... peps/pep-0819.rst @emmatyping # ... peps/pep-2026.rst @hugovk