diff --git a/source/guides/writing-pyproject-toml.rst b/source/guides/writing-pyproject-toml.rst index 144cc3525..a497842a1 100644 --- a/source/guides/writing-pyproject-toml.rst +++ b/source/guides/writing-pyproject-toml.rst @@ -399,6 +399,7 @@ To prevent a package from being uploaded to PyPI, use the special ``Private :: Do Not Upload`` classifier. PyPI will always reject packages with classifiers beginning with ``Private ::``. +.. _writing-pyproject-toml-urls: ``urls`` -------- @@ -406,6 +407,13 @@ beginning with ``Private ::``. A list of URLs associated with your project, displayed on the left sidebar of your PyPI project page. +.. note:: + + See :ref:`well-known-labels` for a listing + of labels that PyPI and other packaging tools are specifically aware of, + and `PyPI's project metadata docs `_ + for PyPI-specific URL processing. + .. code-block:: toml [project.urls] @@ -415,11 +423,34 @@ sidebar of your PyPI project page. Issues = "https://github.com/me/spam/issues" Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md" -Note that if the key contains spaces, it needs to be quoted, e.g., +Note that if the label contains spaces, it needs to be quoted, e.g., ``Website = "https://example.com"`` but ``"Official Website" = "https://example.com"``. +Users are advised to use :ref:`well-known-labels` for their project URLs +where appropriate, since consumers of metadata (like package indices) can +specialize their presentation. + +For example in the following metadata, neither ``MyHomepage`` nor +``"Download Link"`` is a well-known label, so they will be rendered verbatim: +.. code-block:: toml + + [project.urls] + MyHomepage = "https://example.com" + "Download Link" = "https://example.com/abc.tar.gz" + + +Whereas in this metadata ``HomePage`` and ``DOWNLOAD`` both have +well-known equivalents (``homepage`` and ``download``), and can be presented +with those semantics in mind (the project's home page and its external +download location, respectively). + +.. code-block:: toml + + [project.urls] + HomePage = "https://example.com" + DOWNLOAD = "https://example.com/abc.tar.gz" Advanced plugins ================ diff --git a/source/specifications/core-metadata.rst b/source/specifications/core-metadata.rst index 97686c543..c1ce528e7 100644 --- a/source/specifications/core-metadata.rst +++ b/source/specifications/core-metadata.rst @@ -341,32 +341,6 @@ Example:: These tools have been very widely used for many years, so it was easier to update the specification to match the de facto standard. -.. _home-page-optional: -.. _core-metadata-home-page: - -Home-page -========= - -.. versionadded:: 1.0 - -A string containing the URL for the distribution's home page. - -Example:: - - Home-page: http://www.example.com/~cschultz/bvote/ - -.. _core-metadata-download-url: - -Download-URL -============ - -.. versionadded:: 1.1 - -A string containing the URL from which this version of the distribution -can be downloaded. (This means that the URL can't be something like -".../BeagleVote-latest.tgz", but instead must be ".../BeagleVote-0.45.tgz".) - - .. _author-optional: .. _core-metadata-author: @@ -669,6 +643,10 @@ Example:: The label is free text limited to 32 characters. +Starting with :pep:`753`, project metadata consumers (such as the Python +Package Index) can use a standard normalization process to discover "well-known" +labels, which can then be given special presentations when being rendered +for human consumption. See :ref:`well-known-project-urls`. .. _metadata_provides_extra: .. _core-metadata-provides-extra: @@ -819,6 +797,40 @@ Examples:: Deprecated Fields ================= +.. _home-page-optional: +.. _core-metadata-home-page: + +Home-page +--------- + +.. versionadded:: 1.0 + +.. deprecated:: 1.2 + + Per :pep:`753`, use :ref:`core-metadata-project-url` instead. + +A string containing the URL for the distribution's home page. + +Example:: + + Home-page: http://www.example.com/~cschultz/bvote/ + +.. _core-metadata-download-url: + +Download-URL +------------ + +.. versionadded:: 1.1 + +.. deprecated:: 1.2 + + Per :pep:`753`, use :ref:`core-metadata-project-url` instead. + +A string containing the URL from which this version of the distribution +can be downloaded. (This means that the URL can't be something like +"``.../BeagleVote-latest.tgz``", but instead must be +"``.../BeagleVote-0.45.tgz``".) + Requires -------- diff --git a/source/specifications/pyproject-toml.rst b/source/specifications/pyproject-toml.rst index efa562a73..400e43105 100644 --- a/source/specifications/pyproject-toml.rst +++ b/source/specifications/pyproject-toml.rst @@ -318,7 +318,8 @@ Trove classifiers which apply to the project. :ref:`Project-URL ` A table of URLs where the key is the URL label and the value is the -URL itself. +URL itself. See :ref:`well-known-project-urls` for normalization rules +and well-known rules when processing metadata for presentation. Entry points diff --git a/source/specifications/section-distribution-metadata.rst b/source/specifications/section-distribution-metadata.rst index 7fd3361a0..0c0a8aaba 100644 --- a/source/specifications/section-distribution-metadata.rst +++ b/source/specifications/section-distribution-metadata.rst @@ -12,3 +12,4 @@ Package Distribution Metadata pyproject-toml inline-script-metadata platform-compatibility-tags + well-known-project-urls diff --git a/source/specifications/well-known-project-urls.rst b/source/specifications/well-known-project-urls.rst new file mode 100644 index 000000000..30fefd12b --- /dev/null +++ b/source/specifications/well-known-project-urls.rst @@ -0,0 +1,176 @@ +.. _`well-known-project-urls`: + +=================================== +Well-known Project URLs in Metadata +=================================== + +.. important:: + + This document is primarily of interest to metadata *consumers*, + who should use the normalization rules and well-known list below + to make their presentation of project URLs consistent across the + Python ecosystem. + + Metadata *producers* (such as build tools and individual package + maintainers) may continue to use any labels they please, within the + overall ``Project-URL`` length restrictions. However, when possible, users are + *encouraged* to pick meaningful labels that normalize to well-known + labels. + +.. note:: + + See :ref:`Writing your pyproject.toml - urls ` + for user-oriented guidance on choosing project URL labels in your package's + metadata. + +.. note:: This specification was originally defined in :pep:`753`. + +:pep:`753` deprecates the :ref:`core-metadata-home-page` and +:ref:`core-metadata-download-url` metadata fields in favor of +:ref:`core-metadata-project-url`, and defines a normalization and +lookup procedure for determining whether a ``Project-URL`` is +"well-known," i.e. has the semantics assigned to ``Home-page``, +``Download-URL``, or other common project URLs. + +This allows indices (such as the Python Package Index) and other downstream +metadata consumers to present project URLs in a +consistent manner. + +.. _project-url-label-normalization: + +Label normalization +=================== + +.. note:: + + Label normalization is performed by metadata *consumers*, not metadata + producers. + +To determine whether a ``Project-URL`` label is "well-known," metadata +consumers should normalize the label before comparing it to the +:ref:`list of well-known labels `. + +The normalization procedure for ``Project-URL`` labels is defined +by the following Python function: + +.. code-block:: python + + import string + + def normalize_label(label: str) -> str: + chars_to_remove = string.punctuation + string.whitespace + removal_map = str.maketrans("", "", chars_to_remove) + return label.translate(removal_map).lower() + +In plain language: a label is *normalized* by deleting all ASCII punctuation +and whitespace, and then converting the result to lowercase. + +The following table shows examples of labels before (raw) and after +normalization: + +.. list-table:: + :header-rows: 1 + + * - Raw + - Normalized + * - ``Homepage`` + - ``homepage`` + * - ``Home-page`` + - ``homepage`` + * - ``Home page`` + - ``homepage`` + * - ``Change_Log`` + - ``changelog`` + * - ``What's New?`` + - ``whatsnew`` + * - ``github`` + - ``github`` + +.. _well-known-labels: + +Well-known labels +================= + +.. note:: + + The list of well-known labels is a living standard, maintained as part of + this document. + +The following table lists labels that are well-known for the purpose of +specializing the presentation of ``Project-URL`` metadata: + +.. list-table:: + :header-rows: 1 + + * - Label (Human-readable equivalent) + - Description + - Aliases + * - ``homepage`` (Homepage) + - The project's home page + - *(none)* + * - ``source`` (Source Code) + - The project's hosted source code or repository + - ``repository``, ``sourcecode``, ``github`` + * - ``download`` (Download) + - A download URL for the current distribution, equivalent to ``Download-URL`` + - *(none)* + * - ``changelog`` (Changelog) + - The project's comprehensive changelog + - ``changes``, ``whatsnew``, ``history`` + * - ``releasenotes`` (Release Notes) + - The project's curated release notes + - *(none)* + * - ``documentation`` (Documentation) + - The project's online documentation + - ``docs`` + * - ``issues`` (Issue Tracker) + - The project's bug tracker + - ``bugs``, ``issue``, ``tracker``, ``issuetracker``, ``bugtracker`` + * - ``funding`` (Funding) + - Funding Information + - ``sponsor``, ``donate``, ``donation`` + +Package metadata consumers may choose to render aliased labels the same as +their "parent" well known label, or further specialize them. + +Example behavior +================ + +The following shows the flow of project URL metadata from +``pyproject.toml`` to core metadata to a potential index presentation: + +.. code-block:: toml + :caption: Example project URLs in standard configuration + + [project.urls] + "Home Page" = "https://example.com" + DOCUMENTATION = "https://readthedocs.org" + Repository = "https://upstream.example.com/me/spam.git" + GitHub = "https://github.com/example/spam" + +.. code-block:: email + :caption: Core metadata representation + + Project-URL: Home page, https://example.com + Project-URL: DOCUMENTATION, https://readthedocs.org + Project-URL: Repository, https://upstream.example.com/me/spam.git + Project-URL: GitHub, https://github.com/example/spam + +.. code-block:: text + :caption: Potential rendering + + Homepage: https://example.com + Documentation: https://readthedocs.org + Source Code: https://upstream.example.com/me/spam.git + Source Code (GitHub): https://github.com/example/spam + +Observe that the core metadata appears in the form provided by the user +(since metadata *producers* do not perform normalization), but the +metadata *consumer* normalizes and identifies appropriate +human-readable equivalents based on the normalized form: + +* ``Home page`` becomes ``homepage``, which is rendered as ``Homepage`` +* ``DOCUMENTATION`` becomes ``documentation``, which is rendered as ``Documentation`` +* ``Repository`` becomes ``repository``, which is rendered as ``Source Code`` +* ``GitHub`` becomes ``github``, which is rendered as ``Source Code (GitHub)`` + (as a specialization of ``Source Code``)