diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ecd85064f..0b7c7b289 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,9 @@ source/guides/github-actions-ci-cd-sample/* @webknjaz source/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows.rst @webknjaz +# Sphinx extension +pug_sphinx_extensions/ @FFY00 + # build-details.json source/specifications/build-details/ @FFY00 source/specifications/specs/build-details-*.json @FFY00 diff --git a/.github/workflows/pr-preview-links.yml b/.github/workflows/pr-preview-links.yml index 90ea9cc73..291ec3ad2 100644 --- a/.github/workflows/pr-preview-links.yml +++ b/.github/workflows/pr-preview-links.yml @@ -17,6 +17,6 @@ jobs: documentation-links: runs-on: ubuntu-latest steps: - - uses: readthedocs/actions/preview@v1 + - uses: readthedocs/actions/preview@b8bba1484329bda1a3abe986df7ebc80a8950333 # v1.5 with: project-slug: "python-packaging-user-guide" diff --git a/.github/workflows/test-translations.yml b/.github/workflows/test-translations.yml index 45dc60aa3..537a8df72 100644 --- a/.github/workflows/test-translations.yml +++ b/.github/workflows/test-translations.yml @@ -31,9 +31,10 @@ jobs: steps: - name: Grab the repo src - uses: actions/checkout@v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: ${{ env.I18N_BRANCH }} + persist-credentials: false - name: List languages id: languages @@ -53,12 +54,13 @@ jobs: steps: - name: Grab the repo src - uses: actions/checkout@v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: ${{ env.I18N_BRANCH }} + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: >- 3.10 @@ -67,10 +69,12 @@ jobs: run: python -m pip install --upgrade nox virtualenv sphinx-lint - name: Set Sphinx problem matcher - uses: sphinx-doc/github-problem-matcher@v1.0 + uses: sphinx-doc/github-problem-matcher@1f74d6599f4a5e89a20d3c99aab4e6a70f7bda0f # v1.1 - name: Build translated docs in ${{ matrix.language }} - run: nox -s build -- -q -D language=${{ matrix.language }} + run: nox -s build -- -q -D language=${LANGUAGE} + env: + LANGUAGE: ${{ matrix.language }} - name: Set Sphinx Lint problem matcher if: always() @@ -78,4 +82,6 @@ jobs: - name: Lint translation file if: always() - run: sphinx-lint locales/${{ matrix.language }}/LC_MESSAGES/messages.po + run: sphinx-lint locales/${LANGUAGE}/LC_MESSAGES/messages.po + env: + LANGUAGE: ${{ matrix.language }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8503ca720..1f67bad8e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,12 +6,19 @@ on: branches-ignore: - gh-readonly-queue/** # Temporary merge queue-related GH-made branches pull_request: + types: + - opened # default + - synchronize # default + - reopened # default + - ready_for_review # used in PRs created from GitHub Actions workflows workflow_call: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true +permissions: {} + jobs: build: name: ${{ matrix.noxenv }} @@ -24,10 +31,12 @@ jobs: - linkcheck steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: "3.11" cache: 'pip' @@ -55,6 +64,6 @@ jobs: steps: - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 + uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/translation.yml b/.github/workflows/translation.yml index 7cfae2991..67fcb5edf 100644 --- a/.github/workflows/translation.yml +++ b/.github/workflows/translation.yml @@ -17,16 +17,20 @@ jobs: runs-on: ubuntu-latest if: github.repository_owner == 'pypa' + permissions: + contents: write # to push to I18N_BRANCH + steps: - name: Grab the repo src - uses: actions/checkout@v3 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 # To reach the common commit + persist-credentials: true # For `git push` - name: Set up git user as [bot] # Refs: # * https://github.community/t/github-actions-bot-email-address/17204/6 # * https://github.com/actions/checkout/issues/13#issuecomment-724415212 - uses: fregante/setup-git-user@v1.1.0 + uses: fregante/setup-git-user@024bc0b8e177d7e77203b48dab6fb45666854b35 # v2.0.2 - name: Switch to the translation source branch run: | @@ -48,10 +52,12 @@ jobs: run: | sh -x - git merge '${{ github.event.repository.default_branch }}' + git merge "${DEFAULT_BRANCH}" + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: >- 3.10 diff --git a/.github/workflows/update-uv-build-version.yml b/.github/workflows/update-uv-build-version.yml new file mode 100644 index 000000000..d204bd391 --- /dev/null +++ b/.github/workflows/update-uv-build-version.yml @@ -0,0 +1,43 @@ +--- + +name: Update uv build version + +on: + schedule: + - cron: "0 6 * * 1" # mondays at 6am + workflow_dispatch: + +jobs: + update-uv-build-version: + name: Update uv_build version + if: github.repository_owner == 'pypa' # suppress noise in forks + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - name: Set up uv + uses: astral-sh/setup-uv@3259c6206f993105e3a61b142c2d97bf4b9ef83d # v7.1.0 + - name: Update uv_build version + id: update_script + run: uv run scripts/update_uv_build_version.py + - # If there are no changes, no pull request will be created and the action exits silently. + name: Create Pull Request + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: Update uv_build version to ${{ steps.update_script.outputs.version }} + title: Update uv_build version to ${{ steps.update_script.outputs.version }} + draft: true # Trigger CI by un-drafting the PR, otherwise `GITHUB_TOKEN` PRs don't trigger CI. + body: | + Automated update of uv_build version bounds for uv ${{ steps.update_script.outputs.version }}. + + This PR was created automatically by the cron workflow, ping `@konstin` for problems. + branch: bot/update-uv-build-version + delete-branch: true + +... diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index d99b6473c..6c8c62f7d 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -19,12 +19,12 @@ jobs: actions: read steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - name: Install the latest version of uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@3259c6206f993105e3a61b142c2d97bf4b9ef83d # v7.1.0 - name: Run zizmor 🌈 run: uvx zizmor --format sarif source/guides/github-actions-ci-cd-sample/* > results.sarif @@ -32,7 +32,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 with: sarif_file: results.sarif category: zizmor diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index db8b1131a..47b864808 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -15,7 +15,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/codespell-project/codespell - rev: v2.3.0 + rev: v2.4.1 hooks: - id: codespell args: ["-L", "ned,ist,oder", "--skip", "*.po"] @@ -37,7 +37,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.1 + rev: v0.14.10 hooks: - id: ruff - id: ruff-format diff --git a/extra/specifications/schemas/direct-url.schema.json b/extra/specifications/schemas/direct-url.schema.json new file mode 100644 index 000000000..d1f4c860a --- /dev/null +++ b/extra/specifications/schemas/direct-url.schema.json @@ -0,0 +1,99 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://packaging.python.org/en/latest/specifications/schemas/direct-url.schema.json", + "title": "Direct URL Data", + "description": "Data structure that can represent URLs to python projects and distribution artifacts such as VCS source trees, local source trees, source distributions and wheels.", + "definitions": { + "url": { + "type": "string", + "format": "uri" + }, + "DirInfo": { + "type": "object", + "properties": { + "editable": { + "type": ["boolean", "null"] + } + } + }, + "VCSInfo": { + "type": "object", + "properties": { + "vcs": { + "type": "string", + "enum": ["git", "hg", "bzr", "svn"] + }, + "requested_revision": { + "type": "string" + }, + "commit_id": { + "type": "string" + }, + "resolved_revision": { + "type": "string" + } + }, + "required": ["vcs", "commit_id"] + }, + "ArchiveInfo": { + "type": "object", + "properties": { + "hash": { + "type": "string", + "pattern": "^\\w+=[a-f0-9]+$", + "deprecated": true + }, + "hashes": { + "type": "object", + "patternProperties": { + "^[a-f0-9]+$": { + "type": "string" + } + } + } + } + } + }, + "allOf": [ + { + "type": "object", + "properties": { + "url": { + "$ref": "#/definitions/url" + } + }, + "required": ["url"] + }, + { + "anyOf": [ + { + "type": "object", + "properties": { + "dir_info": { + "$ref": "#/definitions/DirInfo" + } + }, + "required": ["dir_info"] + }, + { + "type": "object", + "properties": { + "vcs_info": { + "$ref": "#/definitions/VCSInfo" + } + }, + "required": ["vcs_info"] + }, + { + "type": "object", + "properties": { + "archive_info": { + "$ref": "#/definitions/ArchiveInfo" + } + }, + "required": ["archive_info"] + } + ] + } + ] +} diff --git a/extra/specifications/schemas/pylock.schema.json b/extra/specifications/schemas/pylock.schema.json new file mode 100644 index 000000000..90404e33d --- /dev/null +++ b/extra/specifications/schemas/pylock.schema.json @@ -0,0 +1,345 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://packaging.python.org/en/latest/specifications/schemas/pylock.schema.json", + "additionalProperties": false, + "definitions": { + "tool": { + "type": "object", + "markdownDescription": "Similar usage as that of the `[tool]` table from the [pyproject.toml specification](https://packaging.python.org/en/latest/specifications/pyproject-toml/#pyproject-toml-spec), but at the package version level instead of at the lock file level (which is also available via `[tool]`).", + "additionalProperties": { + "type": "object", + "additionalProperties": true + } + }, + "url": { + "type": "string", + "markdownDescription": "The URL to the source tree." + }, + "path": { + "type": "string", + "markdownDescription": "The path to the local directory of the source tree." + }, + "upload-time": { + "markdownDescription": "The time the file was uploaded (UTC). Must be specified as a datetime literal." + }, + "size": { + "type": "integer", + "markdownDescription": "The size of the archive file." + }, + "hashes": { + "type": "object", + "description": "Known hash values of the file where the key is the hash algorithm and the value is the hash value.", + "additionalProperties": { + "type": "string" + } + }, + "subdirectory": { + "type": "string", + "markdownDescription": "The subdirectory within the [source tree](https://packaging.python.org/en/latest/specifications/source-distribution-format/#source-distribution-format-source-tree) where the project root of the project is (e.g. the location of the `pyproject.toml` file)." + }, + "vcs": { + "type": "object", + "markdownDescription": "Record the version control system details for the [source tree](https://packaging.python.org/en/latest/specifications/source-distribution-format/#source-distribution-format-source-tree) it contains.", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "markdownDescription": "The type of version control system used." + }, + "url": { + "$ref": "#/definitions/url" + }, + "path": { + "$ref": "#/definitions/path" + }, + "requested-revision": { + "type": "string", + "markdownDescription": "The branch/tag/ref/commit/revision/etc. that the user requested." + }, + "commit-id": { + "type": "string", + "markdownDescription": "The exact commit/revision number that is to be installed." + }, + "subdirectory": { + "$ref": "#/definitions/subdirectory" + } + } + }, + "directory": { + "type": "object", + "markdownDescription": "Record the local directory details for the [source tree](https://packaging.python.org/en/latest/specifications/source-distribution-format/#source-distribution-format-source-tree) it contains.", + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "markdownDescription": "The local directory where the source tree is." + }, + "editable": { + "type": "boolean", + "default": false, + "markdownDescription": "A flag representing whether the source tree was an editable install at lock time." + }, + "subdirectory": { + "$ref": "#/definitions/subdirectory" + } + } + }, + "archive": { + "type": "object", + "additionalProperties": false, + "markdownDescription": "A direct reference to an archive file to install from (this can include wheels and sdists, as well as other archive formats containing a source tree).", + "properties": { + "url": { + "$ref": "#/definitions/url" + }, + "path": { + "$ref": "#/definitions/path" + }, + "size": { + "$ref": "#/definitions/size" + }, + "upload-time": { + "$ref": "#/definitions/upload-time" + }, + "hashes": { + "$ref": "#/definitions/hashes" + }, + "subdirectory": { + "$ref": "#/definitions/subdirectory" + } + } + }, + "sdist": { + "type": "object", + "additionalProperties": false, + "markdownDescription": "Details of a [source distribution file name](https://packaging.python.org/en/latest/specifications/source-distribution-format/#source-distribution-format-sdist) for the package.", + "properties": { + "name": { + "type": "string", + "markdownDescription": "The file name of the [source distribution file name](https://packaging.python.org/en/latest/specifications/source-distribution-format/#source-distribution-format-sdist) file." + }, + "upload-time": { + "$ref": "#/definitions/upload-time" + }, + "url": { + "$ref": "#/definitions/url" + }, + "path": { + "$ref": "#/definitions/path" + }, + "size": { + "$ref": "#/definitions/size" + }, + "hashes": { + "$ref": "#/definitions/hashes" + } + } + }, + "wheels": { + "type": "array", + "markdownDescription": "For recording the wheel files as specified by [Binary distribution format](https://packaging.python.org/en/latest/specifications/binary-distribution-format/#binary-distribution-format) for the package.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "markdownDescription": "The file name of the [Binary distribution format](https://packaging.python.org/en/latest/specifications/binary-distribution-format/#binary-distribution-format) file." + }, + "upload-time": { + "$ref": "#/definitions/upload-time" + }, + "url": { + "$ref": "#/definitions/url" + }, + "path": { + "$ref": "#/definitions/path" + }, + "size": { + "$ref": "#/definitions/size" + }, + "hashes": { + "$ref": "#/definitions/hashes" + } + } + } + }, + "1.0": { + "required": ["lock-version", "created-by", "packages"], + "properties": { + "lock-version": { + "type": "string", + "enum": ["1.0"], + "description": "Record the file format version that the file adheres to." + }, + "environments": { + "type": "array", + "markdownDescription": "A list of [environment markers](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#dependency-specifiers-environment-markers) for which the lock file is considered compatible with.", + "items": { + "type": "string", + "description": "Environment marker" + } + }, + "requires-python": { + "type": "string", + "markdownDescription": "Specifies the [Requires-Python](https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata-requires-python) for the minimum Python version compatible for any environment supported by the lock file (i.e. the minimum viable Python version for the lock file)." + }, + "extras": { + "type": "array", + "markdownDescription": "The list of [extras](https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata-provides-extra) supported by this lock file.", + "default": [], + "items": { + "type": "string", + "description": "Extra name" + } + }, + "dependency-groups": { + "type": "array", + "markdownDescription": "The list of [dependency groups](https://packaging.python.org/en/latest/specifications/dependency-groups/#dependency-groups) publicly supported by this lock file (i.e. dependency groups users are expected to be able to specify via a tool’s UI).", + "default": [], + "items": { + "type": "string", + "description": "Dependency group name" + } + }, + "default-groups": { + "type": "array", + "markdownDescription": "The name of synthetic dependency groups to represent what should be installed by default (e.g. what `project.dependencies` implicitly represents).", + "default": [], + "items": { + "type": "string", + "description": "Dependency group name" + } + }, + "created-by": { + "type": "string", + "markdownDescription": "Records the name of the tool used to create the lock file." + }, + "packages": { + "type": "array", + "markdownDescription": "An array containing all packages that may be installed.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name"], + "allOf": [ + { + "if": { + "required": ["vcs"] + }, + "then": { + "not": { + "required": ["directory", "archive", "sdist", "wheels"] + } + } + }, + { + "if": { + "required": ["directory"] + }, + "then": { + "not": { + "required": ["vcs", "archive", "sdist", "wheels"] + } + } + }, + { + "if": { + "required": ["sdist"] + }, + "then": { + "not": { + "required": ["vcs", "directory", "archive"] + } + } + }, + { + "if": { + "required": ["wheels"] + }, + "then": { + "not": { + "required": ["vcs", "directory", "archive"] + } + } + } + ], + "properties": { + "name": { + "type": "string", + "markdownDescription": "The name of the package, [normalized](https://packaging.python.org/en/latest/specifications/name-normalization/#name-normalization)." + }, + "version": { + "type": "string", + "description": "The version of the package." + }, + "marker": { + "type": "string", + "markdownDescription": "The [environment marker](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#dependency-specifiers-environment-markers) which specify when the package should be installed." + }, + "requires-python": { + "type": "string", + "markdownDescription": "Holds the [version specifiers](https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers) for Python version compatibility for the package." + }, + "dependencies": { + "type": "array", + "markdownDescription": "Records the other entries in `[[packages]]` which are direct dependencies of this package.", + "items": { + "type": "object", + "markdownDescription": "A table which contains the minimum information required to tell which other package entry it corresponds to where doing a key-by-key comparison would find the appropriate package with no ambiguity (e.g. if there are two entries for the `spam` package, then you can include the version number like `{name = \"spam\", version = \"1.0.0\"}`, or by source like `{name = \"spam\", vcs = { url = \"...\"}`).", + "additionalProperties": true + } + }, + "vcs": { + "$ref": "#/definitions/vcs" + }, + "directory": { + "$ref": "#/definitions/directory" + }, + "archive": { + "$ref": "#/definitions/archive" + }, + "index": { + "type": "string", + "markdownDescription": "The base URL for the package index from [simple repository API](https://packaging.python.org/en/latest/specifications/simple-repository-api/#simple-repository-api) where the sdist and/or wheels were found (e.g. `https://pypi.org/simple/`)." + }, + "sdist": { + "$ref": "#/definitions/sdist" + }, + "wheels": { + "$ref": "#/definitions/wheels" + }, + "attestation-identities": { + "type": "array", + "markdownDescription": "A recording of the attestations for any file recorded for this package.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["kind"], + "properties": { + "kind": { + "type": "string", + "markdownDescription": "The unique identity of the Trusted Publisher." + } + } + } + }, + "tool": { + "$ref": "#/definitions/tool" + } + } + } + }, + "tool": { + "$ref": "#/definitions/tool" + } + } + } + }, + "oneOf": [ + { + "$ref": "#/definitions/1.0" + } + ], + "type": "object" +} diff --git a/noxfile.py b/noxfile.py index 698e82f9d..484a8d39a 100644 --- a/noxfile.py +++ b/noxfile.py @@ -89,6 +89,7 @@ def linkcheck(session): "--keep-going", # be strict "source", # where the rst files are located "build", # where to put the check output + *session.posargs, ) diff --git a/pug_sphinx_extensions/__init__.py b/pug_sphinx_extensions/__init__.py new file mode 100644 index 000000000..00d91da3c --- /dev/null +++ b/pug_sphinx_extensions/__init__.py @@ -0,0 +1,86 @@ +import os +import pathlib +import urllib + +import sphinx.application +import sphinx.util.logging + + +DOMAIN = "packaging.python.org" + + +logger = sphinx.util.logging.getLogger(__name__) + + +def resolve_local_html_link(app: sphinx.application.Sphinx, url_path: str) -> str: + """Takes path of a link pointing an HTML render of the current project, + and returns local path of the referenced document. + + Support links to renders from both the `html` and `dirhtml` builders. + + Example: + + .. code-block:: python + + >>> resolve_local_html_link('https://packaging.python.org/en/latest/flow/') + '{srcdir}/flow.rst' + >>> resolve_local_html_link('https://packaging.python.org/en/latest/flow.html') + '{srcdir}/flow.rst' + >>> resolve_local_html_link('https://packaging.python.org/en/latest/specifications/schemas/') + '{srcdir}/specifications/schemas/index.rst' + >>> resolve_local_html_link('https://packaging.python.org/en/latest/specifications/schemas/build-details-v1.0.schema.json') + '{html_extra_path0}/specifications/schemas/build-details-v1.0.schema.json' + + """ + # Search for document in html_extra_path + for entry in app.config.html_extra_path: + candidate = (app.confdir / entry / url_path).resolve() + if candidate.is_dir(): + candidate = candidate / "index.html" + if candidate.exists(): + return os.fspath(candidate) + # Convert html path to source path + url_path = url_path.removesuffix("/") # Normalize + if url_path.endswith(".html"): + document = url_path.removesuffix(".html") + elif (candidate := f"{url_path}/index") in app.project.docnames: + document = candidate + else: + document = url_path + return app.env.doc2path(document) + + +def rewrite_local_uri(app: sphinx.application.Sphinx, uri: str) -> str: + """Replace remote URIs targeting https://packaging.python.org/en/latest/... + with local ones, so that local changes are taken into account by linkcheck. + + Additionally, resolve local relative links to html_extra_path. + """ + local_uri = uri + parsed = urllib.parse.urlparse(uri) + # Links to https://packaging.python.org/en/latest/... + if parsed.hostname == DOMAIN and parsed.path.startswith("/en/latest/"): + document = parsed.path.removeprefix("/en/latest/") + local_uri = resolve_local_html_link(app, document) + logger.verbose( + f"{uri!s} is a remote URL that points to local sources, " + "replacing it with a local URL in linkcheck to take new changes " + "into account (pass -vv for more info)" + ) + logger.debug(f"Replacing linkcheck URL {uri!r} with {local_uri!r}") + # Local relative links + if not parsed.scheme and not parsed.netloc and parsed.path: + full_path = pathlib.Path(app.env.docname).parent / parsed.path + local_uri = resolve_local_html_link(app, os.fspath(full_path)) + if local_uri != uri: + logger.verbose(f"Local linkcheck URL {uri!r} resolved as {local_uri!r}") + return local_uri + + +def setup(app: sphinx.application.Sphinx) -> dict[str, bool]: + app.connect("linkcheck-process-uri", rewrite_local_uri) + + return { + "parallel_read_safe": True, + "parallel_write_safe": True, + } diff --git a/scripts/update_uv_build_version.py b/scripts/update_uv_build_version.py new file mode 100644 index 000000000..69fefba27 --- /dev/null +++ b/scripts/update_uv_build_version.py @@ -0,0 +1,64 @@ +# /// script +# requires-python = ">= 3.12" +# dependencies = [ +# "httpx>=0.28.1,<0.29", +# "packaging>=25.0", +# ] +# /// +import os +import re +from pathlib import Path + +import httpx +from packaging.utils import parse_wheel_filename +from packaging.version import Version + + +def main(): + response = httpx.get( + "https://pypi.org/simple/uv-build/", + headers={"Accept": "application/vnd.pypi.simple.v1+json"}, + ) + response.raise_for_status() + data = response.json() + current_release = None + for file in data["files"]: + if not file["filename"].endswith(".whl"): + continue + _name, version, _build, _tags = parse_wheel_filename(file["filename"]) + if version.is_prerelease: + continue + if current_release is None or version > current_release: + current_release = version + + [major, minor, _patch] = current_release.release + if major != 0: + raise NotImplementedError("The script needs to be updated for uv 1.x") + upper_bound = Version(f"{major}.{minor + 1}.0") + + repository_root = Path(__file__).parent.parent + existing = repository_root.joinpath( + "source/shared/build-backend-tabs.rst" + ).read_text() + replacement = f'requires = ["uv_build >= {current_release}, <{upper_bound}"]' + searcher = re.compile(re.escape('requires = ["uv_build') + ".*" + re.escape('"]')) + if not searcher.search(existing): + raise RuntimeError("Could not `uv-build` entry") + updated = searcher.sub(replacement, existing) + + if existing != updated: + print("Updating source/shared/build-backend-tabs.rst") + Path("source/shared/build-backend-tabs.rst").write_text(updated) + if github_output := os.environ.get("GITHUB_OUTPUT"): + with open(github_output, "a") as f: + f.write(f"version={current_release}\n") + f.write("updated=true\n") + else: + print("Already up-to-date source/shared/build-backend-tabs.rst") + if github_output := os.environ.get("GITHUB_OUTPUT"): + with open(github_output, "a") as f: + f.write("updated=false\n") + + +if __name__ == "__main__": + main() diff --git a/source/conf.py b/source/conf.py index 7a05613ea..ccb828b6e 100644 --- a/source/conf.py +++ b/source/conf.py @@ -2,6 +2,11 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information import os +import pathlib +import sys + +_ROOT = pathlib.Path(__file__).resolve().parent.parent +sys.path.append(os.fspath(_ROOT)) # Some options are only enabled for the main packaging.python.org deployment builds RTD_BUILD = bool(os.getenv("READTHEDOCS")) @@ -22,6 +27,7 @@ root_doc = "index" extensions = [ + "pug_sphinx_extensions", "sphinx.ext.extlinks", "sphinx.ext.intersphinx", "sphinx.ext.todo", @@ -132,27 +138,31 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-the-linkcheck-builder linkcheck_ignore = [ - "http://localhost:\\d+", - "https://packaging.python.org/en/latest/specifications/schemas/.*", - "https://test.pypi.org/project/example-package-YOUR-USERNAME-HERE", - "https://pypi.org/manage/*", - "https://test.pypi.org/manage/*", + r"http://localhost:\d+", + r"https://test\.pypi\.org/project/example-package-YOUR-USERNAME-HERE", + r"https://pypi\.org/manage/.*", + r"https://test\.pypi\.org/manage/.*", # Temporarily ignored. Ref: # https://github.com/pypa/packaging.python.org/pull/1308#issuecomment-1775347690 - "https://www.breezy-vcs.org/*", + r"https://www\.breezy-vcs\.org/.*", # Ignore while StackOverflow is blocking GitHub CI. Ref: # https://github.com/pypa/packaging.python.org/pull/1474 - "https://stackoverflow.com/*", - "https://pyscaffold.org/*", - "https://anaconda.org", - "https://www.cisa.gov/sbom", - "https://developers.redhat.com/products/softwarecollections/overview", + r"https://stackoverflow\.com/.*", + r"https://pyscaffold\.org/.*", + r"https://anaconda\.org", + r"https://www\.cisa\.gov/sbom", + r"https://developers\.redhat\.com/products/softwarecollections/overview", + r"https://math-atlas\.sourceforge\.net/?", + r"https://click\.palletsprojects\.com/.*", + r"https://typer\.tiangolo\.com/.*", + r"https://www.npmjs.com/.*", ] linkcheck_retries = 5 # Ignore anchors for common targets when we know they likely won't be found linkcheck_anchors_ignore_for_url = [ # GitHub synthesises anchors in JavaScript, so Sphinx can't find them in the HTML r"https://github\.com/", + r"https://docs\.github\.com/", # While PyPI has its botscraping defenses active, Sphinx can't resolve the anchors # https://github.com/pypa/packaging.python.org/issues/1744 r"https://pypi\.org/", @@ -209,7 +219,6 @@ "tox": ("https://tox.wiki/en/latest/", None), "twine": ("https://twine.readthedocs.io/en/stable/", None), "virtualenv": ("https://virtualenv.pypa.io/en/stable/", None), - "warehouse": ("https://warehouse.pypa.io/", None), } # -- Options for todo extension -------------------------------------------------------- diff --git a/source/contribute.rst b/source/contribute.rst index cf5314b8d..f512dd30d 100644 --- a/source/contribute.rst +++ b/source/contribute.rst @@ -103,7 +103,6 @@ If you are not familiar with reStructuredText (RST) syntax, please read `this gu before translating on Weblate. **Do not translate the text in reference directly** - When translating the text in reference, please do not translate them directly. | Wrong: Translate the following text directly: diff --git a/source/discussions/setup-py-deprecated.rst b/source/discussions/setup-py-deprecated.rst index 6bcd15b58..b13ce190b 100644 --- a/source/discussions/setup-py-deprecated.rst +++ b/source/discussions/setup-py-deprecated.rst @@ -115,7 +115,7 @@ A possible replacement solution (among others) is to rely on setuptools-scm_: * ``python -m setuptools_scm`` -.. _setuptools-scm: https://setuptools-scm.readthedocs.io/en/latest/usage/#as-cli-tool +.. _setuptools-scm: https://setuptools-scm.readthedocs.io/en/latest/usage#as-cli-tool Remaining commands diff --git a/source/glossary.rst b/source/glossary.rst index 6a592125f..40c041f4c 100644 --- a/source/glossary.rst +++ b/source/glossary.rst @@ -160,7 +160,7 @@ Glossary A string with valid SPDX license expression syntax, including one or more SPDX :term:`License Identifier`\(s), - which describes a :term:`Project`'s license(s) + which describes a :term:`Distribution Archive`'s license(s) and how they inter-relate. Examples: ``GPL-3.0-or-later``, @@ -287,8 +287,7 @@ Glossary PyPA is a working group that maintains many of the relevant projects in Python packaging. They maintain a site at :doc:`pypa.io `, host projects on `GitHub - `_ and `Bitbucket - `_, and discuss issues on the + `_, and discuss issues on the `distutils-sig mailing list `_ and `the Python Discourse forum `__. diff --git a/source/guides/creating-command-line-tools.rst b/source/guides/creating-command-line-tools.rst index 8266fffdb..cbe8b3bb0 100644 --- a/source/guides/creating-command-line-tools.rst +++ b/source/guides/creating-command-line-tools.rst @@ -40,33 +40,22 @@ named after the main module: def greet( - name: Annotated[str, typer.Argument(help="The (last, if --gender is given) name of the person to greet")] = "", - gender: Annotated[str, typer.Option(help="The gender of the person to greet")] = "", - knight: Annotated[bool, typer.Option(help="Whether the person is a knight")] = False, + name: Annotated[str, typer.Argument(help="The (last, if --title is given) name of the person to greet")] = "", + title: Annotated[str, typer.Option(help="The preferred title of the person to greet")] = "", + doctor: Annotated[bool, typer.Option(help="Whether the person is a doctor (MD or PhD)")] = False, count: Annotated[int, typer.Option(help="Number of times to greet the person")] = 1 ): - greeting = "Greetings, dear " - masculine = gender == "masculine" - feminine = gender == "feminine" - if gender or knight: - salutation = "" - if knight: - salutation = "Sir " - elif masculine: - salutation = "Mr. " - elif feminine: - salutation = "Ms. " - greeting += salutation - if name: - greeting += f"{name}!" + greeting = "Greetings, " + if doctor and not title: + title = "Dr." + if not name: + if title: + name = title.lower().rstrip(".") else: - pronoun = "her" if feminine else "his" if masculine or knight else "its" - greeting += f"what's-{pronoun}-name" - else: - if name: - greeting += f"{name}!" - elif not gender: - greeting += "friend!" + name = "friend" + if title: + greeting += f"{title} " + greeting += f"{name}!" for i in range(0, count): print(greeting) @@ -145,12 +134,14 @@ Let's test it: .. code-block:: console - $ greet --knight Lancelot - Greetings, dear Sir Lancelot! - $ greet --gender feminine Parks - Greetings, dear Ms. Parks! - $ greet --gender masculine - Greetings, dear Mr. what's-his-name! + $ greet + Greetings, friend! + $ greet --doctor Brennan + Greetings, Dr. Brennan! + $ greet --title Ms. Parks + Greetings, Ms. Parks! + $ greet --title Mr. + Greetings, Mr. mr! Since this example uses ``typer``, you could now also get an overview of the program's usage by calling it with the ``--help`` option, or configure completions via the ``--install-completion`` option. @@ -160,10 +151,10 @@ To just run the program without installing it permanently, use ``pipx run``, whi .. code-block:: console - $ pipx run --spec . greet --knight + $ pipx run --spec . greet --doctor This syntax is a bit impractical, however; as the name of the entry point we defined above does not match the package name, -we need to state explicitly which executable script to run (even though there is only on in existence). +we need to state explicitly which executable script to run (even though there is only one in existence). There is, however, a more practical solution to this problem, in the form of an entry point specific to ``pipx run``. The same can be defined as follows in :file:`pyproject.toml`: @@ -179,7 +170,7 @@ default one and run it, which makes this command possible: .. code-block:: console - $ pipx run . --knight + $ pipx run . --doctor Conclusion ========== diff --git a/source/guides/github-actions-ci-cd-sample/publish-to-test-pypi.yml b/source/guides/github-actions-ci-cd-sample/publish-to-pypi.yml similarity index 90% rename from source/guides/github-actions-ci-cd-sample/publish-to-test-pypi.yml rename to source/guides/github-actions-ci-cd-sample/publish-to-pypi.yml index 8813a0392..155f82555 100644 --- a/source/guides/github-actions-ci-cd-sample/publish-to-test-pypi.yml +++ b/source/guides/github-actions-ci-cd-sample/publish-to-pypi.yml @@ -8,11 +8,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.x" - name: Install pypa/build @@ -24,7 +24,7 @@ jobs: - name: Build a binary wheel and a source tarball run: python3 -m build - name: Store the distribution packages - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: python-package-distributions path: dist/ @@ -44,7 +44,7 @@ jobs: steps: - name: Download all the dists - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v6 with: name: python-package-distributions path: dist/ @@ -66,7 +66,7 @@ jobs: steps: - name: Download all the dists - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v6 with: name: python-package-distributions path: dist/ diff --git a/source/guides/licensing-examples-and-user-scenarios.rst b/source/guides/licensing-examples-and-user-scenarios.rst index 2c25ddfb0..b6cdfe327 100644 --- a/source/guides/licensing-examples-and-user-scenarios.rst +++ b/source/guides/licensing-examples-and-user-scenarios.rst @@ -6,8 +6,8 @@ Licensing examples and user scenarios ===================================== -:pep:`639` has specified the way to declare a project's license and paths to -license files and other legally required information. +:pep:`639` has specified the way to declare a :term:`Distribution Archive`'s +license and paths to license files and other legally required information. This document aims to provide clear guidance how to migrate from the legacy to the standardized way of declaring licenses. Make sure your preferred build backend supports :pep:`639` before @@ -53,7 +53,7 @@ Or, if the project used :file:`setup.cfg`, in its ``[metadata]`` table: [metadata] license = MIT -The output Core Metadata for the distribution packages would then be: +The output Core Metadata for the :term:`Distribution Package` would then be: .. code-block:: email @@ -63,8 +63,9 @@ The output Core Metadata for the distribution packages would then be: The :file:`LICENSE` file would be stored at :file:`/setuptools-{VERSION}/LICENSE` in the sdist and :file:`/setuptools-{VERSION}.dist-info/licenses/LICENSE` in the wheel, and unpacked from there into the site directory (e.g. -:file:`site-packages/`) on installation; :file:`/` is the root of the respective archive -and ``{VERSION}`` the version of the Setuptools release in the Core Metadata. +:file:`site-packages/`) on installation; :file:`/` is the root of the respective +archive and ``{VERSION}`` the version of the Setuptools release in the Core +Metadata. .. _licensing-example-advanced: @@ -83,7 +84,7 @@ directories; specifically: ordered-set==3.1.1 more_itertools==8.8.0 -The license expressions for these projects are: +The appropriate license expressions are: .. code-block:: text @@ -287,7 +288,7 @@ and make sure to remove any legacy ``license`` table subkeys or ``License ::`` classifiers. Your existing ``license`` value may already be valid as one (e.g. ``MIT``, ``Apache-2.0 OR BSD-2-Clause``, etc); otherwise, check the `SPDX license list `__ for the identifier -that matches the license used in your project. +that matches the license used. Make sure to list your license files under ``license-files`` under ``[project]`` in :file:`pyproject.toml` @@ -312,12 +313,11 @@ to describe the licenses involved and the relationship between them. In short, ``License-1 AND License-2`` mean that *both* licenses apply -to your project, or parts of it (for example, you included a file -under another license), and ``License-1 OR License-2`` means that -*either* of the licenses can be used, at the user's option (for example, -you want to allow users a choice of multiple licenses). You can use -parenthesis (``()``) for grouping to form expressions that cover even the most -complex situations. +(for example, you included a file under another license), and +``License-1 OR License-2`` means that *either* of the licenses can be used, at +the user's option (for example, you want to allow users a choice of multiple +licenses). You can use parenthesis (``()``) for grouping to form expressions +that cover even the most complex situations. In your project config file, enter your license expression under ``license`` (``[project]`` table of :file:`pyproject.toml`), diff --git a/source/guides/modernize-setup-py-project.rst b/source/guides/modernize-setup-py-project.rst index 5b6ab3c26..1f71d1973 100644 --- a/source/guides/modernize-setup-py-project.rst +++ b/source/guides/modernize-setup-py-project.rst @@ -67,7 +67,7 @@ For more details: * :ref:`distributing-packages` * :ref:`pyproject-build-system-table` -* :doc:`pip:reference/build-system/pyproject-toml` +* :doc:`pip:reference/build-system` How to handle additional build-time dependencies? @@ -128,7 +128,7 @@ For some projects this isolation is unwanted and it can be deactivated as follow For more details: -* :doc:`pip:reference/build-system/pyproject-toml` +* :doc:`pip:reference/build-system` How to handle packaging metadata? @@ -244,5 +244,5 @@ Where to read more about this? ============================== * :ref:`pyproject-toml-spec` -* :doc:`pip:reference/build-system/pyproject-toml` +* :doc:`pip:reference/build-system` * :doc:`setuptools:build_meta` diff --git a/source/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows.rst b/source/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows.rst index 1ee562cf7..a3d893c9f 100644 --- a/source/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows.rst +++ b/source/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows.rst @@ -75,7 +75,7 @@ Let's begin! 🚀 .. attention:: - For security reasons, you must require `manual approval `_ + For security reasons, you must require `manual approval `_ on each run for the ``pypi`` environment. @@ -87,13 +87,13 @@ Creating a workflow definition GitHub CI/CD workflows are declared in YAML files stored in the ``.github/workflows/`` directory of your repository. -Let's create a ``.github/workflows/publish-to-test-pypi.yml`` +Let's create a ``.github/workflows/publish-to-pypi.yml`` file. Start it with a meaningful name and define the event that should make GitHub run this workflow: -.. literalinclude:: github-actions-ci-cd-sample/publish-to-test-pypi.yml +.. literalinclude:: github-actions-ci-cd-sample/publish-to-pypi.yml :language: yaml :end-before: jobs: @@ -107,7 +107,7 @@ build the distribution packages. First, we'll define the job for building the dist packages of your project and storing them for later use: -.. literalinclude:: github-actions-ci-cd-sample/publish-to-test-pypi.yml +.. literalinclude:: github-actions-ci-cd-sample/publish-to-pypi.yml :language: yaml :start-at: jobs: :end-before: Install pypa/build @@ -119,7 +119,7 @@ And now we can build the dists from source and store them. In this example, we'll use the ``build`` package. So add this to the steps list: -.. literalinclude:: github-actions-ci-cd-sample/publish-to-test-pypi.yml +.. literalinclude:: github-actions-ci-cd-sample/publish-to-pypi.yml :language: yaml :start-at: Install pypa/build :end-before: publish-to-pypi @@ -136,7 +136,7 @@ UI nicely. Additionally, it allows acquiring an OpenID Connect token that the ``pypi-publish`` actions needs to implement secretless Trusted Publishing to PyPI. -.. literalinclude:: github-actions-ci-cd-sample/publish-to-test-pypi.yml +.. literalinclude:: github-actions-ci-cd-sample/publish-to-pypi.yml :language: yaml :start-after: path: dist/ :end-before: steps: @@ -149,7 +149,7 @@ Publishing the distribution to PyPI Finally, add the following steps at the end: -.. literalinclude:: github-actions-ci-cd-sample/publish-to-test-pypi.yml +.. literalinclude:: github-actions-ci-cd-sample/publish-to-pypi.yml :language: yaml :start-after: id-token: write :end-before: publish-to-testpypi: @@ -175,7 +175,7 @@ Now, repeat these steps and create another job for publishing to the TestPyPI package index under the ``jobs`` section: -.. literalinclude:: github-actions-ci-cd-sample/publish-to-test-pypi.yml +.. literalinclude:: github-actions-ci-cd-sample/publish-to-pypi.yml :language: yaml :start-at: publish-to-testpypi @@ -191,7 +191,7 @@ This paragraph showcases the whole workflow after following the above guide. .. collapse:: Click here to display the entire GitHub Actions CI/CD workflow definition - .. literalinclude:: github-actions-ci-cd-sample/publish-to-test-pypi.yml + .. literalinclude:: github-actions-ci-cd-sample/publish-to-pypi.yml :language: yaml That's all, folks! diff --git a/source/guides/supporting-multiple-python-versions.rst b/source/guides/supporting-multiple-python-versions.rst index 8c128ed91..7e945aa53 100644 --- a/source/guides/supporting-multiple-python-versions.rst +++ b/source/guides/supporting-multiple-python-versions.rst @@ -62,7 +62,7 @@ of many continuous-integration systems. There are two hosted services which when used in conjunction provide automated testing across Linux, Mac and Windows: - - `Travis CI `_ provides both a Linux and a macOS + - `Travis CI `_ provides both a Linux and a macOS environment. The Linux environment is Ubuntu 12.04 LTS Server Edition 64 bit while the macOS is 10.9.2 at the time of writing. - `Appveyor `_ provides a Windows environment diff --git a/source/guides/supporting-windows-using-appveyor.rst b/source/guides/supporting-windows-using-appveyor.rst index 0044d8c5e..e884dd976 100644 --- a/source/guides/supporting-windows-using-appveyor.rst +++ b/source/guides/supporting-windows-using-appveyor.rst @@ -237,6 +237,6 @@ For reference, the SDK setup support script is listed here: :linenos: .. _Appveyor: https://www.appveyor.com/ -.. _Travis: https://travis-ci.org/ +.. _Travis: https://travis-ci.com/ .. _GitHub: https://github.com .. _Bitbucket: https://bitbucket.org/ diff --git a/source/guides/tool-recommendations.rst b/source/guides/tool-recommendations.rst index 1ba36ed61..bf8d93d5a 100644 --- a/source/guides/tool-recommendations.rst +++ b/source/guides/tool-recommendations.rst @@ -109,6 +109,11 @@ Do **not** use :ref:`distutils`, which is deprecated, and has been removed from the standard library in Python 3.12, although it still remains available from setuptools. +.. _extension-module-tool-recommendations: + +Build backends for extension modules +------------------------------------ + For packages with :term:`extension modules `, it is best to use a build system with dedicated support for the language the extension is written in, for example: diff --git a/source/guides/writing-pyproject-toml.rst b/source/guides/writing-pyproject-toml.rst index 318fe0d51..a1a595a13 100644 --- a/source/guides/writing-pyproject-toml.rst +++ b/source/guides/writing-pyproject-toml.rst @@ -296,10 +296,10 @@ You can also specify the format explicitly, like this: ``license`` and ``license-files`` --------------------------------- -As per :pep:`639` licenses should be declared with two fields: +As per :pep:`639`, licenses should be declared with two fields: -- ``license`` is an :term:`SPDX license expression ` consisting - of one or more :term:`license identifiers `. +- ``license`` is an :term:`SPDX license expression ` + consisting of one or more :term:`license identifiers `. - ``license-files`` is a list of license file glob patterns. A previous PEP had specified ``license`` to be a table with a ``file`` or a @@ -314,11 +314,13 @@ backend>` now support the new format as shown in the following table. - flit-core [#flit-core-pep639]_ - pdm-backend - poetry-core + - uv-build * - 1.27.0 - 77.0.3 - 3.12 - 2.4.0 - - `not yet `_ + - 2.2.0 + - 0.7.19 .. _license: @@ -348,10 +350,11 @@ As a general rule, it is a good idea to use a standard, well-known license, both to avoid confusion and because some organizations avoid software whose license is unapproved. -If your project is licensed with a license that doesn't have an existing SPDX -identifier, you can create a custom one in format ``LicenseRef-[idstring]``. -The custom identifiers must follow the SPDX specification, -`clause 10.1 `_ of the version 2.2 or any later compatible one. +If your :term:`Distribution Archive` is licensed with a license that doesn't +have an existing SPDX identifier, you can create a custom one in format +``LicenseRef-[idstring]``. The custom identifiers must follow the SPDX +specification, `clause 10.1 `_ of the version 2.2 or any later +compatible one. .. code-block:: toml @@ -585,7 +588,6 @@ A full example .. _pypi-search-pip: https://pypi.org/search?q=pip .. _classifier-list: https://pypi.org/classifiers .. _requires-python-blog-post: https://iscinumpy.dev/post/bound-version-constraints/#pinning-the-python-version-is-special -.. _poetry-pep639-issue: https://github.com/python-poetry/poetry/issues/9670 .. _pytest: https://pytest.org .. _pygments: https://pygments.org .. _rest: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html diff --git a/source/overview.rst b/source/overview.rst index 8c68036a7..70ef2d058 100644 --- a/source/overview.rst +++ b/source/overview.rst @@ -339,7 +339,7 @@ originated and where the technologies below work best: Bringing your own kernel ^^^^^^^^^^^^^^^^^^^^^^^^ -Most operating systems support some form of classical virtualization, +Most desktop operating systems support some form of classical virtualization, running applications packaged as images containing a full operating system of their own. Running these virtual machines, or VMs, is a mature approach, widespread in data center environments. @@ -348,9 +348,13 @@ These techniques are mostly reserved for larger scale deployments in data centers, though certain complex applications can benefit from this packaging. The technologies are Python agnostic, and include: -* `Vagrant `_ -* `VHD `_, `AMI `_, and :doc:`other formats ` -* `OpenStack `_ - A cloud management system in Python, with extensive VM support +* KVM on Linux +* Hyper-V on Windows +* `VHD `_, + `AMI `_, + and :doc:`other formats ` +* `OpenStack `_ - + A cloud management system written in Python, with extensive VM support Bringing your own hardware ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/source/shared/build-backend-tabs.rst b/source/shared/build-backend-tabs.rst index 7fc3a61da..64ef0bf2f 100644 --- a/source/shared/build-backend-tabs.rst +++ b/source/shared/build-backend-tabs.rst @@ -32,3 +32,11 @@ [build-system] requires = ["pdm-backend >= 2.4.0"] build-backend = "pdm.backend" + +.. tab:: uv-build + + .. code-block:: toml + + [build-system] + requires = ["uv_build >= 0.9.21, <0.10.0"] + build-backend = "uv_build" diff --git a/source/specifications/core-metadata.rst b/source/specifications/core-metadata.rst index 550c6e55a..eb9a03ff6 100644 --- a/source/specifications/core-metadata.rst +++ b/source/specifications/core-metadata.rst @@ -6,7 +6,7 @@ Core metadata specifications ============================ -This page describes version 2.4, approved in August 2024. +This page describes version 2.5, approved in September 2025. Fields defined in the following specification should be considered valid, complete and not subject to change. The required fields are: @@ -50,11 +50,11 @@ Metadata-Version .. versionadded:: 1.0 Version of the file format; legal values are "1.0", "1.1", "1.2", "2.1", -"2.2", "2.3", and "2.4". +"2.2", "2.3", "2.4", and "2.5". -Automated tools consuming metadata SHOULD warn if ``metadata_version`` is +Automated tools consuming metadata SHOULD warn if ``metadata-version`` is greater than the highest version they support, and MUST fail if -``metadata_version`` has a greater major version than the highest +``metadata-version`` has a greater major version than the highest version they support (as described in the :ref:`Version specifier specification `, the major version is the value before the first dot). @@ -133,6 +133,16 @@ only, and indicates that the field value was calculated at wheel build time, and may not be the same as the value in the sdist or in other wheels for the project. +Note in particular that if you have obtained a prebuilt wheel, you cannot +assume that a field which is not marked as ``Dynamic`` will have the same value +in other wheels, as some wheels are not built directly from the sdist, but are +modified from existing wheels (the ``auditwheel`` tool does this, for example, +and it's commonly used when building wheels for PyPI). Such modifications +*could* include changing metadata (even non-dynamic metadata). Similarly, if +you have a sdist and a wheel which you didn't build from that sdist, you cannot +assume that the wheel's metadata matches that of the sdist, even if the field +is not marked as ``Dynamic``. + Full details of the semantics of ``Dynamic`` are described in :pep:`643`. .. _core-metadata-platform: @@ -473,6 +483,12 @@ Text string that is a valid SPDX :term:`license expression `, as specified in :doc:`/specifications/license-expression`. +Note that the expression in this field only applies to the +:term:`Distribution Archive` containing the metadata with this field (e.g., +:term:`Source Distribution ` or :term:`Wheel`), +not the project overall or other files related to the project (including other +distribution archives). + Examples:: License-Expression: MIT @@ -708,6 +724,101 @@ user SHOULD be warned and the value ignored to avoid ambiguity. Tools MAY choose to raise an error when reading an invalid name for older metadata versions. +.. _core-metadata-import-name: + +Import-Name (multiple use) +========================== + +.. versionadded:: 2.5 + +A string containing an import name that the project exclusively provides when +installed. The specified import name MUST be a valid Python identifier or can +be empty. The import names listed in this field MUST be importable when the +project is installed on *some* platform for the same version of the project. +This implies that the metadata MUST be consistent across all sdists and wheels +for a project release. + +An import name MAY be followed by a semicolon and the term "private" +(e.g. ``; private``) with any amount of whitespace surrounding the semicolon. +This signals to tools that the import name is not part of the public API for +the project. + +Projects SHOULD list all the shortest import names that are exclusively provided +by the project. 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-Name`` and/or ``Import-Namespace``. + +If a project lists the same name in both ``Import-Name`` and +``Import-Namespace``, tools MUST raise an error due to ambiguity. + +Tools SHOULD raise an error when two projects that are about to be installed +list names that overlap in each other's ``Import-Name`` entries, or when a +project has an entry in ``Import-Name`` that overlaps with another project's +``Import-Namespace`` entries. This is to avoid projects unexpectedly shadowing +another project's code. 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. + +Projects MAY have an empty ``Import-Name`` field in their metadata to represent +a project with no import names (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. + +Examples:: + + Import-Name: PIL + Import-Name: _private_module ; private + Import-Name: zope.interface + Import-Name: + + +.. _core-metadata-import-namespace: + +Import-Namespace (multiple use) +=============================== + +.. versionadded:: 2.5 + +A string containing an import name that the project provides when installed, but +not exclusively. The specified import name MUST be a valid Python identifier. +This field is used for namespace packages where multiple projects can contribute +to the same import namespace. Projects all listing the same import name in +``Import-Namespace`` can be installed together without shadowing each other. + +An import name MAY be followed by a semicolon and the term "private" (e.g. +``; private``) with any amount of whitespace surrounding the semicolon. This +signals to tools that the import name is not part of the public API for the +project. + +Projects SHOULD list all the shortest import names that are exclusively provided +by the project. 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-Name`` and/or ``Import-Namespace``. + +The import names listed in this field MUST be importable when the project is +installed on *some* platform for the same version of the project. This implies +that the metadata MUST be consistent across all sdists and wheels for a project +release. + +If a project lists the same name in both ``Import-Name`` and +``Import-Namespace``, tools MUST raise an error due to ambiguity. + +Note that ``Import-Namespace`` CANNOT be empty like ``Import-Name``. + +Examples:: + + Import-Namespace: zope + Import-Name: _private_module ; private + + Rarely Used Fields ================== @@ -923,30 +1034,42 @@ Example:: History ======= -- August 2024: Core metadata 2.4 was approved through :pep:`639`. +- March 2001: Core metadata 1.0 was approved through :pep:`241`. - - Added the ``License-Expression`` field. - - Added the ``License-File`` field. +- April 2003: Core metadata 1.1 was approved through :pep:`314`. -- March 2022: Core metadata 2.3 was approved through :pep:`685`. +- February 2010: Core metadata 1.2 was approved through :pep:`345`. - - Restricted extra names to be normalized. +- February 2018: Core metadata 2.1 was approved through :pep:`566`. + + - Added ``Description-Content-Type`` and ``Provides-Extra``. + - Added canonical method for transforming metadata to JSON. + - Restricted the grammar of the ``Name`` field. - October 2020: Core metadata 2.2 was approved through :pep:`643`. - Added the ``Dynamic`` field. -- February 2018: Core metadata 2.1 was approved through :pep:`566`. +- March 2022: Core metadata 2.3 was approved through :pep:`685`. - - Added ``Description-Content-Type`` and ``Provides-Extra``. - - Added canonical method for transforming metadata to JSON. - - Restricted the grammar of the ``Name`` field. + - Restricted extra names to be normalized. -- February 2010: Core metadata 1.2 was approved through :pep:`345`. +- August 2024: Core metadata 2.4 was approved through :pep:`639`. -- April 2003: Core metadata 1.1 was approved through :pep:`314`: + - Added the ``License-Expression`` field. + - Added the ``License-File`` field. -- March 2001: Core metadata 1.0 was approved through :pep:`241`. +- August 2025: Clarified that ``Dynamic`` only affects how fields + must be treated when building a wheel from a sdist, not when modifying + a wheel. + +- September 2025: Core metadata 2.5 was approved through :pep:`794`. + + - Added the ``Import-Name`` field. + - Added the ``Import-Namespace`` field. + +- October 2025: Clarified that ``License-Expression`` applies to the containing + distribution file and not the project itself. ---- diff --git a/source/specifications/dependency-groups.rst b/source/specifications/dependency-groups.rst index 22e4cba0d..a35afb475 100644 --- a/source/specifications/dependency-groups.rst +++ b/source/specifications/dependency-groups.rst @@ -4,15 +4,15 @@ Dependency Groups ================= -This specification defines Dependency Groups, a mechanism for storing package +This specification defines dependency groups, a mechanism for storing package requirements in ``pyproject.toml`` files such that they are not included in project metadata when it is built. -Dependency Groups are suitable for internal development use-cases like linting +Dependency groups are suitable for internal development use-cases like linting and testing, as well as for projects which are not built for distribution, like collections of related scripts. -Fundamentally, Dependency Groups should be thought of as being a standardized +Fundamentally, dependency groups should be thought of as being a standardized subset of the capabilities of ``requirements.txt`` files (which are ``pip``-specific). @@ -38,7 +38,7 @@ and a similar table which defines ``docs``, ``test``, and ``coverage`` groups:: The ``[dependency-groups]`` Table --------------------------------- -Dependency Groups are defined as a table in ``pyproject.toml`` named +Dependency groups are defined as a table in ``pyproject.toml`` named ``dependency-groups``. The ``dependency-groups`` table contains an arbitrary number of user-defined keys, each of which has, as its value, a list of requirements. @@ -103,9 +103,9 @@ Package Building Build backends MUST NOT include Dependency Group data in built distributions as package metadata. This means that sdist ``PKG-INFO`` and wheel ``METADATA`` -files should not include referenceable fields containing Dependency Groups. +files should not include referenceable fields containing dependency groups. -It is, however, valid to use Dependency Groups in the evaluation of dynamic +It is, however, valid to use dependency groups in the evaluation of dynamic metadata, and ``pyproject.toml`` files included in sdists will still contain ``[dependency-groups]``. However, the table's contents are not part of a built package's interfaces. @@ -114,28 +114,28 @@ Installing Dependency Groups & Extras ------------------------------------- There is no syntax or specification-defined interface for installing or -referring to Dependency Groups. Tools are expected to provide dedicated +referring to dependency groups. Tools are expected to provide dedicated interfaces for this purpose. Tools MAY choose to provide the same or similar interfaces for interacting -with Dependency Groups as they do for managing extras. Tools authors are +with dependency groups as they do for managing extras. Tools authors are advised that the specification does not forbid having an extra whose name matches a Dependency Group. Separately, users are advised to avoid creating -Dependency Groups whose names match extras, and tools MAY treat such matching +dependency groups whose names match extras, and tools MAY treat such matching as an error. Validation and Compatibility ---------------------------- -Tools supporting Dependency Groups may want to validate data before using it. +Tools supporting dependency groups may want to validate data before using it. When implementing such validation, authors should be aware of the possibility of future extensions to the specification, so that they do not unnecessarily emit errors or warnings. Tools SHOULD error when evaluating or processing unrecognized data in -Dependency Groups. +dependency groups. -Tools SHOULD NOT eagerly validate the contents of *all* Dependency Groups +Tools SHOULD NOT eagerly validate the contents of *all* dependency groups unless they have a need to do so. This means that in the presence of the following data, most tools should allow @@ -151,7 +151,7 @@ the ``foo`` group to be used and only error if the ``bar`` group is used: There are several known cases of tools which have good cause to be stricter. Linters and validators are an example, as their purpose is to - validate the contents of all Dependency Groups. + validate the contents of all dependency groups. Reference Implementation ======================== diff --git a/source/specifications/dependency-specifiers.rst b/source/specifications/dependency-specifiers.rst index e8e3229ff..99886563c 100644 --- a/source/specifications/dependency-specifiers.rst +++ b/source/specifications/dependency-specifiers.rst @@ -63,7 +63,7 @@ Versions may be specified according to the rules of the :ref:`Version specifier specification `. (Note: URI is defined in :rfc:`std-66 <3986>`):: - version_cmp = wsp* '<' | '<=' | '!=' | '==' | '>=' | '>' | '~=' | '===' + version_cmp = wsp* '<=' | '<' | '!=' | '===' | '==' | '>=' | '>' | '~=' version = wsp* ( letterOrDigit | '-' | '_' | '.' | '*' | '+' | '!' )+ version_one = version_cmp version wsp* version_many = version_one (',' version_one)* (',' wsp*)? @@ -142,7 +142,7 @@ document we limit the acceptable values for identifiers to that regex. A full redefinition of name may take place in a future metadata PEP. The regex (run with re.IGNORECASE) is:: - ^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$ + ^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])\Z .. _dependency-specifiers-extras: @@ -339,7 +339,7 @@ Complete Grammar The complete parsley grammar:: wsp = ' ' | '\t' - version_cmp = wsp* <'<=' | '<' | '!=' | '==' | '>=' | '>' | '~=' | '==='> + version_cmp = wsp* <'<=' | '<' | '!=' | '===' | '==' | '>=' | '>' | '~='> version = wsp* <( letterOrDigit | '-' | '_' | '.' | '*' | '+' | '!' )+> version_one = version_cmp:op version:v wsp* -> (op, v) version_many = version_one:v1 (',' version_one)*:v2 (',' wsp*)? -> [v1] + v2 @@ -526,6 +526,11 @@ History in use since late 2022. - April 2025: Added ``extras`` and ``dependency_groups`` for :ref:`lock-file-spec` as approved through :pep:`751`. +- August 2025: The suggested name validation regex was fixed to match the field + specification (it previously finished with ``$`` instead of ``\Z``, + incorrectly permitting trailing newlines) +- December 2025: Ensure ``===`` before ``==`` in grammar, to allow arbitrary + equality comparisons to be parsed. References diff --git a/source/specifications/direct-url-data-structure.rst b/source/specifications/direct-url-data-structure.rst index 0d243652d..a82537f0a 100644 --- a/source/specifications/direct-url-data-structure.rst +++ b/source/specifications/direct-url-data-structure.rst @@ -236,122 +236,7 @@ JSON Schema The following JSON Schema can be used to validate the contents of ``direct_url.json``: -.. code-block:: - - { - "$schema": "https://json-schema.org/draft/2019-09/schema", - "title": "Direct URL Data", - "description": "Data structure that can represent URLs to python projects and distribution artifacts such as VCS source trees, local source trees, source distributions and wheels.", - "definitions": { - "URL": { - "type": "string", - "format": "uri" - }, - "DirInfo": { - "type": "object", - "properties": { - "editable": { - "type": ["boolean", "null"] - } - } - }, - "VCSInfo": { - "type": "object", - "properties": { - "vcs": { - "type": "string", - "enum": [ - "git", - "hg", - "bzr", - "svn" - ] - }, - "requested_revision": { - "type": "string" - }, - "commit_id": { - "type": "string" - }, - "resolved_revision": { - "type": "string" - } - }, - "required": [ - "vcs", - "commit_id" - ] - }, - "ArchiveInfo": { - "type": "object", - "properties": { - "hash": { - "type": "string", - "pattern": "^\\w+=[a-f0-9]+$", - "deprecated": true - }, - "hashes": { - "type": "object", - "patternProperties": { - "^[a-f0-9]+$": { - "type": "string" - } - } - } - } - } - }, - "allOf": [ - { - "type": "object", - "properties": { - "url": { - "$ref": "#/definitions/URL" - } - }, - "required": [ - "url" - ] - }, - { - "anyOf": [ - { - "type": "object", - "properties": { - "dir_info": { - "$ref": "#/definitions/DirInfo" - } - }, - "required": [ - "dir_info" - ] - }, - { - "type": "object", - "properties": { - "vcs_info": { - "$ref": "#/definitions/VCSInfo" - } - }, - "required": [ - "vcs_info" - ] - }, - { - "type": "object", - "properties": { - "archive_info": { - "$ref": "#/definitions/ArchiveInfo" - } - }, - "required": [ - "archive_info" - ] - } - ] - } - ] - } +.. literalinclude:: ../../extra/specifications/schemas/direct-url.schema.json Examples ======== diff --git a/source/specifications/file-yanking.rst b/source/specifications/file-yanking.rst new file mode 100644 index 000000000..4ab8cd5cc --- /dev/null +++ b/source/specifications/file-yanking.rst @@ -0,0 +1,92 @@ +.. _file-yanking: + +============ +File Yanking +============ + +.. note:: + + This specification was originally defined in + :pep:`592`. + +.. note:: + + :pep:`592` includes changes to the HTML and JSON index APIs. + These changes are documented in the :ref:`simple-repository-api` + under :ref:`HTML - Project Detail ` + and :ref:`JSON - Project Detail `. + +Specification +============= + +Links in the simple repository **MAY** have a ``data-yanked`` attribute +which may have no value, or may have an arbitrary string as a value. The +presence of a ``data-yanked`` attribute **SHOULD** be interpreted as +indicating that the file pointed to by this particular link has been +"Yanked", and should not generally be selected by an installer, except +under specific scenarios. + +The value of the ``data-yanked`` attribute, if present, is an arbitrary +string that represents the reason for why the file has been yanked. Tools +that process the simple repository API **MAY** surface this string to +end users. + +The yanked attribute is not immutable once set, and may be rescinded in +the future (and once rescinded, may be reset as well). Thus API users +**MUST** be able to cope with a yanked file being "unyanked" (and even +yanked again). + +Installers +---------- + +The desirable experience for users is that once a file is yanked, when +a human being is currently trying to directly install a yanked file, that +it fails as if that file had been deleted. However, when a human did that +awhile ago, and now a computer is just continuing to mechanically follow +the original order to install the now yanked file, then it acts as if it +had not been yanked. + +An installer **MUST** ignore yanked releases, if the selection constraints +can be satisfied with a non-yanked version, and **MAY** refuse to use a +yanked release even if it means that the request cannot be satisfied at all. +An implementation **SHOULD** choose a policy that follows the spirit of the +intention above, and that prevents "new" dependencies on yanked +releases/files. + +What this means is left up to the specific installer, to decide how to best +fit into the overall usage of their installer. However, there are two +suggested approaches to take: + +1. Yanked files are always ignored, unless they are the only file that + matches a version specifier that "pins" to an exact version using + either ``==`` (without any modifiers that make it a range, such as + ``.*``) or ``===``. Matching this version specifier should otherwise + be done as per :ref:`the version specifiers specification + ` for things like local versions, zero padding, + etc. +2. Yanked files are always ignored, unless they are the only file that + matches what a lock file (such as ``Pipfile.lock`` or ``poetry.lock``) + specifies to be installed. In this case, a yanked file **SHOULD** not + be used when creating or updating a lock file from some input file or + command. + +Regardless of the specific strategy that an installer chooses for deciding +when to install yanked files, an installer **SHOULD** emit a warning when +it does decide to install a yanked file. That warning **MAY** utilize the +value of the ``data-yanked`` attribute (if it has a value) to provide more +specific feedback to the user about why that file had been yanked. + + +Mirrors +------- + +Mirrors can generally treat yanked files one of two ways: + +1. They may choose to omit them from their simple repository API completely, + providing a view over the repository that shows only "active", unyanked + files. +2. They may choose to include yanked files, and additionally mirror the + ``data-yanked`` attribute as well. + +Mirrors **MUST NOT** mirror a yanked file without also mirroring the +``data-yanked`` attribute for it. diff --git a/source/specifications/index.rst b/source/specifications/index.rst index 68d95ab98..c375654a2 100644 --- a/source/specifications/index.rst +++ b/source/specifications/index.rst @@ -17,3 +17,4 @@ and for proposing new ones, is documented on section-package-indices section-python-description-formats section-reproducible-environments + schemas/index.rst diff --git a/source/specifications/name-normalization.rst b/source/specifications/name-normalization.rst index ba3246b63..560d956b5 100644 --- a/source/specifications/name-normalization.rst +++ b/source/specifications/name-normalization.rst @@ -17,7 +17,7 @@ underscore and hyphen. It must start and end with a letter or number. This means that valid project names are limited to those which match the following regex (run with :py:data:`re.IGNORECASE`):: - ^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$ + ^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])\Z .. _name-normalization: @@ -53,3 +53,6 @@ History :pep:`503 <503#normalized-names>`. - November 2015: The specification of valid names was approved through :pep:`508 <508#names>`. +- August 2025: The suggested name validation regex was fixed to match the field + specification (it previously finished with ``$`` instead of ``\Z``, + incorrectly permitting trailing newlines) diff --git a/source/specifications/platform-compatibility-tags.rst b/source/specifications/platform-compatibility-tags.rst index 6feb18cda..b4c14a4c0 100644 --- a/source/specifications/platform-compatibility-tags.rst +++ b/source/specifications/platform-compatibility-tags.rst @@ -82,6 +82,11 @@ decide how to best use the ABI tag. Platform Tag ============ +.. important:: + Platform tags are dependent on the versioning of the operating system or + platform they represent and may change over time as the underlying platform + changes its versioning. + Basic platform tags ------------------- @@ -199,12 +204,13 @@ artefact of Apple's official macOS naming scheme). The schema for compatibility tags is :file:`macosx_{x}_{y}_{arch}`, indicating that the wheel is compatible with macOS ``x.y`` or later on the architecture ``arch``. -The values of ``x`` and ``y`` correspond to the major and minor version number of -the macOS release, respectively. They must both be positive integers, with the -``x`` value being ``>= 10``. The version number always includes a major *and* -minor version, even if Apple's official version numbering only refers to -the major value. For example, ``macosx_11_0_arm64`` indicates compatibility -with macOS 11 or later. +For macOS 10, the tag is :file:`macosx_10_{y}_{arch}`, where ``y`` corresponds +to the minor version number of the macOS release. For macOS 11 and higher, the +tag is :file:`macosx_{x}_0_{arch}`, where ``x`` corresponds to the major +version number of the macOS release. Following the published macOS major +versions, the ``x`` value is either ``10 <= x <= 15``, or ``>=26`` and +corresponding to the year of the macOS release. For example, +``macosx_11_0_arm64`` indicates compatibility with macOS 11 or later. macOS binaries can be compiled for a single architecture, or can include support for multiple architectures in the same binary (sometimes called "fat" binaries). @@ -351,7 +357,7 @@ Compressed Tag Sets To allow for compact filenames of bdists that work with more than one compatibility tag triple, each tag in a filename can instead be a -'.'-separated, sorted, set of tags. For example, pip, a pure-Python +'.'-separated, sorted, collection of tags. For example, pip, a pure-Python package that is written to run under Python 2 and 3 with the same source code, could distribute a bdist with the tag ``py2.py3-none-any``. The full list of simple tags is:: diff --git a/source/specifications/project-status-markers.rst b/source/specifications/project-status-markers.rst new file mode 100644 index 000000000..90df74441 --- /dev/null +++ b/source/specifications/project-status-markers.rst @@ -0,0 +1,89 @@ +.. _project-status-markers: + +====================== +Project Status Markers +====================== + +.. note:: + + This specification was originally defined in + :pep:`792`. + +.. note:: + + :pep:`792` includes changes to the HTML and JSON index APIs. + These changes are documented in the :ref:`simple-repository-api` + under :ref:`HTML - Project Detail ` + and :ref:`JSON - Project Detail `. + +Specification +============= + +A project always has exactly one status. If no status is explicitly noted, +then the project is considered to be in the ``active`` state. + +Indices **MAY** implement any subset of the status markers specified, +as applicable to their needs. + +This standard does not prescribe *which* principals (i.e. project maintainers, +index administrators, etc.) are allowed to set and unset which statuses. + +``active`` +---------- + +Description: The project is active. This is the default status for a project. + +Index semantics: + +* The index hosting the project **MUST** allow uploads of new distributions to + the project. +* The index **MUST** offer existing distributions of the project for download. + +Installer semantics: none. + +``archived`` +------------ + +Description: The project does not expect to be updated in the future. + +Index semantics: + +* The index hosting the project **MUST NOT** allow uploads of new distributions to + the project. +* The index **MUST** offer existing distributions of the project for download. + +Installer semantics: + +* Installers **MAY** produce warnings about a project's archival. + +``quarantined`` +--------------- + +Description: The project is considered generally unsafe for use, e.g. due to +malware. + +Index semantics: + +* The index hosting the project **MUST NOT** allow uploads of new distributions to + the project. +* The index **MUST NOT** offer any distributions of the project for download. + +Installer semantics: + +* Installers **MAY** produce warnings about a project's quarantine, although + doing so is effectively moot (as the index will not offer any distributions + for installation). + +``deprecated`` +-------------- + +Description: The project is considered obsolete, and may have been superseded +by another project. + +Index semantics: + +* This status shares the same semantics as ``active``. + +Installer semantics: + +* Installers **MAY** produce warnings about a project's deprecation. diff --git a/source/specifications/pylock-toml.rst b/source/specifications/pylock-toml.rst index d21294cf9..342e608c5 100644 --- a/source/specifications/pylock-toml.rst +++ b/source/specifications/pylock-toml.rst @@ -672,12 +672,12 @@ See :ref:`pylock-packages-archive-hashes`. - **Type**: array of tables - **Required?**: no -- **Inspiration**: :ref:` provenance-object` +- **Inspiration**: :ref:`provenance-object` - A recording of the attestations for **any** file recorded for this package. - If available, tools SHOULD include the attestation identities found. - Publisher-specific keys are to be included in the table as-is (i.e. top-level), following the spec at - :ref:` index-hosted-attestations`. + :ref:`index-hosted-attestations`. .. _pylock-packages-attestation-identities-kind: @@ -687,7 +687,7 @@ See :ref:`pylock-packages-archive-hashes`. - **Type**: string - **Required?**: yes -- **Inspiration**: :ref:` provenance-object` +- **Inspiration**: :ref:`provenance-object` - The unique identity of the Trusted Publisher. @@ -698,9 +698,9 @@ See :ref:`pylock-packages-archive-hashes`. - **Type**: table - **Required?**: no -- **Inspiration**: :ref:` pyproject-tool-table` +- **Inspiration**: :ref:`pyproject-tool-table` - Similar usage as that of the :ref:`pylock-tool` table from the - :ref:` pyproject-toml-spec`, but at the package version level instead + :ref:`pyproject-toml-spec`, but at the package version level instead of at the lock file level (which is also available via :ref:`pylock-tool`). - Data recorded in the table MUST be disposable (i.e. it MUST NOT affect installation). diff --git a/source/specifications/pyproject-toml.rst b/source/specifications/pyproject-toml.rst index 4ce9b7484..48f35599e 100644 --- a/source/specifications/pyproject-toml.rst +++ b/source/specifications/pyproject-toml.rst @@ -61,6 +61,10 @@ table then the default values as specified above should be used. If the table is specified but is missing required fields then the tool should consider it an error. +Tools may choose to present an error to the user if the file exists, +``[build-system]`` table is missing, and there is no clear indication +that the project should be built (e.g., no setup.py/setup.cfg or other +build configuration files, and no ``[project]`` table). To provide a type-specific representation of the resulting data from the TOML file for illustrative purposes only, the following @@ -136,6 +140,8 @@ The complete list of keys allowed in the ``[project]`` table are: - ``dynamic`` - ``entry-points`` - ``gui-scripts`` +- ``import-names`` +- ``import-namespaces`` - ``keywords`` - ``license`` - ``license-files`` @@ -259,6 +265,12 @@ Text string that is a valid SPDX as specified in :doc:`/specifications/license-expression`. Tools SHOULD validate and perform case normalization of the expression. +This key should **only** be specified if the license expression for any +and all distribution files created by a build backend using the +:file:`pyproject.toml` is the same as the one specified. If the license +expression will differ then it should either be specified as dynamic or +not set at all. + Legacy specification '''''''''''''''''''' @@ -466,6 +478,97 @@ matching :ref:`Provides-Extra ` metadata. +.. _pyproject-toml-import-names: + +``import-names`` +---------------- + +- TOML_ type: array of strings +- Corresponding :ref:`core metadata ` field: + :ref:`Import-Name ` + +An array of strings specifying the import names that the project exclusively +provides when installed. Each string MUST be a valid Python identifier or can +be empty. An import name MAY be followed by a semicolon and the term "private" +(e.g. ``"; private"``) with any amount of whitespace surrounding the semicolon. + +Projects SHOULD list all the shortest import names that are exclusively provided +by the project. 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-names`` and/or ``import-namespaces``. 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-names`` and +``import-namespaces``, then tools MUST raise an error due to ambiguity. + +Projects MAY set ``import-names`` to an empty array to represent a project with +no import names (i.e. there are no Python modules of any kind in the +distribution file). + +Build back-ends MAY support dynamically calculating the value if the user +declares the key in ``project.dynamic``. + +Examples: + +.. code-block:: toml + + [project] + name = "pillow" + import-names = ["PIL"] + +.. code-block:: toml + + [project] + name = "myproject" + import-names = ["mypackage", "_private_module ; private"] + + +.. _pyproject-toml-import-namespaces: + +``import-namespaces`` +--------------------- + +- TOML_ type: array of strings +- Corresponding :ref:`core metadata ` field: + :ref:`Import-Namespace ` + +An array of strings specifying the import names that the project provides when +installed, but not exclusively. Each string MUST be a valid Python identifier. +An import name MAY be followed by a semicolon and the term "private" (e.g. +``"; private"``) with any amount of whitespace surrounding the semicolon. Note +that unlike ``import-names``, ``import-namespaces`` CANNOT be an empty array. + +Projects SHOULD list all the shortest import names that are exclusively provided +by the project. 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-names`` and/or ``import-namespaces``. + +This field is used for namespace packages where multiple projects can contribute +to the same import namespace. Projects all listing the same import name in +``import-namespaces`` can be installed together without shadowing each other. + +If a project lists the same name in both ``import-names`` and +``import-namespaces``, then tools MUST raise an error due to ambiguity. + +Build back-ends MAY support dynamically calculating the value if the user +declares the key in ``project.dynamic``. + +Example: + +.. code-block:: toml + + [project] + name = "zope-interface" + import-namespaces = ["zope"] + import-names = ["zope.interface"] + + .. _pyproject-toml-dynamic: .. _declaring-project-metadata-dynamic: @@ -540,5 +643,10 @@ History - December 2024: The ``license`` key was redefined, the ``license-files`` key was added and ``License::`` classifiers were deprecated through :pep:`639`. +- September 2025: Clarity that the ``license`` key applies to all distribution + files generated from the :file:`pyproject.toml` file. + +- October 2025: The ``import-names`` and ``import-namespaces`` keys were added + through :pep:`794`. .. _TOML: https://toml.io diff --git a/source/specifications/schemas/index.rst b/source/specifications/schemas/index.rst new file mode 100644 index 000000000..a80891975 --- /dev/null +++ b/source/specifications/schemas/index.rst @@ -0,0 +1,8 @@ +.. _`packaging-schemas`: + +PyPA schemas +############ + +- `direct_url.json `_ +- `build-details.json `_ +- `pylock.toml `_ diff --git a/source/specifications/section-package-indices.rst b/source/specifications/section-package-indices.rst index 73004b4d3..1fcefe6ff 100644 --- a/source/specifications/section-package-indices.rst +++ b/source/specifications/section-package-indices.rst @@ -7,4 +7,6 @@ Package Index Interfaces pypirc simple-repository-api + file-yanking index-hosted-attestations + project-status-markers diff --git a/source/specifications/simple-repository-api.rst b/source/specifications/simple-repository-api.rst index d18c425db..3b9a2ccac 100644 --- a/source/specifications/simple-repository-api.rst +++ b/source/specifications/simple-repository-api.rst @@ -12,14 +12,15 @@ and "**OPTIONAL**"" in this document are to be interpreted as described in The interface for querying available package versions and retrieving packages from an index server comes in two forms: -HTML and JSON. +:ref:`HTML ` and +:ref:`JSON `. .. _simple-repository-api-base: -Base HTML API -============= +Base API +======== -A repository that implements the simple API is defined by its base URL, this is +A repository that implements the simple API is defined by its base URL. This is the top level URL that all additional URLs are below. The API is named the "simple" repository due to the fact that PyPI's base URL is ``https://pypi.org/simple/``. @@ -28,11 +29,115 @@ the top level URL that all additional URLs are below. The API is named the URL (so given PyPI's URL, a URL of ``/foo/`` would be ``https://pypi.org/simple/foo/``. +Normalized Names +---------------- + +This spec references the concept of a "normalized" project name. As per +:ref:`the name normalization specification ` +the only valid characters in a name are the ASCII alphabet, ASCII numbers, +``.``, ``-``, and ``_``. The name should be lowercased with all runs of the +characters ``.``, ``-``, or ``_`` replaced with a single ``-`` character. This +can be implemented in Python with the ``re`` module:: + + import re + + def normalize(name): + return re.sub(r"[-_.]+", "-", name).lower() + +.. _simple-repository-api-versioning: + +Versioning PyPI's Simple API +---------------------------- + +This spec proposes the inclusion of a meta tag on the responses of every +successful request to a simple API page, which contains a name attribute +of ``pypi:repository-version``, and a content that is a :ref:`version specifiers +specification ` compatible +version number, which is further constrained to ONLY be Major.Minor, and +none of the additional features supported by :ref:`the version specifiers +specification `. + +This would end up looking like: + +.. code-block:: html + + + +When interpreting the repository version: + +* Incrementing the major version is used to signal a backwards + incompatible change such that existing clients would no longer be + expected to be able to meaningfully use the API. +* Incrementing the minor version is used to signal a backwards + compatible change such that existing clients would still be + expected to be able to meaningfully use the API. + +It is left up to the discretion of any future specs as to what +specifically constitutes a backwards incompatible vs compatible change +beyond the broad suggestion that existing clients will be able to +"meaningfully" continue to use the API, and can include adding, +modifying, or removing existing features. + +It is expectation of this spec that the major version will never be +incremented, and any future major API evolutions would utilize a +different mechanism for API evolution. However the major version +is included to disambiguate with future versions (e.g. a hypothetical +simple api v2 that lived at /v2/, but which would be confusing if the +repository-version was set to a version >= 2). + +API Version History +~~~~~~~~~~~~~~~~~~~ + +This section contains only an abbreviated history of changes, +as marked by the API version number. For a full history of changes including +changes made before API versioning, see :ref:`History `. + +- API version 1.0: Initial version of the API, declared with :pep:`629`. +- API version 1.1: Added ``versions``, ``files[].size``, and ``files[].upload-time`` metadata + to the JSON serialization, declared with :pep:`700`. +- API version 1.2: Added repository "tracks" metadata, declared with :pep:`708`. +- API version 1.3: Added provenance metadata, declared with :pep:`740`. +- API version 1.4: Added status markers, declared with :pep:`792`. + +Clients +~~~~~~~ + +Clients interacting with the simple API **SHOULD** introspect each +response for the repository version, and if that data does not exist +**MUST** assume that it is version 1.0. + +When encountering a major version greater than expected, clients +**MUST** hard fail with an appropriate error message for the user. + +When encountering a minor version greater than expected, clients +**SHOULD** warn users with an appropriate message. + +Clients **MAY** still continue to use feature detection in order to +determine what features a repository uses. + +.. _simple-repository-html-serialization: + +HTML Serialization +------------------ + +.. _simple-repository-html-project-list: + +The following constraints apply to all HTML serialized responses described in +this spec: + +* All HTML responses **MUST** be a valid HTML5 document. +* HTML responses **MAY** contain one or more ``meta`` tags in the + ```` section. The semantics of these tags are defined below. + +Project List +~~~~~~~~~~~~ Within a repository, the root URL (``/`` for this spec which represents the base URL) **MUST** be a valid HTML5 page with a single anchor element per project in -the repository. The text of the anchor tag **MUST** be the name of -the project and the href attribute **MUST** link to the URL for that particular +the repository. + +The text of each anchor tag **MUST** be the name of +the project and the ``href`` attribute **MUST** link to the URL for that particular project. As an example: .. code-block:: html @@ -45,14 +150,26 @@ project. As an example: +.. _simple-repository-html-project-detail: + +Project Detail +~~~~~~~~~~~~~~ + Below the root URL is another URL for each individual project contained within -a repository. The format of this URL is ``//`` where the ```` -is replaced by the normalized name for that project, so a project named -"HolyGrail" would have a URL like ``/holygrail/``. This URL must respond with -a valid HTML5 page with a single anchor element per file for the project. The -href attribute **MUST** be a URL that links to the location of the file for -download, and the text of the anchor tag **MUST** match the final path -component (the filename) of the URL. The URL **SHOULD** include a hash in the +a repository. The format of this URL is ``//``, where the ```` +is replaced by the normalized name for that project. + +.. tip:: + + For example, a project named "HolyGrail" would have a URL like + ``/holygrail/``. + +The project detail URL must respond with a valid HTML5 page with a single +anchor element per file for the project. The ``href`` attribute **MUST** be a +URL that links to the location of the file for download, and the text of the +anchor tag **MUST** match the final path component (the filename) of the URL. + +Each file URL **SHOULD** include a hash in the form of a URL fragment with the following syntax: ``#=``, where ```` is the lowercase name of the hash function (such as ``sha256``) and ```` is the hex encoded digest. @@ -125,6 +242,22 @@ In addition to the above, the following constraints are placed on the API: In the attribute value, < and > have to be HTML encoded as ``<`` and ``>``, respectively. +* A repository **MAY** include a ``data-yanked`` attribute on a file link. + + The ``data-yanked`` attribute may have no value, or may have an + arbitrary string as a value. The presence of a ``data-yanked`` attribute + **SHOULD** be interpreted as indicating that the file pointed to by this + particular link has been "Yanked", and should not generally be selected by + an installer, except under specific scenarios. + + The value of the ``data-yanked`` attribute, if present, is an arbitrary + string that represents the reason for why the file has been yanked. + + .. note:: + + The semantics of how tools should handle yanked files is + described in :ref:`file-yanking`. + * A repository **MAY** include a ``data-provenance`` attribute on a file link. The value of this attribute **MUST** be a fully qualified URL, signaling that the file's provenance can be found at that URL. This URL **MUST** represent @@ -138,168 +271,22 @@ In addition to the above, the following constraints are placed on the API: The format of the linked provenance is defined in :ref:`index-hosted-attestations`. -Normalized Names ----------------- - -This spec references the concept of a "normalized" project name. As per -:ref:`the name normalization specification ` -the only valid characters in a name are the ASCII alphabet, ASCII numbers, -``.``, ``-``, and ``_``. The name should be lowercased with all runs of the -characters ``.``, ``-``, or ``_`` replaced with a single ``-`` character. This -can be implemented in Python with the ``re`` module:: - - import re - - def normalize(name): - return re.sub(r"[-_.]+", "-", name).lower() - -.. _simple-repository-api-yank: - -Adding "Yank" Support to the Simple API -======================================= - -Links in the simple repository **MAY** have a ``data-yanked`` attribute -which may have no value, or may have an arbitrary string as a value. The -presence of a ``data-yanked`` attribute **SHOULD** be interpreted as -indicating that the file pointed to by this particular link has been -"Yanked", and should not generally be selected by an installer, except -under specific scenarios. - -The value of the ``data-yanked`` attribute, if present, is an arbitrary -string that represents the reason for why the file has been yanked. Tools -that process the simple repository API **MAY** surface this string to -end users. - -The yanked attribute is not immutable once set, and may be rescinded in -the future (and once rescinded, may be reset as well). Thus API users -**MUST** be able to cope with a yanked file being "unyanked" (and even -yanked again). - - -Installers ----------- - -The desirable experience for users is that once a file is yanked, when -a human being is currently trying to directly install a yanked file, that -it fails as if that file had been deleted. However, when a human did that -awhile ago, and now a computer is just continuing to mechanically follow -the original order to install the now yanked file, then it acts as if it -had not been yanked. - -An installer **MUST** ignore yanked releases, if the selection constraints -can be satisfied with a non-yanked version, and **MAY** refuse to use a -yanked release even if it means that the request cannot be satisfied at all. -An implementation **SHOULD** choose a policy that follows the spirit of the -intention above, and that prevents "new" dependencies on yanked -releases/files. - -What this means is left up to the specific installer, to decide how to best -fit into the overall usage of their installer. However, there are two -suggested approaches to take: - -1. Yanked files are always ignored, unless they are the only file that - matches a version specifier that "pins" to an exact version using - either ``==`` (without any modifiers that make it a range, such as - ``.*``) or ``===``. Matching this version specifier should otherwise - be done as per :ref:`the version specifiers specification - ` for things like local versions, zero padding, - etc. -2. Yanked files are always ignored, unless they are the only file that - matches what a lock file (such as ``Pipfile.lock`` or ``poetry.lock``) - specifies to be installed. In this case, a yanked file **SHOULD** not - be used when creating or updating a lock file from some input file or - command. - -Regardless of the specific strategy that an installer chooses for deciding -when to install yanked files, an installer **SHOULD** emit a warning when -it does decide to install a yanked file. That warning **MAY** utilize the -value of the ``data-yanked`` attribute (if it has a value) to provide more -specific feedback to the user about why that file had been yanked. - - -Mirrors -------- - -Mirrors can generally treat yanked files one of two ways: - -1. They may choose to omit them from their simple repository API completely, - providing a view over the repository that shows only "active", unyanked - files. -2. They may choose to include yanked files, and additionally mirror the - ``data-yanked`` attribute as well. - -Mirrors **MUST NOT** mirror a yanked file without also mirroring the -``data-yanked`` attribute for it. - -.. _simple-repository-api-versioning: - -Versioning PyPI's Simple API -============================ - -This spec proposes the inclusion of a meta tag on the responses of every -successful request to a simple API page, which contains a name attribute -of ``pypi:repository-version``, and a content that is a :ref:`version specifiers -specification ` compatible -version number, which is further constrained to ONLY be Major.Minor, and -none of the additional features supported by :ref:`the version specifiers -specification `. - -This would end up looking like: +* A repository **MAY** include ``pypi:project-status`` and + ``pypi:project-status-reason`` meta tags on the response itself. -.. code-block:: html - - - -When interpreting the repository version: + The value of ``pypi:project-status`` **MUST** be a valid + project status marker, while the value of + ``pypi:project-status-reason`` **MUST** be an arbitrary string if present. -* Incrementing the major version is used to signal a backwards - incompatible change such that existing clients would no longer be - expected to be able to meaningfully use the API. -* Incrementing the minor version is used to signal a backwards - compatible change such that existing clients would still be - expected to be able to meaningfully use the API. - -It is left up to the discretion of any future specs as to what -specifically constitutes a backwards incompatible vs compatible change -beyond the broad suggestion that existing clients will be able to -"meaningfully" continue to use the API, and can include adding, -modifying, or removing existing features. - -It is expectation of this spec that the major version will never be -incremented, and any future major API evolutions would utilize a -different mechanism for API evolution. However the major version -is included to disambiguate with future versions (e.g. a hypothetical -simple api v2 that lived at /v2/, but which would be confusing if the -repository-version was set to a version >= 2). - -API Version History -------------------- - -This section contains only an abbreviated history of changes, -as marked by the API version number. For a full history of changes including -changes made before API versioning, see :ref:`History `. - -- API version 1.0: Initial version of the API, declared with :pep:`629`. -- API version 1.1: Added ``versions``, ``files[].size``, and ``files[].upload-time`` metadata - to the JSON serialization, declared with :pep:`700`. -- API version 1.2: Added repository "tracks" metadata, declared with :pep:`708`. -- API version 1.3: Added provenance metadata, declared with :pep:`740`. - -Clients -------- + .. note:: -Clients interacting with the simple API **SHOULD** introspect each -response for the repository version, and if that data does not exist -**MUST** assume that it is version 1.0. + The set of valid project status markers and their semantics is described + in :ref:`project-status-markers`. -When encountering a major version greater than expected, clients -**MUST** hard fail with an appropriate error message for the user. + .. note:: -When encountering a minor version greater than expected, clients -**SHOULD** warn users with an appropriate message. - -Clients **MAY** still continue to use feature detection in order to -determine what features a repository uses. + The ``pypi:project-status`` and ``pypi:project-status-reason`` meta tags + were added with API version 1.4. .. _simple-repository-api-metadata-file: @@ -403,8 +390,8 @@ JSON Serialization ------------------ The URL structure from :ref:`the base HTML API specification -` still applies, as this spec only adds an additional -serialization format for the already existing API. +` still applies, as this spec only adds +an additional serialization format for the already existing API. The following constraints apply to all JSON serialized responses described in this spec: @@ -435,6 +422,8 @@ spec: * Keys (at any level) with a leading underscore are reserved as private for index server use. No future standard will assign a meaning to any such key. +.. _simple-repository-json-project-list: + Project List ~~~~~~~~~~~~ @@ -450,7 +439,7 @@ As an example: { "meta": { - "api-version": "1.3" + "api-version": "1.4" }, "projects": [ {"name": "Frob"}, @@ -478,6 +467,7 @@ As an example: best thought of as a set, but both JSON and HTML lack the functionality to have sets. +.. _simple-repository-json-project-detail: Project Detail ~~~~~~~~~~~~~~ @@ -492,6 +482,28 @@ This URL must respond with a JSON encoded dictionary that has four keys: - ``name``: The normalized name of the project. - ``files``: A list of dictionaries, each one representing an individual file. - ``meta``: The general response metadata as `described earlier `__. + + In addition to the general response metadata, the project detail ``meta`` + dictionary **MAY** also include the following: + + - ``project-status``: If present, this **MUST** be a valid project status marker. + + .. note:: + + The set of valid project status markers and their semantics is described + in :ref:`project-status-markers`. + + .. note:: + + The ``project-status`` key was added with API version 1.4. + + - ``project-status-reason``: If present, this **MUST** be an arbitrary string + description of the project status. + + .. note:: + + The ``project-status-reason`` key was added with API version 1.4. + - ``versions``: A list of version strings specifying all of the project versions uploaded for this project. The value of ``versions`` is logically a set, and as such may not contain duplicates, and the order of the versions is @@ -582,8 +594,13 @@ Each individual file dictionary has the following keys: file has been yanked, or a non empty, but otherwise arbitrary, string to indicate that a file has been yanked with a specific reason. If the ``yanked`` key is present and is a truthy value, then it **SHOULD** be interpreted as indicating that the - file pointed to by the ``url`` field has been "Yanked" as per :ref:`the API - yank specification `. + file pointed to by the ``url`` field has been "Yanked". + + .. note:: + + The semantics of how tools should handle yanked files is + described in :ref:`file-yanking`. + - ``size``: A **mandatory** key. It **MUST** contain an integer which is the file size in bytes. .. note:: @@ -618,7 +635,9 @@ As an example: { "meta": { - "api-version": "1.3" + "api-version": "1.4", + "project-status": "active", + "project-status-reason": "this project is not yet haunted" }, "name": "holygrail", "files": [ @@ -891,46 +910,6 @@ which version+format a specific repository URL was configured for, and when maki a request to that server, emit an ``Accept`` header that *only* includes the correct content type. - -TUF Support - PEP 458 ---------------------- - -:pep:`458` requires that all API responses are hashable and that they can be uniquely -identified by a path relative to the repository root. For a Simple API repository, the -target path is the Root of our API (e.g. ``/simple/`` on PyPI). This creates -challenges when accessing the API using a TUF client instead of directly using a -standard HTTP client, as the TUF client cannot handle the fact that a target could -have multiple different representations that all hash differently. - -:pep:`458` does not specify what the target path should be for the Simple API, but -TUF requires that the target paths be "file-like", in other words, a path like -``simple/PROJECT/`` is not acceptable, because it technically points to a -directory. - -The saving grace is that the target path does not *have* to actually match the URL -being fetched from the Simple API, and it can just be a sigil that the fetching code -knows how to transform into the actual URL that needs to be fetched. This same thing -can hold true for other aspects of the actual HTTP request, such as the ``Accept`` -header. - -Ultimately figuring out how to map a directory to a filename is out of scope for this -spec (but it would be in scope for :pep:`458`), and this spec defers making a decision -about how exactly to represent this inside of :pep:`458` metadata. - -However, it appears that the current WIP branch against pip that attempts to implement -:pep:`458` is using a target path like ``simple/PROJECT/index.html``. This could be -modified to include the API version and serialization format using something like -``simple/PROJECT/vnd.pypi.simple.vN.FORMAT``. So the v1 HTML format would be -``simple/PROJECT/vnd.pypi.simple.v1.html`` and the v1 JSON format would be -``simple/PROJECT/vnd.pypi.simple.v1.json``. - -In this case, since ``text/html`` is an alias to ``application/vnd.pypi.simple.v1+html`` -when interacting through TUF, it likely will make the most sense to normalize to the -more explicit name. - -Likewise the ``latest`` metaversion should not be included in the targets, only -explicitly declared versions should be supported. - Recommendations --------------- @@ -1006,3 +985,5 @@ History * June 2023: renaming the field which provides package metadata independently from a package, in :pep:`714` * November 2024: provenance metadata in the HTML and JSON formats, in :pep:`740` +* July 2025: project status markers in the HTML and JSON formats, in :pep:`792` +* July 2025: layout changes (dedicated page for file yanking, introduce concepts before API details) diff --git a/source/specifications/version-specifiers.rst b/source/specifications/version-specifiers.rst index c0b544160..13015794f 100644 --- a/source/specifications/version-specifiers.rst +++ b/source/specifications/version-specifiers.rst @@ -1016,8 +1016,9 @@ Arbitrary equality Arbitrary equality comparisons are simple string equality operations which do not take into account any of the semantic information such as zero padding or -local versions. This operator also does not support prefix matching as the -``==`` operator does. +local versions. The comparison MUST treat ASCII letters case-insensitively, e.g. +by lowercasing, and is unspecified for non-ASCII text. This operator also does +not support prefix matching as the ``==`` operator does. The primary use case for arbitrary equality is to allow for specifying a version which cannot otherwise be represented by this specification. This operator is @@ -1271,3 +1272,4 @@ History - August 2014: This specification was approved through :pep:`440`. - May 2025: Clarify that development releases are a form of pre-release when they are handled. +- Nov 2025: Make arbitrary equality case insensitivity explicit. diff --git a/source/tutorials/managing-dependencies.rst b/source/tutorials/managing-dependencies.rst index db3b82533..bb67a60e3 100644 --- a/source/tutorials/managing-dependencies.rst +++ b/source/tutorials/managing-dependencies.rst @@ -177,3 +177,5 @@ and techniques, listed in alphabetical order, to see if one of them is a better structured as a distributable Python package with a valid ``pyproject.toml`` file. By contrast, Pipenv explicitly avoids making the assumption that the application being worked on will support distribution as a ``pip``-installable Python package. +* `uv `__ for a single tool that covers the entire project + management workflow, including dependency management, packaging, and publishing. diff --git a/source/tutorials/packaging-projects.rst b/source/tutorials/packaging-projects.rst index f2c0851ba..4f69de20b 100644 --- a/source/tutorials/packaging-projects.rst +++ b/source/tutorials/packaging-projects.rst @@ -220,7 +220,7 @@ following this tutorial. your package will work on. For a complete list of classifiers, see https://pypi.org/classifiers/. - ``license`` is the :term:`SPDX license expression ` of - your package. + your :term:`Distribution Archive` files. - ``license-files`` is the list of glob paths to the license files, relative to the directory where :file:`pyproject.toml` is located. - ``urls`` lets you list any number of extra links to show on PyPI. @@ -250,12 +250,12 @@ if you'd like. Creating a LICENSE ------------------ -It's important for every package uploaded to the Python Package Index to include -a license. This tells users who install your package the terms under which they -can use your package. For help picking a license, see -https://choosealicense.com/. Once you have chosen a license, open -:file:`LICENSE` and enter the license text. For example, if you had chosen the -MIT license: +It's important for every :term:`Distribution Archive` uploaded to the Python +Package Index to include a license. This tells users who install your +:term:`Distribution Archive` the terms under which they can use it. For help +picking a license, see https://choosealicense.com/. Once you have chosen a +license, open :file:`LICENSE` and enter the license text. For example, if you +had chosen the MIT license: .. code-block:: text