diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ad28f35ce2a..a0a6ab363d9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -683,11 +683,15 @@ peps/pep-0804.rst @pradyunsg # ... peps/pep-0806.rst @JelleZijlstra peps/pep-0807.rst @dstufft -# ... +peps/pep-0808.rst @FFY00 peps/pep-0809.rst @zooba peps/pep-0810.rst @pablogsal @DinoV @Yhg1s peps/pep-0811.rst @sethmlarson @gpshead # ... +peps/pep-0814.rst @vstinner @corona10 +peps/pep-0815.rst @emmatyping +peps/pep-0816.rst @brettcannon +# ... peps/pep-2026.rst @hugovk # ... peps/pep-3000.rst @gvanrossum diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0f02486b921..5ba6b70c4d8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/render.yml b/.github/workflows/render.yml index 3cac0ff4637..767f5d35892 100644 --- a/.github/workflows/render.yml +++ b/.github/workflows/render.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 # fetch all history so that last modified date-times are accurate diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c6581e0e5d4..41fc84fa785 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,7 +41,7 @@ jobs: - "ubuntu-latest" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4f7f3380ee0..28dc3a896aa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -65,6 +65,11 @@ repos: args: - '--exit-non-zero-on-fix' files: '^pep_sphinx_extensions/tests/' + - id: ruff-format + name: "Format with Ruff" + args: + - '--check' + files: '^release_management/' - repo: https://github.com/tox-dev/tox-ini-fmt rev: 1.4.1 @@ -111,3 +116,12 @@ repos: language: "python" files: '^peps/pep-\d{4}\.rst$' require_serial: true + + # Hook to regenerate release schedules + - id: "regen-schedules" + name: "Regenerate release schedules from python-releases.toml" + entry: "python -m release_management update-peps" + language: "python" + files: "^release_management/" + pass_filenames: false + require_serial: true diff --git a/.pytest.toml b/.pytest.toml new file mode 100644 index 00000000000..82dcecae263 --- /dev/null +++ b/.pytest.toml @@ -0,0 +1,37 @@ +# https://docs.pytest.org/en/stable/reference/customize.html + +[pytest] +minversion = "9.0" + +# https://docs.pytest.org/en/stable/reference/reference.html#command-line-flags +addopts = [ + # Show extra summary information for all non-passing tests + "-r a", + # https://docs.pytest.org/en/stable/explanation/pythonpath.html#import-modes + "--import-mode=importlib", + # https://pytest-cov.readthedocs.io/en/latest/config.html#reference + "--cov=check_peps", + "--cov=pep_sphinx_extensions", + "--cov=release_management", + "--cov-report=html", + "--cov-report=xml", +] + +# Fail if pytest.mark.parametrize() has no parameters +empty_parameter_set_mark = "fail_at_collect" + +filterwarnings = ["error"] + +testpaths = [ + "pep_sphinx_extensions", + "release_management", +] + +# https://docs.pytest.org/en/stable/reference/reference.html#confval-strict +strict_config = true +strict_markers = true +strict_parametrization_ids = true +strict_xfail = true + +# Various tests use Unicode in pytest.mark.parametrize(). +disable_test_id_escaping_and_forfeit_all_rights_to_community_support = true diff --git a/Makefile b/Makefile index 4ec6084b1d1..d15b304dd3c 100644 --- a/Makefile +++ b/Makefile @@ -108,6 +108,11 @@ test: venv spellcheck: _ensure-pre-commit $(VENVDIR)/bin/python3 -m pre_commit run --all-files --hook-stage manual codespell +## regen-all to regenerate generated source files +.PHONY: regen-all +regen-all: + $(PYTHON) -m release_management update-peps + .PHONY: help help : Makefile @echo "Please use \`make ' where is one of" diff --git a/pep_sphinx_extensions/pep_theme/static/mq.css b/pep_sphinx_extensions/pep_theme/static/mq.css index 2609a959ebd..cf38c1cacfa 100644 --- a/pep_sphinx_extensions/pep_theme/static/mq.css +++ b/pep_sphinx_extensions/pep_theme/static/mq.css @@ -152,7 +152,7 @@ section#pep-page-section > article { float: none; - max-width: 17.5cm; + max-width: 100%; width: auto; margin: 0; padding: 0; diff --git a/pep_sphinx_extensions/pep_zero_generator/pep_index_generator.py b/pep_sphinx_extensions/pep_zero_generator/pep_index_generator.py index 2dc3e7ff52d..6b5d56a4392 100644 --- a/pep_sphinx_extensions/pep_zero_generator/pep_index_generator.py +++ b/pep_sphinx_extensions/pep_zero_generator/pep_index_generator.py @@ -26,6 +26,7 @@ from pep_sphinx_extensions.pep_zero_generator import subindices from pep_sphinx_extensions.pep_zero_generator import writer from pep_sphinx_extensions.pep_zero_generator.constants import SUBINDICES_BY_TOPIC +from release_management.serialize import create_release_cycle, create_release_schedule_calendar, create_release_json if TYPE_CHECKING: from sphinx.application import Sphinx @@ -55,7 +56,6 @@ def create_pep_json(peps: list[parser.PEP]) -> str: def write_peps_json(peps: list[parser.PEP], path: Path) -> None: # Create peps.json json_peps = create_pep_json(peps) - Path(path, "peps.json").write_text(json_peps, encoding="utf-8") os.makedirs(os.path.join(path, "api"), exist_ok=True) Path(path, "api", "peps.json").write_text(json_peps, encoding="utf-8") @@ -73,3 +73,12 @@ def create_pep_zero(app: Sphinx, env: BuildEnvironment, docnames: list[str]) -> subindices.generate_subindices(SUBINDICES_BY_TOPIC, peps, docnames, env) write_peps_json(peps, Path(app.outdir)) + + release_cycle = create_release_cycle() + app.outdir.joinpath('api/release-cycle.json').write_text(release_cycle, encoding="utf-8") + + release_json = create_release_json() + app.outdir.joinpath('api/python-releases.json').write_text(release_json, encoding="utf-8") + + release_ical = create_release_schedule_calendar() + app.outdir.joinpath('release-schedule.ics').write_text(release_ical, encoding="utf-8") diff --git a/peps/pep-0012.rst b/peps/pep-0012.rst index c269b60f2f1..377fc915db7 100644 --- a/peps/pep-0012.rst +++ b/peps/pep-0012.rst @@ -599,10 +599,10 @@ processed output using the ``image`` directive: .. image:: diagram.png -Any browser-friendly graphics format is possible; PNG should be +Any browser-friendly graphics format is possible; SVG or PNG are preferred for graphics, JPEG for photos and GIF for animations. -Currently, SVG must be avoided due to compatibility issues with the -PEP build system. +Images should be optimised to reduce their file size, and should +be legible in both light and dark mode in the browser. For accessibility and readers of the source text, you should include a description of the image and any key information contained within diff --git a/peps/pep-0013.rst b/peps/pep-0013.rst index 1d59dcb6120..37b45c0be9d 100644 --- a/peps/pep-0013.rst +++ b/peps/pep-0013.rst @@ -107,8 +107,8 @@ A council election consists of two phases: * Phase 2: Each core team member can assign zero to five stars to each candidate. Voting is performed anonymously. The outcome of the vote is determined using the `STAR voting system `__, - modified to use the `Multi-winner Bloc STAR `__) - approach. If a tie occurs, it may + modified to use the `Multi-winner Bloc STAR `__ + approach. If a tie that is not automatically resolved by the election software occurs, it may be resolved by mutual agreement among the candidates, or else the winner will be chosen at random. @@ -358,6 +358,9 @@ History of amendments Adopted Multi-winner Bloc STAR voting for council elections. * `2024-12-10 `__: Added a one-week deadline for seconding a vote of no confidence. +* `2025-11-12 `__: + Clarified that the software used for elections may resolve ties + automatically if possible. diff --git a/peps/pep-0101.rst b/peps/pep-0101.rst index abe5af9afce..73d8ccd9508 100644 --- a/peps/pep-0101.rst +++ b/peps/pep-0101.rst @@ -646,61 +646,10 @@ permissions. curl -X PURGE https://www.python.org/downloads/release/python-XXX/ -- If this is a **final** release: - - - Add the new version to the `"Python documentation by version" - page `__ and - remove the current version from any 'in development' section. - - - For 3.X.Y, edit all the previous X.Y releases' page(s) to - point to the new release. This includes the content field of the - ``Downloads -> Releases`` entry for the release:: - - Note: Python 3.x.(y-1) has been superseded by - `Python 3.x.y `_. - - And, for those releases having separate release page entries - (phasing these out?), update those pages as well, - e.g. ``download/releases/3.x.y``:: - - Note: Python 3.x.(y-1) has been superseded by - `Python 3.x.y `_. - - - Update the `"Current pre-release testing versions" page - `__. - - - If you're releasing a version before *3.x.0*, - add it to this page, removing the previous pre-release - of version *3.x* as needed. - - - If you're releasing *3.x.0 final*, remove the pre-release - version from this page. - - This is in the "Pages" category on the Django-based website, and finding - it through that UI is kind of a chore. However! If you're already logged - in to the admin interface (which, at this point, you should be), Django - will helpfully add a convenient "Edit this page" link to the top of the - page itself. So you can simply follow the link above, click on the - "Edit this page" link, and make your changes as needed. How convenient! - - - If appropriate, update the `"Python documentation by version" page - `__. - - This lists all releases of Python by version number and links to their - static (not built daily) online documentation. There's a link at the - bottom to the in-development version. - And yes you can press the shiny, exciting "Edit this page" button. - - Write the announcement on `discuss.python.org`_. This is the fuzzy bit because not much can be automated. You can use an earlier announcement as a template, but edit it for content! -- Once the announcement is up on Discourse, send an equivalent to the - following mailing lists: - - * python-list@python.org - * python-announce@python.org - - Also post the announcement to the `Python Insider blog `_. To add a new entry, go to @@ -726,9 +675,7 @@ permissions. - Update the `issue tracker`_ for the new branch: add the new version to the versions list. - - Update the `devguide - `__ - to reflect the new branches and versions. + - Update python-releases.toml_ to reflect the new branches and versions. - Create a PR to update the supported releases table on the `downloads page `__ (see @@ -812,10 +759,10 @@ else does them. Some of those tasks include: * https://www.python.org/downloads/release/python-336/ -- In the `developer's guide - `__, - set the branch status to end-of-life - and update or remove references to the branch elsewhere in the devguide. +- In python-releases.toml_, set the branch status to end-of-life. + +- Update or remove references to the branch in the `developer's guide + `__. - Retire the release from the `issue tracker`_. Tasks include: @@ -833,8 +780,6 @@ else does them. Some of those tasks include: * `discuss.python.org`_ - * mailing lists (python-dev, python-list, python-announcements) - * `Python Insider blog `_ - Enjoy your retirement and bask in the glow of a job well done! @@ -884,6 +829,7 @@ This document has been placed in the public domain. .. _deferred-blocker: https://github.com/python/cpython/labels/deferred-blocker .. _discuss.python.org: https://discuss.python.org .. _issue tracker: https://github.com/python/cpython/issues +.. _python-releases.toml: https://github.com/python/peps/blob/HEAD/release_management/python-releases.toml .. _python/cpython: https://github.com/python/cpython .. _python/peps: https://github.com/python/peps .. _python/release-tools: https://github.com/python/release-tools diff --git a/peps/pep-0545.rst b/peps/pep-0545.rst index 1a5204ca690..633d497b44a 100644 --- a/peps/pep-0545.rst +++ b/peps/pep-0545.rst @@ -322,9 +322,11 @@ and http://zanata.org/. python-docs-translations '''''''''''''''''''''''' -The `python-docs-translations GitHub organization `_ -is home to several useful translation tools such as the translations -`dashboard `_. +The `python-docs-translations GitHub organization `__ +is home to several useful translation tools including +`translations.python.org `__ +(`python-docs-translations/dashboard +`__). Documentation Contribution Agreement diff --git a/peps/pep-0569.rst b/peps/pep-0569.rst index fc8d32d73ed..bfecefa4afd 100644 --- a/peps/pep-0569.rst +++ b/peps/pep-0569.rst @@ -51,6 +51,10 @@ Release Schedule 3.8.0 schedule -------------- +.. release schedule: feature + +Actual: + - 3.8 development begins: Monday, 2018-01-29 - 3.8.0 alpha 1: Sunday, 2019-02-03 - 3.8.0 alpha 2: Monday, 2019-02-25 @@ -58,51 +62,65 @@ Release Schedule - 3.8.0 alpha 4: Monday, 2019-05-06 - 3.8.0 beta 1: Tuesday, 2019-06-04 (No new features beyond this point.) - - 3.8.0 beta 2: Thursday, 2019-07-04 - 3.8.0 beta 3: Monday, 2019-07-29 - 3.8.0 beta 4: Friday, 2019-08-30 - 3.8.0 candidate 1: Tuesday, 2019-10-01 - 3.8.0 final: Monday, 2019-10-14 +.. release schedule: ends + Bugfix releases --------------- -- 3.8.1rc1: Tuesday, 2019-12-10 -- 3.8.1: Wednesday, 2019-12-18 -- 3.8.2rc1: Monday, 2020-02-10 -- 3.8.2rc2: Monday, 2020-02-17 -- 3.8.2: Monday, 2020-02-24 -- 3.8.3rc1: Wednesday, 2020-04-29 -- 3.8.3: Wednesday, 2020-05-13 -- 3.8.4rc1: Tuesday, 2020-06-30 -- 3.8.4: Monday, 2020-07-13 -- 3.8.5: Monday, 2020-07-20 (security hotfix) -- 3.8.6rc1: Tuesday, 2020-09-08 -- 3.8.6: Thursday, 2020-09-24 -- 3.8.7rc1: Monday, 2020-12-07 -- 3.8.7: Monday, 2020-12-21 -- 3.8.8rc1: Tuesday, 2021-02-16 -- 3.8.8: Friday, 2021-02-19 -- 3.8.9: Friday, 2021-04-02 (security hotfix) -- 3.8.10: Monday, 2021-05-03 (final regular bugfix release with binary - installers) +.. release schedule: bugfix + +Actual: + +- 3.8.1 candidate 1: Tuesday, 2019-12-10 +- 3.8.1 final: Wednesday, 2019-12-18 +- 3.8.2 candidate 1: Monday, 2020-02-10 +- 3.8.2 candidate 2: Monday, 2020-02-17 +- 3.8.2 final: Monday, 2020-02-24 +- 3.8.3 candidate 1: Wednesday, 2020-04-29 +- 3.8.3 final: Wednesday, 2020-05-13 +- 3.8.4 candidate 1: Tuesday, 2020-06-30 +- 3.8.4 final: Monday, 2020-07-13 +- 3.8.5 final: Monday, 2020-07-20 + (security hotfix) +- 3.8.6 candidate 1: Tuesday, 2020-09-08 +- 3.8.6 final: Thursday, 2020-09-24 +- 3.8.7 candidate 1: Monday, 2020-12-07 +- 3.8.7 final: Monday, 2020-12-21 +- 3.8.8 candidate 1: Tuesday, 2021-02-16 +- 3.8.8 final: Friday, 2021-02-19 +- 3.8.9 final: Friday, 2021-04-02 + (security hotfix) +- 3.8.10 final: Monday, 2021-05-03 + (Final regular bugfix release with binary installers) + +.. release schedule: ends Source-only security fix releases --------------------------------- Provided irregularly on an "as-needed" basis until October 7th 2024. -- 3.8.11: Monday, 2021-06-28 -- 3.8.12: Monday, 2021-08-30 -- 3.8.13: Wednesday, 2022-03-16 -- 3.8.14: Tuesday, 2022-09-06 -- 3.8.15: Tuesday, 2022-10-11 -- 3.8.16: Tuesday, 2022-12-06 -- 3.8.17: Tuesday, 2023-06-06 -- 3.8.18: Thursday, 2023-08-24 -- 3.8.19: Tuesday, 2024-03-19 -- 3.8.20: Friday, 2024-09-06 (final security release) +.. release schedule: security + +- 3.8.11 final: Monday, 2021-06-28 +- 3.8.12 final: Monday, 2021-08-30 +- 3.8.13 final: Wednesday, 2022-03-16 +- 3.8.14 final: Tuesday, 2022-09-06 +- 3.8.15 final: Tuesday, 2022-10-11 +- 3.8.16 final: Tuesday, 2022-12-06 +- 3.8.17 final: Tuesday, 2023-06-06 +- 3.8.18 final: Thursday, 2023-08-24 +- 3.8.19 final: Tuesday, 2024-03-19 +- 3.8.20 final: Friday, 2024-09-06 + (final security release) + +.. release schedule: ends Features for 3.8 diff --git a/peps/pep-0570.rst b/peps/pep-0570.rst index 4bedf5cf581..fbd49620a3d 100644 --- a/peps/pep-0570.rst +++ b/peps/pep-0570.rst @@ -1,7 +1,7 @@ PEP: 570 Title: Python Positional-Only Parameters Author: Larry Hastings , - Pablo Galindo , + Pablo Galindo Salgado , Mario Corchero , Eric N. Vander Weele BDFL-Delegate: Guido van Rossum diff --git a/peps/pep-0596.rst b/peps/pep-0596.rst index eee2eeaa277..5339a1128c4 100644 --- a/peps/pep-0596.rst +++ b/peps/pep-0596.rst @@ -2,7 +2,7 @@ PEP: 596 Title: Python 3.9 Release Schedule Author: Łukasz Langa Discussions-To: https://discuss.python.org/t/pep-596-python-3-9-release-schedule-doubling-the-release-cadence/1828 -Status: Active +Status: Final Type: Informational Topic: Release Created: 04-Jun-2019 @@ -40,6 +40,8 @@ Note: the dates below use a 17-month development period that results in a 12-month release cadence between feature versions, as defined by :pep:`602`. +.. release schedule: feature + Actual: - 3.9 development begins: Tuesday, 2019-06-04 @@ -59,28 +61,37 @@ Actual: - 3.9.0 candidate 2: Thursday, 2020-09-17 - 3.9.0 final: Monday, 2020-10-05 +.. release schedule: ends + Bugfix releases --------------- +.. release schedule: bugfix + Actual: - 3.9.1 candidate 1: Tuesday, 2020-11-24 - 3.9.1 final: Monday, 2020-12-07 - 3.9.2 candidate 1: Tuesday, 2021-02-16 - 3.9.2 final: Friday, 2021-02-19 -- 3.9.3: Friday, 2021-04-02 (security hotfix; recalled due to bpo-43710) -- 3.9.4: Sunday, 2021-04-04 (ABI compatibility hotfix) -- 3.9.5: Monday, 2021-05-03 -- 3.9.6: Monday, 2021-06-28 -- 3.9.7: Monday, 2021-08-30 -- 3.9.8: Friday, 2021-11-05 (recalled due to bpo-45235) -- 3.9.9: Monday, 2021-11-15 -- 3.9.10: Friday, 2022-01-14 -- 3.9.11: Wednesday, 2022-03-16 -- 3.9.12: Wednesday, 2022-03-23 -- 3.9.13: Tuesday, 2022-05-17 (final regular bugfix release with binary - installers) +- 3.9.3 final: Friday, 2021-04-02 + (security hotfix; recalled due to bpo-43710) +- 3.9.4 final: Sunday, 2021-04-04 + (ABI compatibility hotfix) +- 3.9.5 final: Monday, 2021-05-03 +- 3.9.6 final: Monday, 2021-06-28 +- 3.9.7 final: Monday, 2021-08-30 +- 3.9.8 final: Friday, 2021-11-05 + (recalled due to bpo-45235) +- 3.9.9 final: Monday, 2021-11-15 +- 3.9.10 final: Friday, 2022-01-14 +- 3.9.11 final: Wednesday, 2022-03-16 +- 3.9.12 final: Wednesday, 2022-03-23 +- 3.9.13 final: Tuesday, 2022-05-17 + (Final regular bugfix release with binary installers) + +.. release schedule: ends Source-only security fix releases @@ -88,17 +99,22 @@ Source-only security fix releases Provided irregularly on an "as-needed" basis until October 2025. -- 3.9.14: Tuesday, 2022-09-06 -- 3.9.15: Tuesday, 2022-10-11 -- 3.9.16: Tuesday, 2022-12-06 -- 3.9.17: Tuesday, 2023-06-06 -- 3.9.18: Thursday, 2023-08-24 -- 3.9.19: Tuesday, 2024-03-19 -- 3.9.20: Friday, 2024-09-06 -- 3.9.21: Tuesday, 2024-12-03 -- 3.9.22: Tuesday, 2025-04-08 -- 3.9.23: Tuesday, 2025-06-03 -- 3.9.24: Thursday, 2025-10-09 +.. release schedule: security + +- 3.9.14 final: Tuesday, 2022-09-06 +- 3.9.15 final: Tuesday, 2022-10-11 +- 3.9.16 final: Tuesday, 2022-12-06 +- 3.9.17 final: Tuesday, 2023-06-06 +- 3.9.18 final: Thursday, 2023-08-24 +- 3.9.19 final: Tuesday, 2024-03-19 +- 3.9.20 final: Friday, 2024-09-06 +- 3.9.21 final: Tuesday, 2024-12-03 +- 3.9.22 final: Tuesday, 2025-04-08 +- 3.9.23 final: Tuesday, 2025-06-03 +- 3.9.24 final: Thursday, 2025-10-09 +- 3.9.25 final: Friday, 2025-10-31 + +.. release schedule: ends 3.9 Lifespan @@ -107,10 +123,14 @@ Provided irregularly on an "as-needed" basis until October 2025. 3.9 received bugfix updates approximately every 2 months for approximately 18 months. Some time after the release of 3.10.0 final, the ninth and final 3.9 bugfix update was released. After that, -it is expected that security updates (source only) will be released -until 5 years after the release of 3.9 final, so until approximately -October 2025. - +security updates (source only) were released +until October 31st 2025, that is 5 years after the release of 3.9 final. + +As of 2025-10-31, 3.9 has reached the +`end-of-life phase `_ +of its release cycle. 3.9.25 was the final security release. +The codebase for 3.9 is now frozen and no further updates will be +provided nor issues of any kind will be accepted on the bug tracker. Features for 3.9 ================ diff --git a/peps/pep-0617.rst b/peps/pep-0617.rst index ecc376d3261..4d146c6b122 100644 --- a/peps/pep-0617.rst +++ b/peps/pep-0617.rst @@ -1,7 +1,7 @@ PEP: 617 Title: New PEG parser for CPython Author: Guido van Rossum , - Pablo Galindo , + Pablo Galindo Salgado , Lysandros Nikolaou Discussions-To: python-dev@python.org Status: Final diff --git a/peps/pep-0619.rst b/peps/pep-0619.rst index 1835a9db950..5405e2aee57 100644 --- a/peps/pep-0619.rst +++ b/peps/pep-0619.rst @@ -37,6 +37,8 @@ Note: the dates below use a 17-month development period that results in a 12-month release cadence between feature versions, as defined by :pep:`602`. +.. release schedule: feature + Actual: - 3.10 development begins: Monday, 2020-05-18 @@ -56,9 +58,13 @@ Actual: - 3.10.0 candidate 2: Tuesday, 2021-09-07 - 3.10.0 final: Monday, 2021-10-04 +.. release schedule: ends + Bugfix releases --------------- +.. release schedule: bugfix + Actual: - 3.10.1: Monday, 2021-12-06 @@ -71,14 +77,18 @@ Actual: - 3.10.8: Tuesday, 2022-10-11 - 3.10.9: Tuesday, 2022-12-06 - 3.10.10: Wednesday, 2023-02-08 -- 3.10.11: Wednesday, 2023-04-05 (final regular bugfix release with binary - installers) +- 3.10.11: Wednesday, 2023-04-05 + (Final regular bugfix release with binary installers) + +.. release schedule: ends Source-only security fix releases --------------------------------- Provided irregularly on an "as-needed" basis until October 2026. +.. release schedule: security + - 3.10.12: Tuesday, 2023-06-06 - 3.10.13: Thursday, 2023-08-24 - 3.10.14: Tuesday, 2024-03-19 @@ -88,6 +98,8 @@ Provided irregularly on an "as-needed" basis until October 2026. - 3.10.18: Tuesday, 2025-06-03 - 3.10.19: Thursday, 2025-10-09 +.. release schedule: ends + 3.10 Lifespan ------------- diff --git a/peps/pep-0626.rst b/peps/pep-0626.rst index cd6a1d70ed3..1d8b54ae6c8 100644 --- a/peps/pep-0626.rst +++ b/peps/pep-0626.rst @@ -1,7 +1,7 @@ PEP: 626 Title: Precise line numbers for debugging and other tools. Author: Mark Shannon -BDFL-Delegate: Pablo Galindo +BDFL-Delegate: Pablo Galindo Salgado Status: Final Type: Standards Track Created: 15-Jul-2020 diff --git a/peps/pep-0641.rst b/peps/pep-0641.rst index 393133b88e6..2e3771eb257 100644 --- a/peps/pep-0641.rst +++ b/peps/pep-0641.rst @@ -3,7 +3,7 @@ Title: Using an underscore in the version portion of Python 3.10 compatibility t Author: Brett Cannon , Steve Dower , Barry Warsaw -PEP-Delegate: Pablo Galindo +PEP-Delegate: Pablo Galindo Salgado Discussions-To: https://discuss.python.org/t/pep-641-using-an-underscore-in-the-version-portion-of-python-3-10-compatibility-tags/5513 Status: Rejected Type: Standards Track diff --git a/peps/pep-0648.rst b/peps/pep-0648.rst index 19b4e287b66..b02c477d394 100644 --- a/peps/pep-0648.rst +++ b/peps/pep-0648.rst @@ -1,7 +1,7 @@ PEP: 648 Title: Extensible customizations of the interpreter at startup Author: Mario Corchero -Sponsor: Pablo Galindo +Sponsor: Pablo Galindo Salgado Discussions-To: https://discuss.python.org/t/pep-648-extensible-customizations-of-the-interpreter-at-startup/6403 Status: Rejected Type: Standards Track diff --git a/peps/pep-0657.rst b/peps/pep-0657.rst index 55005c2f8bb..ec83803d244 100644 --- a/peps/pep-0657.rst +++ b/peps/pep-0657.rst @@ -1,6 +1,6 @@ PEP: 657 Title: Include Fine Grained Error Locations in Tracebacks -Author: Pablo Galindo , +Author: Pablo Galindo Salgado , Batuhan Taskaya , Ammar Askar Discussions-To: https://discuss.python.org/t/pep-657-include-fine-grained-error-locations-in-tracebacks/8629 diff --git a/peps/pep-0664.rst b/peps/pep-0664.rst index 198f2069943..18aa1d11d6b 100644 --- a/peps/pep-0664.rst +++ b/peps/pep-0664.rst @@ -38,6 +38,8 @@ Note: the dates below use a 17-month development period that results in a 12-month release cadence between feature versions, as defined by :pep:`602`. +.. release schedule: feature + Actual: - 3.11 development begins: Monday, 2021-05-03 @@ -56,11 +58,15 @@ Actual: - 3.11.0 beta 5: Tuesday, 2022-07-26 - 3.11.0 candidate 1: Monday, 2022-08-08 - 3.11.0 candidate 2: Monday, 2022-09-12 -- 3.11.0 final: Monday, 2022-10-24 +- 3.11.0 final: Monday, 2022-10-24 + +.. release schedule: ends Bugfix releases --------------- +.. release schedule: bugfix + Actual: - 3.11.1: Tuesday, 2022-12-06 @@ -71,20 +77,26 @@ Actual: - 3.11.6: Monday, 2023-10-02 - 3.11.7: Monday, 2023-12-04 - 3.11.8: Tuesday, 2024-02-06 -- 3.11.9: Tuesday, 2024-04-02 (final regular bugfix release with binary - installers) +- 3.11.9: Tuesday, 2024-04-02 + (Final regular bugfix release with binary installers) + +.. release schedule: ends Source-only security fix releases --------------------------------- Provided irregularly on an "as-needed" basis until October 2027. +.. release schedule: security + - 3.11.10: Saturday, 2024-09-07 - 3.11.11: Tuesday, 2024-12-03 - 3.11.12: Tuesday, 2025-04-08 - 3.11.13: Tuesday, 2025-06-03 - 3.11.14: Thursday, 2025-10-09 +.. release schedule: ends + 3.11 Lifespan ------------- diff --git a/peps/pep-0693.rst b/peps/pep-0693.rst index 4bfdab160d3..8ac0d92fe9f 100644 --- a/peps/pep-0693.rst +++ b/peps/pep-0693.rst @@ -32,6 +32,8 @@ Note: the dates below use a 17-month development period that results in a 12-month release cadence between feature versions, as defined by :pep:`602`. +.. release schedule: feature + Actual: - 3.12 development begins: Sunday, 2022-05-08 @@ -50,11 +52,15 @@ Actual: - 3.12.0 candidate 1: Sunday, 2023-08-06 - 3.12.0 candidate 2: Wednesday, 2023-09-06 - 3.12.0 candidate 3: Tuesday, 2023-09-19 -- 3.12.0 final: Monday, 2023-10-02 +- 3.12.0 final: Monday, 2023-10-02 + +.. release schedule: ends Bugfix releases --------------- +.. release schedule: bugfix + Actual: - 3.12.1: Thursday, 2023-12-07 @@ -66,16 +72,23 @@ Actual: - 3.12.7: Tuesday, 2024-10-01 - 3.12.8: Tuesday, 2024-12-03 - 3.12.9: Tuesday, 2025-02-04 -- 3.12.10: Tuesday, 2025-04-08 (final regular bugfix release with binary installers) +- 3.12.10: Tuesday, 2025-04-08 + (Final regular bugfix release with binary installers) + +.. release schedule: ends Source-only security fix releases --------------------------------- Provided irregularly on an as-needed basis until October 2028. +.. release schedule: security + - 3.12.11: Tuesday, 2025-06-03 - 3.12.12: Thursday, 2025-10-09 +.. release schedule: ends + 3.12 Lifespan ------------- diff --git a/peps/pep-0694.rst b/peps/pep-0694.rst index 582638654fa..87c9e6c9b50 100644 --- a/peps/pep-0694.rst +++ b/peps/pep-0694.rst @@ -12,6 +12,7 @@ Post-History: `27-Jun-2022 `__ `06-Aug-2025 `__ `27-Sep-2025 `__ + `07-Dec-2025 `__ Abstract @@ -84,8 +85,8 @@ In addition, there are a number of major issues with the legacy API: The new upload API proposed in this PEP provides ways to solve all of these problems, either directly or through an extensible approach, allowing servers to implement features such as resumable and parallel uploads. -This upload API this PEP proposes provides better error reporting, a more robust release testing experience, -and atomic and simultaneous publishing of all release artifacts. +This upload API this PEP proposes provides better and more standardized error reporting, a more robust release +testing experience, and atomic and simultaneous publishing of all release artifacts. Legacy API ========== @@ -274,17 +275,26 @@ are determined by the index operator. Errors ------ +Unless otherwise specified, all error (4xx and 5xx) responses from the server **MUST** use the :rfc:`9457` +(Problem Details for HTTP APIs) format. In particular, the server **MUST** use the "Problem Details JSON +Object" defined in :rfc:`Section 3 <9457#section-3>` and **SHOULD** use the ``application/problem+json`` media +type in its responses. + Clients in general should be prepared to handle `HTTP response error status codes -`_ which **MAY** contain payloads of the -the following format: +`_ which **SHOULD** contain payloads like +the following, although note that the details are index-specific, as long as they conform to RFC 9457. By way +of example, PyPI could return the following error body: .. code-block:: json { + "type": "https://docs.pypi.org/api/errors/error-types#invalid-filename", + "status": 400, + "title": "The artifact used an invalid wheel file name format", + "details": "See https://packaging.python.org/en/latest/specifications/binary-distribution-format/", "meta": { "api-version": "2.0" }, - "message": "...", "errors": [ { "source": "...", @@ -293,11 +303,12 @@ the following format: ] } -Besides the standard ``meta`` key, this has the following top level keys: +RFC 9457 defines ``type``, ``status``, ``title``, and ``details``. The ``meta`` and ``errors`` keys are +"extension members", defined in :rfc:`Section 3.2 <9457#section-3.2>`. The index **SHOULD** include these +extension members. -``message`` - A singular message that encapsulates all errors that may have happened on this - request. +``meta`` + The same request/response metadata structure as defined in the :ref:`publishing-session` description. ``errors`` An array of specific errors, each of which contains a ``source`` key, which is a string that @@ -683,14 +694,14 @@ The request looks like: Besides the standard ``meta`` key, the request JSON has the following additional keys: ``filename`` (**required**) - The name of the file being uploaded. The filename **MUST** conform to either the `source - distribution file name specification + The name of the file being uploaded. The filename **MUST** conform to either the `source distribution + file name specification `_ or the `binary distribution file name convention `_. - Indexes **SHOULD** validate these file names at the time of the request, returning a ``400 Bad - Request`` error code, as described in the :ref:`session-errors` section when the file names do - not conform. + Indexes **SHOULD** validate these file names at the time of the request, returning a ``400 Bad Request`` + error code and an RFC 9457 style error body, as described in the :ref:`session-errors` section when the + file names do not conform. ``size`` (**required**) The size in bytes of the file being uploaded. @@ -1094,7 +1105,11 @@ as experience is gained operating Upload 2.0. Change History ============== -* `23-Sep-2025 `__ +* `06-Dec-2025 `__ + + * Error responses conform to the :rfc:`9457` format. + +* `23-Sep-2025 `__ * Remove the ``nonce`` and ``gentoken()`` algorithm. Indexes are now responsible for generating an cryptographically secure session token and obfuscated stage URL (but only if they support diff --git a/peps/pep-0701.rst b/peps/pep-0701.rst index d14c1929c3d..3a6f81b3855 100644 --- a/peps/pep-0701.rst +++ b/peps/pep-0701.rst @@ -1,6 +1,6 @@ PEP: 701 Title: Syntactic formalization of f-strings -Author: Pablo Galindo , +Author: Pablo Galindo Salgado , Batuhan Taskaya , Lysandros Nikolaou , Marta Gómez Macías diff --git a/peps/pep-0705.rst b/peps/pep-0705.rst index cae156d4127..44908d69213 100644 --- a/peps/pep-0705.rst +++ b/peps/pep-0705.rst @@ -1,7 +1,7 @@ PEP: 705 Title: TypedDict: Read-only items Author: Alice Purcell -Sponsor: Pablo Galindo +Sponsor: Pablo Galindo Salgado Discussions-To: https://discuss.python.org/t/pep-705-read-only-typeddict-items/37867 Status: Final Type: Standards Track diff --git a/peps/pep-0719.rst b/peps/pep-0719.rst index 52b1169bf90..db8f2c66199 100644 --- a/peps/pep-0719.rst +++ b/peps/pep-0719.rst @@ -33,6 +33,8 @@ Note: the dates below use a 17-month development period that results in a 12-month release cadence between feature versions, as defined by :pep:`602`. +.. release schedule: feature + Actual: - 3.13 development begins: Monday, 2023-05-22 @@ -52,9 +54,13 @@ Actual: - 3.13.0 candidate 3: Tuesday, 2024-10-01 - 3.13.0 final: Monday, 2024-10-07 +.. release schedule: ends + Bugfix releases --------------- +.. release schedule: bugfix + Actual: - 3.13.1: Tuesday, 2024-12-03 @@ -62,19 +68,24 @@ Actual: - 3.13.3: Tuesday, 2025-04-08 - 3.13.4: Tuesday, 2025-06-03 - 3.13.5: Wednesday, 2025-06-11 + (hotfix) - 3.13.6: Wednesday, 2025-08-06 - 3.13.7: Thursday, 2025-08-14 - 3.13.8: Tuesday, 2025-10-07 - 3.13.9: Tuesday, 2025-10-14 +- 3.13.10: Tuesday, 2025-12-02 +- 3.13.11: Friday, 2025-12-05 Expected: -- 3.13.10: Tuesday, 2025-12-02 -- 3.13.11: Tuesday, 2026-02-03 -- 3.13.12: Tuesday, 2026-04-07 -- 3.13.13: Tuesday, 2026-06-09 -- 3.13.14: Tuesday, 2026-08-04 -- 3.13.15: Tuesday, 2026-10-06 +- 3.13.12: Tuesday, 2026-02-03 +- 3.13.13: Tuesday, 2026-04-07 +- 3.13.14: Tuesday, 2026-06-09 +- 3.13.15: Tuesday, 2026-08-04 +- 3.13.16: Tuesday, 2026-10-06 + (Final regular bugfix release with binary installers) + +.. release schedule: ends Source-only security fix releases diff --git a/peps/pep-0745.rst b/peps/pep-0745.rst index 0e8530c5286..349aaf67e3e 100644 --- a/peps/pep-0745.rst +++ b/peps/pep-0745.rst @@ -33,6 +33,8 @@ The dates below use a 17-month development period that results in a 12-month release cadence between feature versions, as defined by :pep:`602`. +.. release schedule: feature + Actual: - 3.14 development begins: Wednesday, 2024-05-08 @@ -53,9 +55,39 @@ Actual: - 3.14.0 candidate 3: Thursday, 2025-09-18 - 3.14.0 final: Tuesday, 2025-10-07 +.. release schedule: ends + +Bugfix releases +--------------- + +.. release schedule: bugfix + +Actual: + +- 3.14.1: Tuesday, 2025-12-02 +- 3.14.2: Friday, 2025-12-05 + Expected: -Subsequent bugfix releases every two months. +- 3.14.3: Tuesday, 2026-02-03 +- 3.14.4: Tuesday, 2026-04-07 +- 3.14.5: Tuesday, 2026-06-09 +- 3.14.6: Tuesday, 2026-08-04 +- 3.14.7: Tuesday, 2026-10-06 +- 3.14.8: Tuesday, 2026-12-01 +- 3.14.9: Tuesday, 2027-02-02 +- 3.14.10: Tuesday, 2027-04-06 +- 3.14.11: Tuesday, 2027-06-01 +- 3.14.12: Tuesday, 2027-08-03 +- 3.14.13: Tuesday, 2027-10-05 + (Final regular bugfix release with binary installers) + +.. release schedule: ends + +Source-only security fix releases +--------------------------------- + +Provided irregularly on an as-needed basis until October 2030. 3.14 lifespan diff --git a/peps/pep-0747.rst b/peps/pep-0747.rst index 9bccabfef2b..5ba1ed95ee5 100644 --- a/peps/pep-0747.rst +++ b/peps/pep-0747.rst @@ -289,11 +289,13 @@ Type checkers should validate that this argument is a valid type expression:: x1 = TypeForm(str | None) reveal_type(v1) # Revealed type is "TypeForm[str | None]" - x2 = TypeForm("list[int]") + x2 = TypeForm('list[int]') revealed_type(v2) # Revealed type is "TypeForm[list[int]]" x3 = TypeForm('type(1)') # Error: invalid type expression +The static type of a ``TypeForm(T)`` is ``TypeForm[T]``. + At runtime the ``TypeForm(...)`` callable simply returns the value passed to it. This explicit syntax serves two purposes. First, it documents the developer's @@ -516,12 +518,14 @@ Reference Implementation Pyright (version 1.1.379) provides a reference implementation for ``TypeForm``. -Mypy contributors also `plan to implement `__ -support for ``TypeForm``. +Mypy (`commit 1b7e71`_; Nov 3, 2025) provides +a reference implementation for ``TypeForm``. A reference implementation of the runtime component is provided in the ``typing_extensions`` module. +.. _commit 1b7e71: https://github.com/python/mypy/commit/1b7e717ecc56cd13d76bc110a1db2796e8b3c918 + Rejected Ideas ============== diff --git a/peps/pep-0758.rst b/peps/pep-0758.rst index 9660dbddd88..8f75e145380 100644 --- a/peps/pep-0758.rst +++ b/peps/pep-0758.rst @@ -1,6 +1,6 @@ PEP: 758 Title: Allow ``except`` and ``except*`` expressions without parentheses -Author: Pablo Galindo , Brett Cannon +Author: Pablo Galindo Salgado , Brett Cannon Status: Final Type: Standards Track Created: 30-Sep-2024 diff --git a/peps/pep-0760.rst b/peps/pep-0760.rst index 721944c3d03..31b5c4a4143 100644 --- a/peps/pep-0760.rst +++ b/peps/pep-0760.rst @@ -1,6 +1,6 @@ PEP: 760 Title: No More Bare Excepts -Author: Pablo Galindo , Brett Cannon +Author: Pablo Galindo Salgado , Brett Cannon Status: Withdrawn Type: Standards Track Created: 02-Oct-2024 diff --git a/peps/pep-0772.rst b/peps/pep-0772.rst index 3935ae78f85..b60677c5199 100644 --- a/peps/pep-0772.rst +++ b/peps/pep-0772.rst @@ -339,6 +339,10 @@ Board voting membership affirmations. PSF voting members may opt-out (annually or indefinitely) from Packaging Council elections independently of their choice to vote in PSF Board elections. +The process for ensuring elector eligibility to vote in Packaging Council elections will be +documented in the `Python Packaging User Guide `_ before +the inaugural election, and reviewed by the returns officer prior to every subsequent election. + .. _process: Processes @@ -445,8 +449,8 @@ this relationship would be figured out by the inaugural Council. Appendix A: PEP approval process ================================ -This PEP likely requires an atypical approval process, given the parties that must agree. To that -end, the authors will submit this PEP +This PEP requires an atypical approval process, given the parties that must agree. To that end, the +authors will submit this PEP #. for a vote with the PSF Board, which must approve the linking of Packaging Council Electors to the PSF Membership, and the deactivation of the Packaging Workgroup. @@ -457,6 +461,8 @@ end, the authors will submit this PEP enforce the PSF Code of Conduct, in addition to enforcement mechanisms otherwise approved by the Foundation. - Requested language added in `PR 4550 `_. + - Resolution from `PSF Board 2025-08-13 minutes + `__. #. for a vote on the pypa-committers mailing list, in accordance with the process outlined in :pep:`609` diff --git a/peps/pep-0788.rst b/peps/pep-0788.rst index d933316f97f..5c328e6b815 100644 --- a/peps/pep-0788.rst +++ b/peps/pep-0788.rst @@ -1,6 +1,6 @@ PEP: 788 Title: Protecting the C API from Interpreter Finalization -Author: Peter Bierma +Author: Peter Bierma Sponsor: Victor Stinner Discussions-To: https://discuss.python.org/t/104150 Status: Draft @@ -553,7 +553,6 @@ With this PEP, you would implement it like this: return -1; } int res = PyFile_WriteString(to_write, file); - free(to_write); if (res < 0) { PyErr_Print(); } diff --git a/peps/pep-0790.rst b/peps/pep-0790.rst index 6a67aa7df81..210f0a92c2a 100644 --- a/peps/pep-0790.rst +++ b/peps/pep-0790.rst @@ -31,14 +31,16 @@ Release schedule The dates below use a 17-month development period that results in a 12-month release cadence between feature versions, as defined by :pep:`602`. +.. release schedule: feature + Actual: - 3.15 development begins: Wednesday, 2025-05-07 - 3.15.0 alpha 1: Tuesday, 2025-10-14 +- 3.15.0 alpha 2: Wednesday, 2025-11-19 Expected: -- 3.15.0 alpha 2: Tuesday, 2025-11-18 - 3.15.0 alpha 3: Tuesday, 2025-12-16 - 3.15.0 alpha 4: Tuesday, 2026-01-13 - 3.15.0 alpha 5: Tuesday, 2026-02-10 @@ -53,6 +55,8 @@ Expected: - 3.15.0 candidate 2: Tuesday, 2026-09-01 - 3.15.0 final: Thursday, 2026-10-01 +.. release schedule: ends + Subsequent bugfix releases every two months. diff --git a/peps/pep-0791.rst b/peps/pep-0791.rst index 573619bfb56..ff1400ff49c 100644 --- a/peps/pep-0791.rst +++ b/peps/pep-0791.rst @@ -1,15 +1,22 @@ PEP: 791 Title: math.integer --- submodule for integer-specific mathematics functions -Author: Sergey B Kirpichev +Author: Neil Girdhar , + Sergey B Kirpichev , + Tim Peters , + Serhiy Storchaka Sponsor: Victor Stinner Discussions-To: https://discuss.python.org/t/92548 -Status: Draft +Status: Final Type: Standards Track Created: 12-May-2025 Python-Version: 3.15 Post-History: `12-Jul-2018 `__, `09-May-2025 `__, `19-May-2025 `__, +Resolution: `23-Oct-2025 `__ + + +.. canonical-doc:: `math.integer — integer-specific mathematics functions `_ Abstract @@ -146,8 +153,8 @@ Module functions will accept integers and objects that implement the object to an integer number. Suitable functions must be computed exactly, given sufficient time and memory. -The :pypi:`intmath` package will provide new submodule content for older Python -versions. +The :pypi:`intmath` package, available on PyPI, will provide new submodule +content for older Python versions. Possible Extensions @@ -169,8 +176,8 @@ compatible interface for the stdlib. Backwards Compatibility ======================= -As aliases in :external+py3.14:mod:`math` will be kept for an indefinite time -(their use would be discouraged), there are no anticipated code breaks. +As aliases in :external+py3.14:mod:`math` will be kept indefinitely (their use +would be discouraged), there are no anticipated code breaks. How to Teach This @@ -201,10 +208,11 @@ isqrt() renaming --------------------------------------------- There was a brief discussion about exposing :external+py3.14:func:`math.isqrt` -as ``imath.sqrt`` in the same way that :external+py3.14:func:`cmath.sqrt` is -the complex version of :external+py3.14:func:`math.sqrt`. However, ``isqrt`` -is ultimately a different function: it is the floor of the square root. It -would be confusing to give it the same name (under a different module). +as ``sqrt`` in the new namespace in the same way that +:external+py3.14:func:`cmath.sqrt` is the complex version of +:external+py3.14:func:`math.sqrt`. However, ``isqrt`` is ultimately a +different function: it is the floor of the square root. It would be confusing +to give it the same name (under a different submodule). Module name @@ -218,15 +226,15 @@ Other proposed names include ``ntheory`` (like SymPy's submodule), But the SC prefers a submodule rather than a new top-level module. Most popular variants of the :external+py3.14:mod:`math`'s submodule are: -``integer``, ``discrete`` or ``ntheory`` (author preference). +``integer``, ``discrete`` or ``ntheory``. Acknowledgements ================ -Thanks to Tim Peters for reviving the idea of splitting the -:external+py3.14:mod:`math` module. Thanks to Neil Girdhar for substantial -improvements of the initial draft. +Thanks to Victor Stinner for sponsoring this PEP. +Thanks to everyone who participated in the discussions on discuss.python.org, +providing feedback, especially to Oscar Benjamin, Steve Dower and Paul Moore. Copyright diff --git a/peps/pep-0793.rst b/peps/pep-0793.rst index 5caee64aee5..868e14945e6 100644 --- a/peps/pep-0793.rst +++ b/peps/pep-0793.rst @@ -2,12 +2,13 @@ PEP: 793 Title: PyModExport: A new entry point for C extension modules Author: Petr Viktorin Discussions-To: https://discuss.python.org/t/93444 -Status: Draft +Status: Accepted Type: Standards Track Created: 23-May-2025 Python-Version: 3.15 Post-History: `14-Mar-2025 `__, `27-May-2025 `__, +Resolution: `23-Oct-2025 `__ Abstract @@ -633,14 +634,11 @@ Example Reference Implementation ======================== -A draft implementation is available in a -`GitHub branch `_. - +Implementation is tracked in +`GitHub issue #140550 `_. -Open Issues -=========== - -(Add yours!) +A draft implementation was available in a +`GitHub branch `_. Rejected Ideas diff --git a/peps/pep-0799.rst b/peps/pep-0799.rst index bc7c5098f3c..b941420f147 100644 --- a/peps/pep-0799.rst +++ b/peps/pep-0799.rst @@ -1,6 +1,6 @@ PEP: 799 Title: A dedicated ``profiling`` package for organizing Python profiling tools -Author: Pablo Galindo , +Author: Pablo Galindo Salgado , László Kiss Kollár Discussions-To: https://discuss.python.org/t/pep-799-a-dedicated-profilers-package-for-organizing-python-profiling-tool/100898 Status: Accepted diff --git a/peps/pep-0803.rst b/peps/pep-0803.rst index edece9a2c31..cb1643e4343 100644 --- a/peps/pep-0803.rst +++ b/peps/pep-0803.rst @@ -63,12 +63,13 @@ to those builds. To build against the Stable ABI, the extension must use a *Limited API*, that is, only a subset of the functions, structures, etc. that CPython exposes. -The Limited API is versioned, and building against Limited API 3.X -yields an extension that is ABI-compatible with CPython 3.X and *any* later -version (though bugs in CPython sometimes cause incompatibilities in practice). -Also, the Limited API is not “stable”: newer versions may remove API that -were a part of older versions. +Both the Limited API and the Stable ABI are versioned, and building against +Stable ABI 3.X requires using only Limited API 3.X, and yields an extension +that is ABI-compatible with CPython 3.X and *any* later version +(though bugs in CPython sometimes cause incompatibilities in practice). +The Limited API is not “stable”: newer versions may remove API that +were a part of older versions. This PEP proposes the most significant such removal to date. @@ -88,6 +89,7 @@ No backwards compatibility now However, we won't block the possibility of extending compatibility to CPython 3.14 and below. + See a :ref:`rejected idea ` for how this could work. API changes are OK The new Limited API may require extension authors to make significant @@ -172,9 +174,12 @@ New Export Hook (PEP 793) ------------------------- Implementation of this PEP requires :pep:`793` (``PyModExport``): -A new entry point for C extension modules) to be -accepted, providing a new “export hook” for defining extension modules. -Using the new hook will become mandatory in Limited API 3.15. +A new entry point for C extension modules), which was accepted for Python +3.15. + +Since existing ways of defining modules use API that this PEP removes +(namely, :c:type:`PyModuleDef`), extensions will need to migrate to PEP 793's +new “export hook” when switching to Limited API 3.15. Runtime ABI checks @@ -185,6 +190,12 @@ will continue to be responsible for not putting incompatible extensions on Python's import paths. This decision makes sense since tools typically have much richer metadata than what CPython can check. +Typically, build tools and installers use `PyPA packaging metadata`_ and +`platform compatibility tags`_ to communicate compatibility details, but other +models are possible. + +.. _PyPA packaging metadata: https://packaging.python.org/en/latest/specifications/core-metadata/ +.. _platform compatibility tags: https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/ However, CPython will add a line of defense against outdated or misconfigured tools, or human mistakes, in the form of a new *module slot* containing @@ -218,15 +229,45 @@ The ``abi3t`` wheel tag ----------------------- Wheels that use a stable ABI compatible with free-threading CPython builds -should use a new ABI tag: ``abi3t``. +should use a new `ABI tag`_: ``abi3t``. The name is chosen to reflect the fact that this ABI is similar to ``abi3``, -except changes necessary to support free-threading (which uses the letter ``t`` -in existing, version-specific ABI tags like ``cp314t``). +with limitations necessary to support free-threading (which uses the letter +``t`` in existing, version-specific ABI tags like ``cp314t``). + +.. _ABI tag: https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/#abi-tag + +Package installers should treat this tag as completely separate from ``abi3``. +They should allow ``abi3t``-tagged wheels for free-threaded builds wherever +they currently allow ``abi3``-tagged ones for (orherwise equal) non-free-threaded +builds. + +Build tools should generate ``abi3.abi3t`` instead of ``abi3`` when the Python +tag is ``cp315`` and above (or equivalently: when setting ``Py_LIMITED_API`` +to ``3.15`` (``0x030f0000``) or above). +``abi3.abi3t`` is a `compressed tag set`_ that signals compatibility with both +``abi3`` and ``abi3t``. + +.. _compressed tag set: https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/#compressed-tag-sets + +.. note:: + + The version of the Stable ABI is indicated by the `Python wheel tag`_; this + PEP does not change that. + For example, a wheel tagged ``cp315-abi3.abi3t`` will be compatible with + 3.15, 3.16, and later versions; + ``cp317-abi3.abi3t`` will be compatible with 3.17+. -Since wheels built using Limited API 3.15 will be compatible with both -GIL-enabled builds and free-threaded ones, they should use the -`compressed ABI tag set `__ -``abi3.abi3t``. +.. _Python wheel tag: https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/#python-tag + +The ``abi3t`` tag can be used in extensions compatible with earlier versions of +free-threaded Python. +For example, an extension compatible with GIL-enabled *and* free-threaded +builds of *3.14*, 3.15, and higher versions would be tagged +``cpy314-abi3.abi3t``. +This PEP does not propose an official way to build such extensions, but since +a mechanism for that can be added later (see +:ref:`a Rejected idea `), installers should be ready to accept +the tag. New API @@ -297,6 +338,20 @@ free-threaded one. * ❌ * ✅ * ❌ + * * ``cp314-abi3t`` (*) + * ❌ + * ✅ + * ❌ + * ✅ + * ❌ + * ✅ + * * ``cp314-abi3.abi3t`` (*) + * ✅ + * ✅ + * ✅ + * ✅ + * ✅ + * ✅ * * ``cp315-cp315`` * ❌ * ❌ @@ -318,6 +373,13 @@ free-threaded one. * ❌ * ✅ * ❌ + * * ``cp315-abi3t`` (*) + * ❌ + * ❌ + * ❌ + * ✅ + * ❌ + * ✅ * * ``cp315-abi3.abi3t`` * ❌ * ❌ @@ -326,6 +388,8 @@ free-threaded one. * ✅ * ✅ +(*): Wheels with these tags cannot be built; see table below + The following table summarizes which wheel tag should be used for an extension built with a given interpreter and ``Py_LIMITED_API`` macro: @@ -349,6 +413,14 @@ built with a given interpreter and ``Py_LIMITED_API`` macro: * 3.14+ (GIL) * ``PY_PACK_VERSION(3, 14)`` * existing + * * ``cp314-abi3t`` + * N/A + * N/A + * out of spec + * * ``cp314-abi3.abi3t`` + * N/A + * N/A + * reserved * * ``cp315-cp315`` * 3.15 (GIL) * (unset) @@ -361,6 +433,10 @@ built with a given interpreter and ``Py_LIMITED_API`` macro: * 3.15+ (GIL) * ``PY_PACK_VERSION(3, 15)`` * discontinued + * * ``cp315-abi3t`` + * N/A + * N/A + * out of spec * * ``cp315-abi3.abi3t`` * 3.15+ (any) * ``PY_PACK_VERSION(3, 15)`` @@ -371,8 +447,16 @@ Values in the *Note* column: * *existing*: The wheel tag is currently in use * *continued*: The wheel tag continues the existing scheme * *discontinued*: The wheel tag continues the existing scheme, but it will - be discouraged. Older tools may still generate it. + be discouraged. Older tools may still generate it. Installers will + continue to accept it, but only for GIL-enabled builds, even though the wheel + would be compatible with free-threaded ones. * *new*: Proposed in this PEP. +* *reserved*: A mechanism to build a matching extension is not proposed in + this PEP, but may be added in the future. + Installers should be prepared to handle the tag. +* *out of spec*: Should not be used as-is: extensions should be tagged + ``abi3.abi3t`` rather than only ``abi3``. + The entry is included for installers that decompose compressed tag sets. Security Implications @@ -398,7 +482,8 @@ This PEP combines several pieces, implemented individually: ``_Py_OPAQUE_PYOBJECT`` macro. Implemented in GitHub pull request `python/cpython#136505 `__. -- For ``PyModExport``, see :pep:`793`. +- For ``PyModExport``, see :pep:`793` and + `GitHub issue #140550 `_. - A version-checking slot was implemented in GitHub pull request `python/cpython#137212 `__. - A check for older ``abi3`` was implemented in GitHub pull request @@ -430,9 +515,27 @@ It would also make the free-threading memory layout of ``PyObject`` part of the stable ABI, preventing future adjustments. +.. _pep803-no-shim: + Shims for compatibility with CPython 3.14 ----------------------------------------- +It’s possible to build a ``cp314-abi3.abi3t`` extension – one compatible +with 3.14 (both free-threaded build and default). +There are several challenges around this: + +* making it convenient and safe for general extensions +* testing it (as CPython’s test suite doesn’t involve other CPython versions + than the one being tested) + +So, providing a mechanism to build such extensions is best suited to an +external project (for example, one like `pythoncapi-compat`_). +It's out of scope for CPython’s C API, and this PEP. + +.. _pythoncapi-compat: https://github.com/python/pythoncapi-compat + +To sketch how such a mechanism could work: + The main issue that prevents compatibility with Python 3.14 is that with opaque ``PyObject`` and ``PyModuleDef``, it is not feasible to initialize an extension module. @@ -443,12 +546,11 @@ free-threading and GIL-enabled) are “frozen”, so it is possible for an extension to query the running interpreter, and for 3.14, use a ``struct`` definition corresponding to the detected build's ``PyModuleDef``. -This is too onerous to support and test in CPython's Limited API at this point, -but it may be allowed in the future. +.. _pep803-no-avoid-abi3t: -Using the Python wheel tag to determine compatibility ------------------------------------------------------ +Using the Python version wheel tag to determine compatibility +------------------------------------------------------------- A previous version of this PEP avoided adding a new wheel tag (``abi3t``), and specified that wheels tagged ``abi3`` would be compatible with @@ -477,6 +579,16 @@ an unnecessary technical change. Using ``abi3.abi4`` in wheel tags but only ``.abi3`` in filenames would look more inconsistent than ``abi3.abi3t`` and ``.abi3``. +If we add ``abi4`` tag, the ``Py_LIMITED_API`` value would either need to: + +* change to start with ``4`` to match ``abi4``, but no longer correspond + to ``PY_VERSION_HEX`` (making it harder to generate and check), or +* not change, making it inconsistent with ``abi4``. + +Adding ``abi3t`` is a smaller change than adding ``abi4``, making it work +better as a transitional state before larger changes like :pep:`809`'s +``abi2026``. + Copyright ========= diff --git a/peps/pep-0804.rst b/peps/pep-0804.rst index 239567c86d3..0159aec885f 100644 --- a/peps/pep-0804.rst +++ b/peps/pep-0804.rst @@ -27,15 +27,14 @@ Packages on PyPI often require build-time and runtime dependencies that are not present on PyPI. :pep:`725` introduced metadata to express such dependencies. Using concrete external dependency metadata for a Python package requires mapping the given dependency identifiers to the specifiers -used in other ecosystems, which would allow to: +used in other ecosystems, which would allow: - Enabling tools to automatically map external dependencies to packages in other packaging repositories/ecosystems, - Including the needed external dependencies *with the package names used by the relevant system package manager on the user's system* in error messages emitted by Python package installers and build frontends, - as well as allowing the user to query for those names directly to obtain install - instructions. + as well as allowing the user to obtain installation instructions for those packages. Packaging ecosystems like Linux distros, conda, Homebrew, Spack, and Nix need full sets of dependencies for Python packages, and have tools like pyp2rpm_ @@ -45,7 +44,7 @@ upstream Python packages. Before PEP 725, external dependencies were handled man because there was no metadata for this in ``pyproject.toml`` or any other standard metadata file. Enabling its automatic conversion is a key benefit of this PEP, making Python packaging easier and more reliable. In addition, the -authors envision other types of tools making use of this information; e.g., +authors envision other types of tools making use of this information; e.g. dependency analysis tools like Repology_, Dependabot_ and libraries.io_. @@ -72,17 +71,24 @@ Ubuntu and CentOS in a Docker container. RPM-based distributions, like Fedora, can use a `rule-based implementation `__ (``NameConvertor``) in pyp2rpm_. The main rule is that the RPM name for a PyPI package is -``f"python-{pypi_package_name}"``. This seems to work quite well; there are a -few variants like Python version specific names, where the prefix contains the -Python major and minor version numbers (e.g. ``python311-`` instead of -``python-``). +typically ``f"python3-{pypi_package_name}"``. The rare exceptions include packages that +primarily distribute an application, which drop the prefix, (e.g. the Black formatter +is simply ``black``, not ``python3-black``), and variants for different Python versions +(e.g. in RHEL 9 ``setuptools`` can be found as ``python3-setuptools`` for Python 3.9, +but ``python3.11-setuptools`` and ``python3.12-setuptools`` are also available). More details +are available in `Fedora's packaging guidelines for Python `__. + +Debian packages typically follow a ``f"python3-{import_name}"`` naming scheme, with some +exceptions: some sub-communities have an infix (e.g. Django packages go under +``f"python3-django-*"``), and applications are often distributed by their name, with no +``python3-`` prefix. Additional details are available in `Debian's Python Policy `__. Gentoo follows a similar approach to naming Python packages, using the ``dev-python/`` category and some `well-specified rules `__. Conda-forge has a more explicit name mapping, because the base names are the -same in conda-forge as on PyPI (e.g., ``numpy`` maps to ``numpy``), but there -are many exceptions because of both name collisions and renames (e.g., the PyPI +same in conda-forge as on PyPI (e.g. ``numpy`` maps to ``numpy``), but there +are many exceptions because of both name collisions and renames (e.g. the PyPI name for PyTorch is ``torch`` while in conda-forge it's ``pytorch``). There are several name mappings efforts maintained by different teams. Conda-forge's infrastructure generates one in `regro/cf-graph-countyfair `__. @@ -135,8 +141,7 @@ instructions for its external build dependencies (C/C++/Fortran compilers, OpenBLAS, pkg-config): - Debian/Ubuntu: ``sudo apt install -y gcc g++ gfortran libopenblas-dev liblapack-dev pkg-config python3-pip python3-dev`` -- Fedora: ``sudo dnf install gcc-gfortran python3-devel openblas-devel lapack-devel pkgconfig`` -- CentOS/RHEL: ``sudo yum install gcc-gfortran python3-devel openblas-devel lapack-devel pkgconfig`` +- Fedora/CentOS/RHEL: ``sudo dnf install gcc-gfortran python3-devel openblas-devel lapack-devel pkgconfig`` - Arch Linux: ``sudo pacman -S gcc-fortran openblas pkgconf`` - Homebrew on macOS: ``brew install gfortran openblas pkg-config`` @@ -147,10 +152,10 @@ this could be made both more comprehensive and easier to maintain through a tool command with semantics of *"show this ecosystem's preferred package manager install command for all external dependencies"*. This may be done as a standalone tool, or as a new subcommand in any Python development workflow tool -(e.g., Pip, Poetry, Hatch, PDM, uv). +(e.g. Pip, Poetry, Hatch, PDM, uv). To this end, each ecosystem mapping can provide a list of package managers -known to be compatible, with templated instructions on how to install and query +known to be compatible, with templated instructions on how to install and query installed packages. The provided install command templates are paired with query command templates so those tools can check whether the needed packages are already present without having to attempt an install operation (which might be expensive and have unintended @@ -162,7 +167,7 @@ Registry design The mapping infrastructure has been designed to present the following components and properties: - A central registry of PEP 725 identifiers (DepURLs), including at least the - well-known generic and virtual identifiers considered canonical. + well-known ``generic`` and ``virtual`` identifiers considered canonical. - A list of known ecosystems, where ecosystem maintainers can register their name mapping(s). - A standardized schema that defines how mappings should be structured. Each mapping can also provide programmatic details about how their supported package manager(s) work. @@ -230,9 +235,9 @@ of dictionaries, in which each entry consists of: - a ``specs`` field whose value MUST be one of: - - a dictionary with three keys (``build``, ``host``, ``run``). The values + - a dictionary with three keys (``build``, ``build_host``, ``run``). The values MUST be a string or list of strings representing the ecosystem-specific package - identifiers as needed as build-, host- and runtime dependencies (see PEP 725 for + identifiers as needed as build-, host- and runtime dependencies (see :pep:`725` for details on these definitions). - for convenience, a string or a list of strings are also accepted as a @@ -246,45 +251,111 @@ of dictionaries, in which each entry consists of: field will be imported. Either ``specs`` or ``specs_from`` MUST be present. - an optional ``urls`` field whose value MUST be a URL, a list of URLs, or a - dictionary that maps a string to a URL. This is useful to link to external + dictionary that maps a non-empty string to a URL. This is useful to link to external resources that provide more information about the mapped packages. -The mappings SHOULD also specify another section ``package_managers``, reporting +The mappings MUST also specify another section ``package_managers``, reporting which package managers are available in the ecosystem and how to use them. This field MUST -take a list of dictionaries, with each of them reporting the following fields: +take an empty list or a list of dictionaries, with each of them reporting the following fields: - ``name`` (string), unique identifier for this package manager. Usually, the executable name. + - ``commands`` (list of dictionaries), the commands to run to install the mapped package(s) and - check whether they are already installed. -- ``specifier_syntax``: instructions on how to map a subset of PEP 440 specifiers to - the target package manager. Three levels of support are offered: name-only, exact-version-only, - and version-range compatibility (with per-operator translations). - -Each mapping MUST have a canonical URL for online retrieval. These mappings -MAY also be packaged for offline distribution in each platform. The authors -recommend placing in the standard location for data artifacts in each operating + check whether they are already installed. Two types of commands are proposed: + + - ``install``, to generate install instructions. The exit code MUST be ``0`` on success. + + - ``query``, to check whether a given package is already installed. If the package is + installed, the command MUST result in an exit code of ``0``. Otherwise, a non-zero exit code + MUST be returned. + +- ``specifier_syntax``: instructions on how to map a subset of PEP 440 specifiers (as determined + in PEP 725)to the target package manager. Three levels of support are offered: name-only, + exact-version-only, and version-range compatibility (with per-operator translations). + +Each mapping MUST have a canonical URL for online retrieval, with the +filename following the rules described in the section below. These +mappings MAY also be packaged for offline distribution in each platform. The authors +recommend placing them in the standard location for data artifacts in each operating system; e.g. ``$XDG_DATA_DIRS`` on Linux and others, ``~/Library/Application Support`` on macOS, and ``%LOCALAPPDATA%`` for Windows. The subdirectory identifier MUST be ``external-packaging-metadata-mappings``. This data directory SHOULD only -contain mapping documents named ``{ecosystem-identifier}.mapping.json``. The central +contain mapping documents named ``{ecosystem-identifier}.mapping.json`` (see section below +for details on the construction of ecosystem identifiers). The central registry and known ecosystem documents MAY also be distributed in this directory, as ``registry.json`` and ``known-ecosystems.json``, respectively. +.. note:: + + The ``specifier_syntax`` mappings are meant to provide interoperability between + ecosystems where choosing which package version to install is possible. For + example, this is not the case in many Linux distributions, where each distro + release commits to a package version during its lifecycle (although often + with the necessary security backports). + + In these cases, the ``install`` command could be used, optimistically, in + "name-only" mode, hoping that the OS-provided version is a good fit. A more + pessimistic alternative would be to use the ``query`` command first to see if + the available version matches the project constraints, and then install the + package by name. + + Even in those cases, perfect 1:1 version matching is not always possible due to how + different ecosystems map upstream releases to repackaged versions (e.g. the epoch + had to be bumped to accommodate a change of release schema). In that regard, we do + not encode explicit mapping semantics for epochs or pre-releases. + + Known ecosystems ---------------- The list of known ecosystems has two roles: 1. Reporting the canonical URL for its mapping. -2. Assigning a short identifier to each ecosystem. This is the identifier - that MUST be used in the mapping filenames mentioned above so they can be - found in the local filesystem. +2. Assigning a unique, short identifier to each ecosystem. + +Ecosystem identifiers +^^^^^^^^^^^^^^^^^^^^^ + +The identifier MUST conform to this regex: ``[a-z0-9\-_.]+(\+[a-z0-9\-_.])?``. +In other words, a first field optionally followed by a second, separated +by a ``+`` symbol. + +For ecosystems corresponding to Linux distributions, +the first field MUST correspond to the ``ID`` string as specified in the +`os-release `__ +specification. If provided, the second field MUST correspond to the +``VERSION_ID`` string if relevant. + +Since the version field is optional, tools SHOULD try to access the versioned +identifier but fallback to the name-only identifier if not found. -For ecosystems corresponding to Linux distributions, the identifier MUST be the -one reported by their `os-release `__ -``ID`` parameter. For other ecosystems, it MUST be decided during the submission to -the list of known ecosystems document. It MUST only use the characters allowed in -``os-release``'s ``ID`` field, as per this regex ``[a-z0-9\-_.]+``. +The complete filename MUST be ``{identifier}.mapping.json``. + +Some examples: + +.. list-table:: + + * - Ecosystem + - Identifier + - Filename + * - Debian Bookworm + - ``debian+12`` + - ``debian+12.mapping.json`` + * - Fedora 40 + - ``fedora+40`` + - ``fedora+40.mapping.json`` + * - Ubuntu 24.04 + - ``ubuntu+24.04`` + - ``ubuntu+24.04.mapping.json`` + * - Arch Linux (rolling) + - ``arch`` + - ``arch.mapping.json`` + * - Homebrew + - ``homebrew`` + - ``homebrew.mapping.json`` + * - conda-forge + - ``conda-forge`` + - ``conda-forge.mapping.json`` Schema details -------------- @@ -377,7 +448,7 @@ The known ecosystems list is specified by the following * - Type - ``string`` * - Description - - URL of the mappings schema in use for the document. + - URL of the schema in use for the document. * - Required - False @@ -389,6 +460,8 @@ The known ecosystems list is specified by the following * - Type - ``integer`` + * - Description + - Version of the schema in use. * - Required - False @@ -405,7 +478,7 @@ The known ecosystems list is specified by the following * - Required - True -This dictionary maps non-empty string keys referring to the ecosystem identifiers +This dictionary maps non-empty string keys referring to the ecosystem *identifiers* to a sub-dictionary defined as: .. list-table:: @@ -416,7 +489,7 @@ to a sub-dictionary defined as: - Value type - Value description - Required - * - ``Literal['mapping']`` + * - ``mapping`` - ``AnyURL`` - URL to the mapping for this ecosystem - True @@ -448,6 +521,8 @@ The mappings are specified by the following * - Type - ``integer`` + * - Description + - Version of the schema in use. * - Required - False @@ -460,7 +535,7 @@ The mappings are specified by the following * - Type - ``string`` * - Description - - Name of the schema + - Name of the schema. * - Required - True @@ -471,7 +546,7 @@ The mappings are specified by the following :widths: 25 75 * - Type - - ``string | None`` + - ``string`` * - Description - Free-form field to add information this mapping. Allows Markdown. @@ -503,7 +578,7 @@ Each entry in this list is defined as: - Required * - ``id`` - ``DepURLField`` (``string`` matching regex ``^dep:.+$``) - - DepURL, as provided in the central registry + - DepURL, as provided in the central registry. - True * - ``description`` - ``string`` @@ -514,9 +589,9 @@ Each entry in this list is defined as: - Hyperlinks to web locations that provide more information about the definition. - False * - ``specs`` - - ``string | list[string] | dict[Literal['build', 'host', 'run'], string | list[string]]`` + - ``string | list[string] | dict[Literal['build', 'build_host', 'run'], string | list[string]]`` - Ecosystem-specific identifiers for this package. The full form is a dictionary - that maps the categories ``build``, ``host`` and ``run`` to their corresponding + that maps the categories ``build``, ``build_host`` and ``run`` to their corresponding package identifiers. As a shorthand, a single string or a list of strings can be provided, in which case will be used to populate the three categories identically. - Either ``specs`` or ``specs_from`` MUST be present. @@ -555,12 +630,13 @@ Each entry in this list is defined as a dictionary with these fields: - Required * - ``name`` - ``string`` - - Short identifier for this package manager (usually the command name) + - Short identifier for this package manager (usually the command name). - True * - ``commands`` - ``dict[Literal['install', 'query'], dict[Literal['command', 'requires_elevation', 'multiple_specifiers'], list[str] | bool | Literal['always', 'name-only', 'never']]]`` - Commands used to install or query the given package(s). Only two keys - are allowed: ``install`` and ``query``. Their value is a dictionary + are allowed: ``install`` (to generate install instructions) and ``query`` (to + check whether a given package is already installed). Their value is a dictionary with: - a required key ``command`` that takes a list of strings @@ -571,23 +647,23 @@ Each entry in this list is defined as a dictionary with these fields: (e.g. administrator on Windows, superuser on Linux and macOS). - an enum ``multiple_specifiers`` that determines whether the command - accepts multiple package specifiers at the same time, accepting one of: + accepts multiple package specifiers at the same time, taking one of: - - ``always``, default in ``install``. + - ``always``, default in ``install``. - - ``name-only``, the command only accepts multiple specifiers if they do - not contain version constraints. + - ``name-only``, the command only accepts multiple specifiers if they do + not contain version constraints. - - ``never``, default in ``query``. + - ``never``, default in ``query``. - Exactly one of the ``command`` items MUST include a ``{}`` placeholder, - which will be replaced by the mapped package identifier(s). The + Exactly one of the ``command`` items MUST be the ``{}`` placeholder, + which will be replaced by the mapped package specifier(s). The ``install`` command SHOULD support the placeholder being replaced by - multiple identifiers, ``query`` MUST only receive a single identifier + multiple specifiers, ``query`` MUST only receive a single specifier per command. - True * - ``specifier_syntax`` - - ``dict[Literal['name_only', 'exact_version', 'version_ranges'], None | list[str] | dict[Literal['and', 'equal', 'greater_than', 'greater_than_equal', 'less_than', 'less_than_equal', 'not_equal', 'syntax'], None | str | list[str]]`` + - ``dict[Literal['name_only', 'exact_version', 'version_ranges'], None | list[str] | dict[Literal['and', 'equal', 'greater_than', 'greater_than_equal', 'less_than', 'less_than_equal', 'syntax'], None | str | list[str]]`` - Mapping of allowed PEP440 version specifiers to the syntax used in this package manager. Three top-level keys are expected and required: @@ -610,12 +686,12 @@ Each entry in this list is defined as a dictionary with these fields: ``and``). They MAY also include the ``{name}`` placeholder. - the keys ``equal``, ``greater_than``, ``greater_than_equal``, - ``less_than``, ``less_than_equal``, and ``not_equal`` take a string + ``less_than``, and ``less_than_equal`` take a string if the operator is supported, ``None`` otherwise. In the former case, the value MUST include the ``{version}`` placeholder, and MAY include ``{name}``. - - the key ``{and}`` takes a string used to join multiple version + - the key ``and`` takes a string used to join multiple version constraints in a single token, or ``None`` if only a single constraint can be used per token. In the latter case, the different constraints will be "exploded" into several tokens using the @@ -682,10 +758,10 @@ for brevity, could look like: "mappings": [ { "id": "dep:generic/zlib", - "description": "zlib data compression library for the next generation systems. From zlib-ng/zlib-ng.", - "specs": "zlib-ng", // Simplest form + "description": "Massively spiffy yet delicately unobtrusive compression library.", + "specs": "zlib", // Simplest form "urls": { - "feedstock": "https://github.com/conda-forge/zlib-ng-feedstock" + "feedstock": "https://github.com/conda-forge/zlib-feedstock" } }, { @@ -693,7 +769,7 @@ for brevity, could look like: "description": "WebP image library. libwebp-base ships libraries; libwebp ships the binaries.", "specs": { // expanded form with single spec per category "build": "libwebp", - "host": "libwebp-base", + "build_host": "libwebp-base", "run": "libwebp" }, "urls": { @@ -708,7 +784,7 @@ for brevity, could look like: "clang", "clangxx" ], - "host": [ + "build_host": [ "clangdev" ], "run": [ @@ -761,7 +837,6 @@ for brevity, could look like: "greater_than_equal": ">={version}", "less_than": "<{version}", "less_than_equal": "<={version}", - "not_equal": "!={version}", "syntax": [ "{name}{ranges}" ] @@ -832,7 +907,7 @@ plus ``meson-python`` as the build backend: build-requires = [ "dep:virtual/compiler/cxx", ] - host-requires = [ + build-host-requires = [ "dep:generic/zlib", ] @@ -847,18 +922,18 @@ following: build-requires = [ "dep:virtual/compiler/cxx", ] - host-requires = [ + build-host-requires = [ "dep:generic/zlib", ] # show all external dependencies, but mapped to the autodetected ecosystem $ python -m pyproject_external show --output=mapped . [external] - build_requires = [ + build-requires = [ "g++", "python3", ] - host_requires = [ + build-host-requires = [ "zlib1g", "zlib1g-dev", ] @@ -974,9 +1049,11 @@ The ``pyproject-external`` Python API also allows users to do these operations p 'pixi' >>> external.to_dict(mapped_for=ecosystem, package_manager=package_manager) {'external': {'build_requires': ['c-compiler', 'cxx-compiler', 'python']}} - >>> external.install_command(ecosystem, package_manager=package_manager) + >>> external.install_commands(ecosystem, package_manager=package_manager) # {"command": ["pixi", "add", "{}"]} - ['pixi', 'add', 'c-compiler', 'cxx-compiler', 'python'] + [ + ['pixi', 'add', 'c-compiler', 'cxx-compiler', 'python'], + ] >>> external.query_commands(ecosystem, package_manager=package_manager) # {"command": ["pixi", "list", "{}"]} [ @@ -1047,7 +1124,7 @@ connectivity to fetch the mappings from their online sources. Instead: - they should vendor the relevant documents in the distributed packages, - or depend on prepackaged, offline distributions of these documents, -- or implement best-practices for authenticity verification of the fetched documents. +- or implement best practices for authenticity verification of the fetched documents. The install commands have the potential to modify the system configuration of the user. When available, tools should prefer creating ephemeral, isolated environments for the @@ -1083,14 +1160,16 @@ affordances like issue and pull request templates, or linting tools. Package ecosystem maintainers usage ----------------------------------- -Missing mapping entries will result in the absence tailored error messages and +Missing mapping entries will result in the absence of tailored error messages and other UX affordances for end users of the impacted ecosystems. It is thus recommended that each package ecosystem keeps their mappings up-to-date with the central registry. The key to this will be automation, like linting scripts -(see example at `external-metadata-mappings `__), +(see example at `external-metadata-mappings +`__), or periodic notifications via issues or draft submissions. -Establishing the initial mapping is likely to involve a lot of work, but ideally the maintenance on an ongoing basis effort should require smaller effort. +Establishing the initial mapping is likely to involve a lot of work, but ideally +the maintenance on an ongoing basis effort should require smaller effort. As best practices are discovered and agreed on, they should get documented in the central registry repository as learning materials for the mapping @@ -1118,7 +1197,7 @@ true if the user only relies on wheels, since the only impact will be driven by external runtime dependencies (expected to be rare), and even in those cases they need to opt-in by installing a compatible tool. -Users that do opt-in may find missing entries in for their target ecosystems, for +Users that do opt-in may find missing entries for their target ecosystems, for which they should obtain informative error messages that point to the relevant documentation sections. This will allow them to get acquainted with the nature of the issue and its potential solutions. @@ -1126,7 +1205,7 @@ of the issue and its potential solutions. We hope that this results in a subset of them reporting the missing entries, submitting a fix to the affected mapping or, if totally absent, even deciding to maintain a new one on their own. To that end, they should get familiar with -the responsibilties of mapping maintainers (discussed above). +the responsibilities of mapping maintainers (discussed above). Reference Implementation ======================== @@ -1191,7 +1270,7 @@ The reasons include: where that extra metadata can be obtained (e.g. the repository at the URL will likely include details about authorship and licensing). - These details can also be obtained from the actual target ecosystems. In some - cases this might even be preferable; e.g., for licenses, where downstream packaging + cases this might even be preferable; e.g. for licenses, where downstream packaging can actually affect it by unvendoring dependencies or adjusting optional bits. - Those details may change over the lifetime of the project, and keeping them up-to-date would increase the maintenance burden on the governance body. @@ -1199,7 +1278,6 @@ The reasons include: discrepancies across target ecosystems, where different versions may be available or required. - Mapping PyPI projects to repackaged counterparts in target ecosystems --------------------------------------------------------------------- @@ -1263,6 +1341,129 @@ We suggest simply checking that the provided identifiers are well-formed. Future work may choose to also enforce that the identifiers are recognized as canonical, once the central registry has matured with significant adoption. +Inheritance and cross-referenced mappings +----------------------------------------- + +A potential improvement to improve the reusability of mappings is to provide a +mechanism to inherit a parent mapping and extend it or replace it with additional +values. The authors have decided to not add this feature given the implied complexity +(e.g. URL resolution, nested dependencies, possibilities of broken resources). Instead, +the following alternatives are proposed: + +- For mapping authors, automate the generation of derived mappings via scripting and + cron jobs. For example, simple logic such as fetching the parent mapping, applying the + necessary modifications and republishing it to the target location should not result + in much maintenance burden. + +- For end-users wishing to extend a given mapping with custom overrides, client-side + tools should implement the necessary affordances to do this easily. For example, + a tool such as ``pyproject-external`` could provide the following CLI flags or + environment variables: + + - ``--use-mapping`` / ``_USE_MAPPING``: Use the given local or + remote mapping instead of the the canonical location. + + - ``--patch-mapping`` / ``_PATCH_MAPPING``: Given a local or + remote mapping, replace the matching keys in the canonical location + and append the non-matching ones. + + - ``--extend-mapping`` / ``_EXTEND_MAPPING``: Given a local + or remote mapping, append its contents to the canonical one. Assuming + the tool allows the user to pick different mapping options if more than + one is available, this option enriches the set of options without + complete overrides. + +So, for example, given a package with this ``external`` table: + +.. code-block:: toml + + [external] + build-requires = [ + "dep:virtual/compiler/c", + ] + host-requires = [ + "dep:generic/libffi", + ] + +And a target ecosystem that maps ``dep:virtual/compiler/c`` to ``gcc`` +but ``clang`` is preferred, the following mapping override could be provided: + +.. code-block:: json + + { + "$schema": "https://raw.githubusercontent.com/jaimergp/external-metadata-mappings/main/schemas/external-mapping.schema.json", + "schema_version": 1, + "name": "ecosystem override", + "description": "Mapping override for my ecosystem of choice", + "mappings": [ + { + "id": "dep:virtual/compiler/c", + "description": "Clang override", + "specs": "clang" + } + ] + } + +Then, it would be used like this: + +.. code-block:: shell + + $ python -m my-tool show \ + sdist/cryptography-46.0.2.tar.gz \ + --output install-command \ + --patch-mapping=my-override.mapping.json + +Tracking package name changes +----------------------------- + +Packaging ecosystems tend to correct, extend and evolve the naming schemes used. +It is common to split what started as a monolithic build into smaller components +(e.g. avoid shipping development files to runtime-only environments, saving bandwidth). +Some practitioners also use the package name to track ABI compatibility across SONAME +changes. The reasons may be multiple and diverse, but the problem is the same: a +given upstream project name may be distributed as different names over time. + +A proposal to track these changes in the mapping suggested the inclusion of additional +date fields (such as ``valid_from`` and ``valid_to``), but the authors decided to reject +this idea. It adds complexity to the implementation, it is difficult to maintain up-to-date, +and doesn't add value to the end-user, simply serving as a historical record. + +Instead, we expect that versioned distributions maintain a separate mapping per release +(see the proposed mapping naming schemes). Rolling ecosystems should strive to keep +alias packages around, with deprecation warnings if needed and feasible. In general, we +also recommend keeping the mapping files under public version control so end-users +can refer to older versions if necessary. + +Reusing existing databases as a central registry +------------------------------------------------ + +A cursory online search for cross-ecosystem databases of packages would reveal +different sets of results close to the needs of this proposal, but not quite there. +For example: + +- Some solutions only focus on Linux distributions or Unix systems, + like Repology_ or `pkgs.org `__. +- Other services like `Libraries.io `__ require a login. +- Other providers like `ecosyste.ms `__ are only available via APIs. +- The service `purldb `__ only focuses + on collecting concrete PURLs (which identify specific package artifacts), + instead of abstract PURLs concerned with identifying input requirements. + +The proposed mappings try to be as lightweight as possible, without +requiring the maintenance of a live server and an API. Simply a collection +of static JSON files that can be easily updated and distributed online and offline. + +If in the future a service exists providing the following features, then it would +be a strong contender for superseding this PEP: + +- Provides mappings between source DepURLs, PURLs and their repackaged counterparts. + This implies that PURLs have gained the notion of virtual packages and ergonomic + version range expressions. +- Can generate package manager instructions for a given input PURL. +- Can be distributed as local artifacts for offline consumption. +- Does not require a live server or an API. +- FOSS-licensed. + Open Issues =========== diff --git a/peps/pep-0807.rst b/peps/pep-0807.rst index 1e54e30241c..adff6c70f15 100644 --- a/peps/pep-0807.rst +++ b/peps/pep-0807.rst @@ -125,6 +125,11 @@ apply to all parts of this PEP's specification: Receiving servers **SHOULD** respond with a ``406 Not Acceptable`` status code if any other ``Accept`` header is present. +* Unless otherwise specified, all error (4xx and 5xx) responses from the server + **MUST** use the :rfc:`9457` (Problem Details for HTTP APIs) format. + In particular, the server **MUST** use the "Problem Details JSON Object" + defined in :rfc:`Section 3 <9457#section-3>` and **SHOULD** use + the ``application/problem+json`` media type in its responses. Trusted Publishing Discovery ---------------------------- @@ -189,16 +194,10 @@ The discovery mechanism is as follows: If the server does not support Trusted Publishing for the given upload URL, it **MUST** respond with a ``404 Not Found`` status code. -When responding with a ``404 Not Found``, the server **SHOULD NOT** -include a response body. If a response body is included, it **MUST** -be ignored by the client. Servers **MAY** additionally respond with any other standard HTTP -error code in the 400 or 500 range to indicate an error condition. - -Non-``200 OK``, non-``404 Not Found`` responses **MAY** include a body which, -if present, **MUST** be a JSON object containing an -`Error Response `__. +error code in the 400 or 500 range to indicate an appropriate error +condition. Trusted Publishing Token Exchange --------------------------------- @@ -233,11 +232,8 @@ containing a JSON object with the following field: - ``audience``: a string containing the expected OIDC audience. -On failure, the server **MUST** respond with any standard HTTP -error code in the 400 or 500 range to indicate an error condition. -Failure responses **MAY** include a body which, if present, -**MUST** be a JSON object containing an -`Error Response `__. +On failure, the server **MUST** respond with a standard HTTP +error code in the 400 or 500 range to indicate the appropriate error condition. Token Minting ~~~~~~~~~~~~~ @@ -277,29 +273,7 @@ containing a JSON object with the following fields: above) to determine when to refresh the upload credential, if needed. On failure, the server **MUST** respond with any standard HTTP -error code in the 400 or 500 range to indicate an error condition. -Failure responses **MUST** include a body which, if present, -**MUST** be a JSON object containing an `Error Response `__. - -Error Responses ---------------- - -When an error response body is included, it **MUST** be a JSON object -containing the following fields: - -- ``message``: a string containing a short, high-level - human-readable summary of the error. - -- ``errors``: an array of one or more objects, each containing - the following fields: - - - ``code``: a string containing a machine-readable error code. - - ``description``: a string containing a human-readable - description of the error. - -This PEP does not specify any particular error codes. Clients **SHOULD NOT** -assume that error codes are consistent across different indices, and instead -**MUST** treat error codes as opaque strings. +error code in the 400 or 500 range to indicate the appropriate error condition. Security Implications ===================== @@ -417,7 +391,7 @@ Footnotes .. [#fn-hash] - The discovery key may be computed thus: + The discovery key may be computed thusly: .. code-block:: pycon diff --git a/peps/pep-0808.rst b/peps/pep-0808.rst new file mode 100644 index 00000000000..19ce00c5ecb --- /dev/null +++ b/peps/pep-0808.rst @@ -0,0 +1,423 @@ +PEP: 808 +Title: Including static values in dynamic project metadata +Author: Henry Schreiner , + Cristian Le +Sponsor: Filipe Laíns +PEP-Delegate: Paul Moore +Discussions-To: https://discuss.python.org/t/104883 +Status: Draft +Type: Standards Track +Topic: Packaging +Created: 19-Sep-2025 +Post-History: `17-Apr-2025 `__, + `14-Nov-2025 `__ + + +Abstract +======== + +This PEP relaxes the constraint on dynamic metadata listed in the ``[project]`` +section in ``pyproject.toml``. It is now permitted to define a static portion +of a dynamic metadata field in the ``[project]`` table as long as the field is +a table or array. + +This allows users to opt into allowing a backend to extend metadata while still +keeping the static portions of the metadata defined in the standard location in +``pyproject.toml``, and allows inspection tools to still be able to process the +static portions of the metadata. + + +Motivation +========== + +In the core metadata specification originally set out in :pep:`621`, metadata +can be specified in three ways. First, it can be listed in the ``[project]`` +table. This makes it statically inferable, meaning any tool (not just the +build backend) can reliably compute the value. Second, a field can be listed in +the ``project.dynamic`` list, which allows the build backend to compute the +value. Finally, a value could be missing from both the ``project`` table and +the ``project.dynamic`` list, in which case the matching metadata is guaranteed +to be empty. + +This system provided two important benefits to Python packaging. A standard +specification that all major backends have now adopted makes teaching much +easier; a single tutorial is now sufficient to cover the metadata portion of +configuring any backend. Users can now switch from a general purpose backend to +a specialized backend without changing their static metadata. Tooling like +schema validation tools can verify and catch configuration mistakes. + +The second benefit is improved support for static tools that read the source +files looking for metadata. This is useful for dependency chain analysis, such +as creating "used by" and "uses" graphs. It is used for code quality tooling to +detect the minimum supported version of Python. It is used by cibuildwheel_ to +automatically avoid building wheels that are not supported. It is not used, +however, to avoid wheel builds when the SDist is available; that was addressed +by METADATA 2.2, which added a ``Dynamic`` field in the SDist metadata that +lets a tool know if the metadata can change when making a wheel - this is an +easy mistake to make due to the similarity of the names. + +Due to the rapidly increasing popularity of the project table, support from all +major backends, and a rise of backends supporting complex compiled extensions, +an issue with the restrictions applied in :pep:`621` is becoming more apparent. +In PEP 621, the metadata choice is all-or-nothing; metadata must be completely +static, or listed in the dynamic field and completely absent from the static +definition. For the most common use cases, this is fine; there is little +benefit to set the ``version`` statically if you are going to override it +dynamically. If you are using a custom README processor to filter or modify the +README for proper display, it's not a big deal to have to specify the +configuration in a custom ``[tool.*]`` section. But there is a specific class +of cases where the all-or-nothing approach is problematic: lists of items where +the backend needs to add items are currently forced to be fully dynamically +specified (that is, in a backend-specific configuration location). This causes +both of the original benefits (standard location and static tooling support) to +be lost. + +Rationale +========= + + +:pep:`621` includes the following statement: + + In an earlier version of this PEP, tools were allowed to extend data for + fields. For instance, build back-ends could take the version number and add + a local version for when they built the wheel. Tools could also add more + trove classifiers for things like the license or supported Python versions. + + In the end, though, it was thought better to start out stricter and + contemplate loosening how static the data could be considered based on + real-world usage. + +In this PEP, we are proposing a limited and explicit loosening of the +restrictions on the ``[project]`` table and ``project.dynamic`` list. + +Every list and every table with arbitrary keys will now be allowed to be +specified both statically, in the ``[project]`` table, and in the +``project.dynamic`` list. If it is present in both places, the build backend +can extend list items and add new keys, but not modify existing entries. + + +Use Cases +--------- + +There is an entire class of metadata fields where advanced use cases +would really benefit from a relaxation of this rule. Here are some use +cases that have come up: + +- Pinning dependency requirements when building the wheel. When building + PyTorch_ extensions, for example, the version you build with adds a constraint + to the wheel you create that is not present with the SDist. +- Generating extra scripts from a build system (this is currently proposed in + scikit-build-core_). +- Adding entry points dynamically (validate-pyproject-schema-store_ could have + used this to generate an entry point for each schema present in the package). +- Adding dependencies or optional dependencies based on configuration (such as + making an all dependency, or reading dependencies from dependency-groups, for + example). Adding constraints can also be useful; pybind11_ uses a ``global`` + extra that pins ``pybind11-global==``, as both packages are in the + same repository and released in sync. toga_ is a collection of packages that + is currently unable to set any static dependencies due to the same sort of + pinning problem. +- Adding classifiers; some backends can compute classifiers from other places + and inject them (Poetry_ being the best known example). +- Adding license files to the wheel based on what libraries are linked in (this + is an active discussion in follow-up to :pep:`639`). +- Adding SBOMs when building - :pep:`770` had to remove the ``pyproject.toml`` + field specifically because you *want* the build tool to add these, so the + ``[project]`` table setting would be useless, you would almost never be able + to use it. +- Adding generated modules to ``import-names`` or ``import-namespaces`` is + another example. + +All of these use cases have a similar feature: they are adding something +dynamically to a fixed list (possibly a narrower pin for the dependency case). + +You can implement these today, but it requires providing a completely separate +configuration location for the non-extended portion, and static analysis tools +lose the ability to detect anything. Since the current solution is to move all +the metadata out of the standard field, this proposal will increase the +availability of metadata for static tooling. + + +Example: pinning +---------------- + +For example, say you want to allow an imaginary build backend +(``my-build-backend``) to pin to the supported build of PyTorch_. Before this +PEP, you could do this: + +.. code-block:: toml + + [project] + dynamic = ["dependencies"] + + [tool.my-build-backend] + original-dependencies = ["torch", "packaging"] + pin-to-build-versions = ["torch=={exact}"] + +Which would effectively expand to the following SDist metadata: + +.. code-block:: text + + Dynamic: Requires-Dist + Requires-Dist: packaging + Requires-Dist: torch + +Which could then make a wheel with this: + +.. code-block:: text + + Requires-Dist: packaging + Requires-Dist: torch + Requires-Dist: torch==2.8.0 + +Static tooling can no longer tell that ``torch`` and ``packaging`` are runtime +dependencies, and the build backend has to duplicate the dependency table, +making it harder for users to learn and read; the standardized place proposed +by :pep:`621` and adopted by all major build backends is lost. + +With this PEP, this could now be specified like this: + +.. code-block:: toml + + [project] + dependencies = ["torch", "packaging"] + dynamic = ["dependencies"] + + [tool.my-build-backend] + pin-to-build-versions = ["torch=={exact}"] + +Static tooling can now detect the static dependencies, and the build backend no +longer needs to create and document a new location for the standard +``project.dependencies`` field (the ``original-dependencies`` field above, for +example). + + + +Future Updates +-------------- + +Loosening this rule to allow purely additive metadata should address many of +the use cases that have been seen in practice. If further changes are needed, +this can be revisited in a future PEP; this PEP neither recommends nor +precludes future updates like this. + +Terminology +=========== + +The keywords "MUST", "MUST NOT", "REQUIRED", +"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" +in this document are to be interpreted as described in :rfc:`2119`. + +Specification +============= + +Any field that is comprised of a list or a table with arbitrary entries will +now be allowed to be present in both the ``[project]`` table and the +``project.dynamic`` list. If a field is present in both places, then the build +backend is allowed to insert entries into the list or table, but not remove +entries, reorder entries, or modify the entries. Tables of arrays allow adding +a new table entry or extending an existing array according to the rules above. + +The fields that are arrays or tables with arbitrary entries are: + +* ``authors``, ``maintainers``: New author tables can be added to the list. + Existing authors cannot be modified (list of tables with pre-defined keys). +* ``classifiers``: Classifiers can be added to the list. +* ``dependencies``: New dependencies can be added, including more tightly + constrained existing dependencies. +* ``entry-points``: Entry points can be added, to either new or existing + groups. Existing entry points cannot be changed or removed. +* ``scripts``, ``gui-scripts``: New scripts can be added. Existing ones cannot + be changed or removed. +* ``keywords``: Keywords can be added to the list. +* ``license-files``: Files can be added to the list. +* ``optional-dependencies``: A new extra or new items can be added to an + existing extra. +* ``urls``: New urls can be added. Existing ones cannot be changed or removed. +* ``import-names``, ``import-namespaces``: New import names or namespaces can + be added. Existing ones cannot be modified or removed. + +To add items, users must opt in by listing the field in ``dynamic``; without +that, the metadata continues to be entirely static. + +A backend SHOULD error if a field is specified and it does not support +extending that field, to protect against possible user error. We recommend +being as strict as possible to avoid unnecessary entries in the ``dynamic`` +list. + +Static analysis tools, when detecting a field is both specified and in the +``project.dynamic`` array, SHOULD assume the field is incomplete, allowing for +new entries to be present when the package is built. + +The ``Dynamic`` field, as specified in :pep:`643`, is unaffected by this PEP, +and backends can continue to fill it as they choose. However, a backend MUST +ensure that both the SDist and the wheel metadata include the static metadata +portion of the project table. + +Reference Implementation +======================== + +The choice to support dynamic metadata for each field is already left up to +backends, and this PEP simply relaxes restrictions on what a backend is allowed +to do with dynamic metadata. + +The pyproject-metadata_ project, which is used by +several build backends, will need to modify the correctness check to account +for the possible extensions; this is in `a draft PR `__. + +The dynamic-metadata_ project, which provides a plugin +system that backends can use to share dynamic metadata plugins, was designed to +allow this possibility, and a similar PR to the one above will allow additive +metadata. + +Backwards Compatibility +======================= + +Using metadata from SDists or wheels is unaffected. The METADATA version does +not need to be incremented. + +This does not affect any existing ``pyproject.toml`` files, since this was +strictly not allowed before this PEP. + +When users adopt this in a ``pyproject.toml``, the backend must support it; an +error will be correctly generated if it doesn't, following the previous +standard. Frontends were never required to throw an error, though some +frontends may need to be updated to benefit from the partially static metadata. +Some frontends and other tooling may need updating, such as schema +validators, just like other ``pyproject.toml`` PEPs. + +Static analysis tools may require updating to handle this change. Tools should +check the dynamic table first, like this: + +.. code-block:: + + match pyproject["project"]: + # New in PEP 808 + case {my.key: value, "dynamic": dyn} if my.key in dyn: + print(f"Partial {my.key}: {value}") + case {"dynamic": dyn} if my.key in dyn: + print(f"Fully dynamic {my.key}") + case {my.key: value}: + print(f"Fully static {my.key}: {value}") + case _: + print(f"No metadata for {my.key}") + +Before this PEP, tools could reverse the order of the dynamic and static +blocks, assuming that an entry in the project table meant it could not be +dynamic. If they do this, they will now incorrectly assume they have all the +metadata for a field, when they in fact only have part of it. + + +Security Implications +===================== + +There are no security concerns that are not already present, as this just adds +a static component to existing dynamic metadata support. + +How to Teach This +================= + +If you currently have dynamic metadata, but some list or table entries are +known statically, you can now make that explicit by adding the static entry in +the ``[project]`` table, while also keeping the entry in the +``project.dynamic`` list to allow the dynamic portion to be added by your build +backend. + +The current guides that state metadata must not be listed in both ``[project]`` +and ``project.dynamic`` can be updated to say that lists and tables marked with +``project.dynamic`` can still have static entries. Since dynamic metadata is +already an advanced concept, this will likely not affect most existing tutorial +material aimed at introductory packaging. + +The ``pyproject.toml`` `specification `__ will be updated to +include the behavior of fields when specified and also listed in the dynamic +field. + +It should also be noted that specifying something in ``dynamic`` will require +any tool that needs the full metadata to invoke the backend even if it is +partially statically specified. So, it should not be used unless necessary, +just like any other dynamic metadata. + + +Rejected Ideas +============== + +Special case some fields without adding dynamic +----------------------------------------------- + +This has come up specifically for the pinning build dependency use case, but +could also be applied to more of the use cases listed. This would not cover all +the use cases seen, though, and an explicit, opt-in approach is better for +static tooling. + + +Include string fields +--------------------- + +Some string fields could also be extended. Most notably, the ``license`` field +would benefit from being extendable, and due to the semantics of SPDX +expressions, extension could be defined through ``AND``. This was not added to +this PEP because that would require individual fields to have custom semantics. + +The other string fields, namely ``version`` and ``requires-python`` (``name`` +is not allowed to be specified dynamically), have less reason to be extended. +Fixed key tables, like the deprecated ``license.text``/``license.file`` or +``readme.text``/``readme.file`` also have no clear benefit being partially +dynamic. + + +Fully remove restrictions on backends +------------------------------------- + +Another option would be to simply allow backends to do whatever they wanted if +a field is statically defined and in the dynamic array. This would sacrifice +the ability for static tooling to infer anything about the field, and could +potentially confuse users by allowing the backend to ignore or change what they +entered. This is not worse than the status quo for static tooling and dynamic +metadata, but the current proposal improves the ability of static tooling to +infer some things about dynamic fields. Knowing some of the dependencies is +better for most applications than not knowing anything about the dependencies, +for example. + +Allow simplifications +--------------------- + +An earlier draft of this PEP had a clause allowing backends to simplify some +types of fields; most notably dependency specifiers would have allowed +"tightening", such as ``torch`` being replaced by ``torch>=1.2``, for example. +This was removed due to it being impossible to ensure a variation will resolve +identically on all resolvers within the current specification, and to simplify +the contract with backends. Any other simplifications would be purely cosmetic, +and so were left out. The order in the current PEP is now required to match the +original static metadata, with the dynamic portion only allowing insertions. + + +Add a general mechanism to specify dynamic-metadata +--------------------------------------------------- + +This PEP does not cover methods to specify dynamic metadata; that continues to +be entirely up to the backend. An earlier draft proposal did this, but it was +deemed better to develop that as a library (dynamic-metadata_, for the curious) +instead. This may be revisited in the future. + +References +========== + +.. _cibuildwheel: https://cibuildwheel.pypa.io +.. _pyprojectspec: https://packaging.python.org/en/latest/specifications/pyproject-toml +.. _pyproject-metadata: https://github.com/pypa/pyproject-metadata +.. _pyprojectmetadatapr: https://github.com/pypa/pyproject-metadata/pull/241 +.. _dynamic-metadata: https://github.com/scikit-build/dynamic-metadata +.. _PyTorch: https://pytorch.org/ +.. _scikit-build-core: https://github.com/scikit-build/scikit-build-core +.. _validate-pyproject-schema-store: https://pypi.org/project/validate-pyproject-schema-store/ +.. _pybind11: https://github.com/pybind/pybind11 +.. _Poetry: https://python-poetry.org/ +.. _setuptools: https://github.com/pypa/setuptools +.. _toga: https://github.com/beeware/toga + +Copyright +========= + +This document is placed in the public domain or under the +CC0-1.0-Universal license, whichever is more permissive. diff --git a/peps/pep-0810.rst b/peps/pep-0810.rst index c2b7e2a2e39..db84fa16f80 100644 --- a/peps/pep-0810.rst +++ b/peps/pep-0810.rst @@ -1,6 +1,6 @@ PEP: 810 Title: Explicit lazy imports -Author: Pablo Galindo , +Author: Pablo Galindo Salgado , Germán Méndez Bravo , Thomas Wouters , Dino Viehland , @@ -8,12 +8,13 @@ Author: Pablo Galindo , Noah Kim , Tim Stumbaugh Discussions-To: https://discuss.python.org/t/104131 -Status: Draft +Status: Accepted Type: Standards Track Created: 02-Oct-2025 Python-Version: 3.15 Post-History: `03-Oct-2025 `__, +Resolution: `03-Nov-2025 `__ Abstract @@ -361,6 +362,15 @@ being imported, and (if applicable) the fromlist. An import remains lazy only if the filter function returns ``True``. If no lazy import filter is set, all *potentially lazy* imports are lazy. +Note on .pth files +~~~~~~~~~~~~~~~~~~ + +The lazy import mechanism does not apply to .pth files processed by the +``site`` module. While .pth files have special handling for lines that begin +with ``import`` followed by a space or tab, this special handling will not be +adapted to support lazy imports. Imports specified in .pth files remain eager +as they always have been. + Lazy objects ------------ @@ -617,6 +627,9 @@ lazy imports filter: ``"normal"`` (respect ``lazy`` keyword only), ``"all"`` (force all imports to be potentially lazy), or ``"none"`` (force all imports to be eager). +* ``sys.get_lazy_imports()`` - Returns the current lazy imports mode as a + string: ``"normal"``, ``"all"``, or ``"none"``. + The filter function is called for every potentially lazy import, and must return ``True`` if the import should be lazy. This allows for fine-grained control over which imports should be lazy, useful for excluding modules with @@ -692,6 +705,11 @@ The global lazy imports flag can be controlled through: * The ``PYTHON_LAZY_IMPORTS=`` environment variable * The ``sys.set_lazy_imports(mode)`` function (primarily for testing) +The precedence order for setting the lazy imports mode follows the standard +Python pattern: ``sys.set_lazy_imports()`` takes highest precedence, followed +by ``-X lazy_imports=``, then ``PYTHON_LAZY_IMPORTS=``. If none +are specified, the mode defaults to ``"normal"``. + Where ```` can be: * ``"normal"`` (or unset): Only explicitly marked lazy imports are lazy diff --git a/peps/pep-0811.rst b/peps/pep-0811.rst index 88ffee687e3..5f9307d2a25 100644 --- a/peps/pep-0811.rst +++ b/peps/pep-0811.rst @@ -3,13 +3,14 @@ Title: Defining Python Security Response Team membership and responsibilities Author: Seth Michael Larson Sponsor: Gregory P. Smith Discussions-To: https://discuss.python.org/t/104606 -Status: Draft +Status: Accepted Type: Process Topic: Governance Created: 22-Oct-2025 Post-History: `06-Oct-2025 `__, `28-Oct-2025 `__, +Resolution: `04-Dec-2025 `__ Abstract ======== @@ -59,10 +60,9 @@ Onboarding new contributors to the PSRT Unlike most open-source contributions, the work of the PSRT doesn't happen in the open. Instead, most work occurs privately by a trusted group to limit -access to undisclosed -vulnerability reports. Given the sensitive nature of this work, it appears opaque from the outside, and -it's difficult to get started as a newcomer and to understand the -expectations of the group. +access to undisclosed vulnerability reports. Given the sensitive nature of this +work, it appears opaque from the outside, and it's difficult to get started as a +newcomer and to understand the expectations of the group. In practice this has meant that relatively few new members join the PSRT, which over time could negatively impact the group's ability to triage reports @@ -110,8 +110,7 @@ decide who should be admitted, but there must be a system responsible for evaluating PSRT membership. This PEP proposes limiting PSRT membership only to active coordinators -of security vulnerabilities that are core developers or triagers, -members involved in oversight (Steering Council), +of security vulnerabilities, members involved in oversight (Steering Council), and members who need information about security releases (Release Managers). Activity is recommended as the metric for membership to avoid adding additional @@ -154,12 +153,14 @@ Specification PSRT Membership Policy ---------------------- -The Python Steering Council may add or remove members and admins of the PSRT. -New PSRT members must be core team members, triagers, or PSF staff, -and must be `proposed to and accepted`_ by the Steering Council. +The PSRT will run nominations `similar to core team nominations`_, where +a nomination of a new member is brought to the PSRT by an existing PSRT member +and then that nomination is voted on by existing PSRT members. New members +are expected to be drawn from core developers, triagers, or PSF staff. +It is granted by receiving at least two-thirds positive votes from a vote of +existing PSRT members that is open for one week and is not vetoed by the +Steering Council. -Once the Steering Council votes on a membership change to the PSRT then -PSRT admins will enact the change. A list of PSRT members will be published publicly and kept up-to-date by PSRT admins. @@ -167,6 +168,7 @@ Once per year the Steering Council will receive a report of inactive members of the PSRT with the recommendation to remove the inactive users from the PSRT. "Inactive" is defined here as a member who hasn't coordinated or commented on a vulnerability report in the past year since the last report was generated. +The Steering Council may remove members of the PSRT with a simple vote. Members of the PSRT who are a Release Manager or Steering Council member may remain in the PSRT regardless of inactivity in vulnerability reports. @@ -176,11 +178,7 @@ in the past year and without an exemption for minimum activity (Steering Council Release Managers) prior to publication of this PEP. At the time of writing, this would reduce the PSRT membership size to ~15 members from ~30. -This PEP also proposes not removing members of the PSRT who are active but -not yet core team members or triagers, allowing them to be "legacied" in -to the new PSRT Membership Policy. - -.. _proposed to and accepted: https://github.com/python/steering-council/ +.. _similar to core team nominations: https://devguide.python.org/core-team/join-team/ PSRT Admins ~~~~~~~~~~~ @@ -236,7 +234,7 @@ following additional responsibilities: * Managing the GitHub team, mailing list, Discord channel, and other PSRT venues to ensure they are synchronized with the canonical list of - PSRT members determined by the Steering Council. + PSRT members. * On a yearly basis, providing the Steering Council with a report including a list of inactive PSRT members. diff --git a/peps/pep-0814.rst b/peps/pep-0814.rst new file mode 100644 index 00000000000..91ac0b3e649 --- /dev/null +++ b/peps/pep-0814.rst @@ -0,0 +1,449 @@ +PEP: 814 +Title: Add frozendict built-in type +Author: Victor Stinner , Donghee Na +Discussions-To: https://discuss.python.org/t/104854 +Status: Draft +Type: Standards Track +Created: 12-Nov-2025 +Python-Version: 3.15 +Post-History: `13-Nov-2025 `__ + + +Abstract +======== + +A new public immutable type ``frozendict`` is added to the ``builtins`` +module. + +We expect ``frozendict`` to be safe by design, as it prevents any unintended +modifications. This addition benefits not only CPython’s standard +library, but also third-party maintainers who can take advantage of a +reliable, immutable dictionary type. + + +Rationale +========= + +The proposed ``frozendict`` type: + +* implements the ``collections.abc.Mapping`` protocol, +* supports pickling. + +The following use cases illustrate why an immutable mapping is +desirable: + +* Immutable mappings are hashable which allows their use as dictionary + keys or set elements. + +* This hashable property permits functions decorated with + ``@functools.lru_cache()`` to accept immutable mappings as arguments. + Unlike an immutable mapping, passing a plain ``dict`` to such a function + results in error. + +* Using an immutable mapping as a function parameter's default value + avoids the problem of mutable default values. + +* Immutable mappings can be used to safely share dictionaries across + thread and asynchronous task boundaries. The immutability makes it + easier to reason about threads and asynchronous tasks. + +There are already third-party ``frozendict`` and ``frozenmap`` packages +available on PyPI, proving that there is demand for +immutable mappings. + + +Specification +============= + +A new public immutable type ``frozendict`` is added to the ``builtins`` +module. It is not a ``dict`` subclass but inherits directly from +``object``. + + +Construction +------------ + +``frozendict`` implements a ``dict``-like construction API: + +* ``frozendict()`` creates a new empty immutable mapping. + +* ``frozendict(**kwargs)`` creates a mapping from ``**kwargs``, + e.g. ``frozendict(x=1, y=2)``. + +* ``frozendict(collection)`` creates a mapping from the passed + collection object. The passed collection object can be: + + - a ``dict``, + - another ``frozendict``, + - or an iterable of key/value tuples. + +* ``frozendict(collection, **kwargs)`` combines the two previous + constructions. + +Keys must be hashable, but values can be non-hashable. +Using hashable values creates a hashable ``frozendict``. + +Creating a ``frozendict`` from a ``dict``, ``frozendict(dict)``, has a +complexity of *O*\ (*n*): items are copied (shallow copy). + +The insertion order is preserved. + + +Iteration +--------- + +As ``frozendict`` implements the standard ``collections.abc.Mapping`` +protocol, so all expected methods of iteration are supported:: + + assert list(m.items()) == [('foo', 'bar')] + assert list(m.keys()) == ['foo'] + assert list(m.values()) == ['bar'] + assert list(m) == ['foo'] + +Iterating on ``frozendict``, as on ``dict``, uses the insertion order. + + +Hashing and Comparison +---------------------- + +``frozendict`` instances can be hashable just like tuple objects:: + + hash(frozendict(foo='bar')) # works + hash(frozendict(foo=['a', 'b', 'c'])) # error, list is not hashable + +The hash value does not depend on the items' order. It is computed on +keys and values. Pseudo-code of ``hash(frozendict)``:: + + hash(frozenset(frozendict.items())) + +Equality test does not depend on the items' order either. Example:: + + >>> a = frozendict(x=1, y=2) + >>> b = frozendict(y=2, x=1) + >>> hash(a) == hash(b) + True + >>> a == b + True + +It's possible to compare ``frozendict`` to ``dict``. Example:: + + >>> frozendict(x=1, y=2) == dict(x=1, y=2) + True + + +Union operators +--------------- + +It's possible to join two ``frozendict``, or a ``frozendict`` with a +``dict``, with the merge (``|``) operator. Example:: + + >>> frozendict(x=1) | frozendict(y=1) + frozendict({'x': 1, 'y': 1}) + >>> frozendict(x=1) | dict(y=1) + frozendict({'x': 1, 'y': 1}) + +If some keys are in common, the values of the right operand are taken:: + + >>> frozendict(x=1, y=2) | frozendict(y=5) + frozendict({'x': 1, 'y': 5}) + +The update operator ``|=`` does not modify a ``frozendict`` in-place, but +creates a new ``frozendict``:: + + >>> d = frozendict(x=1) + >>> copy = d + >>> d |= frozendict(y=2) + >>> d + frozendict({'x': 1, 'y': 2}) + >>> copy # left unchanged + frozendict({'x': 1}) + +See also :pep:`584` "Add Union Operators To dict". + + +Copy +---- + +``frozendict.copy()`` returns a shallow copy. In CPython, it simply +returns the same ``frozendict`` (new reference). + +Use ``copy.deepcopy()`` to get a deep copy. + +Example:: + + >>> import copy + >>> d = frozendict(mutable=[]) + >>> shallow_copy = d.copy() + >>> deep_copy = copy.deepcopy(d) + >>> d['mutable'].append('modified') + >>> d + frozendict({'mutable': ['modified']}) + >>> shallow_copy # modified! + frozendict({'mutable': ['modified']}) + >>> deep_copy # unchanged + frozendict({'mutable': []}) + + +Typing +------ + +It is possible to use the standard typing notation for ``frozendict``\ s:: + + m: frozendict[str, int] = frozendict(x=1) + + +Representation +-------------- + +``frozendict`` will not use a special syntax for its representation. +The ``repr()`` of a ``frozendict`` instance looks like this: + + >>> frozendict(x=1, y=2) + frozendict({'x': 1, 'y': 2}) + + +C API +----- + +Add the following APIs: + +* ``PyAnyDict_Check(op)`` macro +* ``PyAnyDict_CheckExact(op)`` macro +* ``PyFrozenDict_Check()`` macro +* ``PyFrozenDict_CheckExact()`` macro +* ``PyFrozenDict_New(collection)`` function +* ``PyFrozenDict_Type`` + +Even if ``frozendict`` is not a ``dict`` subclass, it can be used with +``PyDict_GetItemRef()`` and similar "PyDict_Get" functions. + +Passing a ``frozendict`` to ``PyDict_SetItem()`` or ``PyDict_DelItem()`` +fails with ``TypeError``. ``PyDict_Check()`` on a ``frozendict`` is +false. + +Exposing the C API helps authors of C extensions supporting +``frozendict`` when they need to support thread-safe immutable +containers. It will be important since +:pep:`779` (Criteria for supported status for free-threaded Python) was +accepted, people need this for their migration. + + +Differences between ``dict`` and ``frozendict`` +=============================================== + +* ``dict`` has more methods than ``frozendict``: + + * ``__delitem__(key)`` + * ``__setitem__(key, value)`` + * ``clear()`` + * ``pop(key)`` + * ``popitem()`` + * ``setdefault(key, value)`` + * ``update(*args, **kwargs)`` + +* A ``frozendict`` can be hashed with ``hash(frozendict)`` if all keys + and values can be hashed. + + +Possible candidates for ``frozendict`` in the stdlib +==================================================== + +We have identified several stdlib modules where adopting ``frozendict`` +can enhance safety and prevent unintended modifications by design. We +also believe that there are additional potential use cases beyond the +ones listed below. + +Note: it remains possible to bind again a variable to a new modified +``frozendict`` or a new mutable ``dict``. + +Python modules +-------------- + +Replace ``dict`` with ``frozendict`` in function results: + +* ``email.headerregistry``: ``ParameterizedMIMEHeader.params()`` + (replace ``MappingProxyType``) +* ``enum``: ``EnumType.__members__()`` (replace ``MappingProxyType``) + +Replace ``dict`` with ``frozendict`` for constants: + +* ``_opcode_metadata``: ``_specializations``, ``_specialized_opmap``, + ``opmap`` +* ``_pydatetime``: ``specs`` (in ``_format_time()``) +* ``_pydecimal``: ``_condition_map`` +* ``bdb``: ``_MonitoringTracer.EVENT_CALLBACK_MAP`` +* ``dataclasses``: ``_hash_action`` +* ``dis``: ``deoptmap``, ``COMPILER_FLAG_NAMES`` +* ``functools``: ``_convert`` +* ``gettext``: ``_binary_ops``, ``_c2py_ops`` +* ``imaplib``: ``Commands``, ``Mon2num`` +* ``json.decoder``: ``_CONSTANTS``, ``BACKSLASH`` +* ``json.encoder``: ``ESCAPE_DCT`` +* ``json.tool``: ``_group_to_theme_color`` +* ``locale``: ``locale_encoding_alias``, ``locale_alias``, + ``windows_locale`` +* ``opcode``: ``_cache_format``, ``_inline_cache_entries`` +* ``optparse``: ``_builtin_cvt`` +* ``platform``: ``_ver_stages``, ``_default_architecture`` +* ``plistlib``: ``_BINARY_FORMAT`` +* ``ssl``: ``_PROTOCOL_NAMES`` +* ``stringprep``: ``b3_exceptions`` +* ``symtable``: ``_scopes_value_to_name`` +* ``tarfile``: ``PAX_NUMBER_FIELDS``, ``_NAMED_FILTERS`` +* ``token``: ``tok_name``, ``EXACT_TOKEN_TYPES`` +* ``tomllib._parser``: ``BASIC_STR_ESCAPE_REPLACEMENTS`` +* ``typing``: ``_PROTO_ALLOWLIST`` + +Accept ``frozendict`` type: + +* ``builtins``: ``eval()`` and ``exec()`` (*globals* argument) + +Extension modules +----------------- + +Replace ``dict`` with ``frozendict`` for constants: + +* ``errno``: ``errorcode`` + + +Relationship to PEP 416 frozendict +================================== + +Since 2012 (:pep:`416`), the Python ecosystem has evolved: + +* ``asyncio`` was added in 2014 (Python 3.4) +* Free threading was added in 2024 (Python 3.13) +* ``concurrent.interpreters`` was added in 2025 (Python 3.14) + +There are now more use cases to share immutable mappings. + +``frozendict`` now preserves the insertion order, whereas PEP 416 +``frozendict`` was unordered (as :pep:`603` ``frozenmap``). ``frozendict`` +relies on the ``dict`` implementation which preserves the insertion +order since Python 3.6. + +The first motivation to add ``frozendict`` was to implement a sandbox +in Python. It's no longer the case in this PEP. + +``types.MappingProxyType`` was added in 2012 (Python 3.3). This type is +not hashable and it's not possible to inherit from it. It's also easy to +retrieve the original dictionary which can be mutated, for example using +``gc.get_referents()``. + + +Relationship to PEP 603 frozenmap +================================= + +``collections.frozenmap`` has different properties than frozendict: + +* ``frozenmap`` items are unordered, whereas ``frozendict`` preserves + the insertion order. +* ``frozenmap`` has additional methods: + + * ``including(key, value)`` + * ``excluding(key)`` + * ``union(mapping=None, **kw)`` + + These methods to mutate a ``frozenmap`` have a complexity of *O*\ (1). + +* A mapping lookup (``mapping[key]``) has a complexity of *O*\ (log *n*) + with ``frozenmap`` and a complexity of *O*\ (1) with ``frozendict``. + + +Reference Implementation +======================== + +* https://github.com/python/cpython/pull/141508 +* ``frozendict`` shares most of its code with the ``dict`` type. +* Add ``PyFrozenDictObject`` structure which inherits from + ``PyDictObject`` and has an additional ``ma_hash`` member. + + +Thread Safety +============= + +Once the ``frozendict`` is created, it is immutable and can be shared +safely between threads without any synchronization. + + +Future Work +=========== + +We are also going to make ``frozendict`` to be more efficient in terms +of memory usage and performance compared to ``dict`` in future. + + +Rejected Ideas +============== + +Inherit from dict +----------------- + +If ``frozendict`` inherits from ``dict``, it would become possible to +call ``dict`` methods to mutate an immutable ``frozendict``. For +example, it would be possible to call +``dict.__setitem__(frozendict, key, value)``. + +It may be possible to prevent modifying ``frozendict`` using ``dict`` +methods, but that would require to explicitly exclude ``frozendict`` +which can affect ``dict`` performance. Also, there is a higher risk of +forgetting to exclude ``frozendict`` in some methods. + +If ``frozendict`` does not inherit from ``dict``, there is no such +issue. + + +Deferred Ideas +============== + +New syntax for ``frozendict`` literals +-------------------------------------- + +Various syntaxes have been proposed to write ``frozendict`` literals. + +A new syntax can be added later if needed. + +Method to convert ``dict`` to ``frozendict`` +-------------------------------------------- + +Different methods have been proposed to convert a mutable ``dict`` to an +immutable ``frozendict`` with *O*\ (1) complexity, such as +``dict.freeze()``. The idea would be to move ``dict`` contents into +``frozendict``: it would make the ``dict`` empty. Another idea would be +to use "copy-on-write": only copy the ``dict`` at its first +modification. + +We consider that such method can be added later if needed, but it +doesn't have to be added right now. Moreover, if such method is added, +it would be nice to add a similar method for ``list``/``tuple`` and +``set``/``frozenset``. See also :pep:`351` (Freeze protocol). + +Type annotation +--------------- + +It `has been proposed +`__ +to add ``class TD(TypedDict, frozen=True)`` or ``Frozen[MyTypedDict]`` +to define a ``frozendict`` type. + +We consider that such type can be added later if needed. + + +References +========== + +* :pep:`416` (``frozendict``) +* :pep:`603` (``collections.frozenmap``) + + +Acknowledgements +================ + +This PEP is based on prior work from Yury Selivanov (:pep:`603`). + + +Copyright +========= + +This document is placed in the public domain or under the +CC0-1.0-Universal license, whichever is more permissive. diff --git a/peps/pep-0815.rst b/peps/pep-0815.rst new file mode 100644 index 00000000000..4fd6967a87c --- /dev/null +++ b/peps/pep-0815.rst @@ -0,0 +1,81 @@ +PEP: 815 +Title: Deprecate ``RECORD.jws`` and ``RECORD.p7s`` +Author: Konstantin Schütze , + William Woodruff +Sponsor: Emma Harper Smith +PEP-Delegate: Paul Moore +Discussions-To: https://discuss.python.org/t/105232 +Status: Draft +Type: Standards Track +Topic: Packaging +Created: 04-Dec-2025 +Post-History: `09-Jun-2025 `__, + `08-Dec-2025 `__, + + +Abstract +======== + +This PEP deprecates the ``RECORD.jws`` and ``RECORD.p7s`` wheel signature +files. Lack of support in tooling means that these virtually unused files do +not provide the security they purport. Users looking for wheel signing should +instead refer to :ref:`index hosted attestations +`. + + +Motivation +========== + +No major Python packaging tool supports generating or checking either +``RECORD.jws`` or ``RECORD.p7s``. Notably, neither pip nor uv validate the +hashes in ``RECORD``, a requirement for using signature files. The +:ref:`binary distribution format ` +presents them as security features, potentially resulting in user confusion. + +The state of the art for hashing and signing wheels has shifted from +in-archive information to out-of-archive information presented on the index, +such as hashes and :ref:`attestations ` +in the :ref:`simple repository API `. Unlike +the hashes in ``RECORD``, tools such as pip and uv validate index provided +hashes. + +Both files are virtually unused. A GitHub search for ``path:**.dist-info/RECORD`` +yields 635k results, ``path:**.dist-info/RECORD.jws`` has 8 distinct results +and ``path:**.dist-info/RECORD.p7s`` has zero results. + + +Specification +============= + +The ``RECORD.jws`` and ``RECORD.p7s`` files are deprecated, and the +:ref:`binary distribution format specification +` will be updated to reflect this. Build +backends and other tools MUST NOT add these files to wheels. Installers +SHOULD NOT attempt to verify them, while they remain excluded from ``RECORD``. + + +Backwards Compatibility +======================= + +No build backends and installers that the authors are aware of require any +changes, as they do not support these files beyond skipping them when +processing the ``RECORD`` file. If any build backends do currently write these +files, they need to deprecate and eventually remove this feature. + +For verifying provenance, users should refer to +:ref:`index hosted attestations `. + + +Security Implications +===================== + +This PEP strengthens the security of the Python packaging ecosystem by +reducing the divergence between security features presented in the +specification and the security features supported by tools. + + +Copyright +========= + +This document is placed in the public domain or under the +CC0-1.0-Universal license, whichever is more permissive. diff --git a/peps/pep-0816.rst b/peps/pep-0816.rst new file mode 100644 index 00000000000..333cc0f6f95 --- /dev/null +++ b/peps/pep-0816.rst @@ -0,0 +1,172 @@ +PEP: 816 +Title: WASI Support +Author: Brett Cannon +Discussions-To: Pending +Status: Draft +Type: Informational +Created: 05-Nov-2025 +Post-History: Pending + + +Abstract +======== + +This PEP outlines the expected support for WASI_ by CPython. It contains enough +details to know what WASI and WASI SDK version is expected to be supported for +any release of CPython while official WASI support is specified in :pep:`11`. + + +Motivation +========== + +CPython has supported WASI according to :pep:`11` since Python 3.11. As part of +this support, CPython needs to target two different things: the WASI_ version +and the `WASI SDK`_ version (both of whose development is driven by the +`Bytecode Alliance`_). The former is the specification of WASI itself while the +latter is a version of clang_ with a specific version of wasi-libc_ as the +sysroot that allows for compiling CPython to WASI. There is roughly an annual +release cadence for new WASI versions while there's no set release cadence for +WASI SDK. + +Agreeing on which WASI and WASI SDK versions to support allows for clear +targeting of the CPython code base towards those versions. This also lets the +community set appropriate expectations as to what will (not) be considered a +bug if it only manifests itself under a certain WASI or WASI SDK version. It +also provides the community an overall target for WASI and WASI SDK for any +specific Python version when building other software like libraries. This is +important as WASI SDK is NOT forwards- or backwards-compatible due to the ABI +of wasi-libc not making any compatibility guarantees, making broad coordination +important so code works together. + +This coordination and recording around version targets can be important in +situations where the selected version is non-obvious. For example, WASI SDK 26 +and 27 have a `bug `__ +which cause CPython to hang in certain situations (including exiting the REPL). +Because the bug is in thread support code it wouldn't necessarily be obvious to +others in the community that CPython will not target those versions and thus +3rd-party code should also avoid those versions. + + +Rationale +========= + +While technically separate, CPython cannot support a version of WASI until WASI +SDK supports it. WASI versions are considered backwards-compatible with each +other, but WASI SDK is NOT compatible forwards or backwards thanks to wasi-libc +not having ABI compatibility guarantees. As such, it's important to set support +expectations for a specific WASI SDK version in CPython. Historically, the +support difference between WASI SDK versions for CPython have involved settings +in the ``config.site`` file that is maintained for WASI. Support issues have +come up due to bugs in WASI SDK itself. Finally, new features being supported +by WASI SDK can also cause code that wasn't previously used in a WASI build to +suddenly be run which can require code updates to pass tests. + +As for WASI version support, that typically translates into what stdlib modules +are (potentially) usable with a WASI build. For example, WASI 0.2 added socket +support and WASI 0.3.1 is scheduled to have some form of threading support. +Knowing what WASI version is supported sets expectations for what stdlib +modules should be supported. + +Interpreter feature availability can be dependent on the WASI version. For +instance, until there is threading support there can't be free-threading +support in WASI. Once again, this helps set expectations of what should be +working based on the target WASI version. + + +Specification +============= + +The WASI and WASI SDK version supported by a CPython version in CI or its +stable Buildbot builder when b1 is released MUST be the version to be supported +for the lifetime of that Python version after this PEP is accepted. If there is +a discrepancy between CI and the Buildbot builder for some reason, the WASI +maintainers as specified by :pep:`11` will choose which sets of versions will +be designated as the versions to support. + +The WASI version and WASI SDK version supported for a Python version MUST be +recorded in :pep:`11` as an official record. + +If for some reason a supported WASI SDK version needs to change after being +recorded, a note will be made in :pep:`11` as to when and why the change was +made. Such a change is at the discretion of the maintainers of WASI support as +listed in :pep:`11` and does not require steering council approval. The +expectation, though, is that such a change SHOULD NOT occur. + +Changing the WASI version after it has been recorded MUST NOT occur UNLESS the +steering council approves it. This is due to the increased support burden for +new WASI versions and the shift in expectations of what CPython would support +when support expectations have already been set. + +Any updates to :pep:`11` to reflect the appropriate WASI version for the target +triple for the main branch MUST be made as needed, but it does NOT require +steering council approval to update. The steering council is spared needing to +approve such an update as it does not constitute a new platform and is more in +line with a new OS release which currently does not require steering council +approval. + + +Designated Support +================== + +Note that while WASI SDK in some places lists both a major and minor version, +in actuality the minor version has never been set to anything other than ``0`` +and there's an expectation that +`any minor version will be ABI compatible with the overall major version `__. +As well, the WASI community only refers to WASI SDK versions by their major +version due to there having never been a minor release. Subsequently, this PEP +only records the major version of WASI SDK until such time that there's a need +to record a minor version. + +====== ==== ======== +Python WASI WASI SDK +====== ==== ======== +3.14 0.1 24 +3.13 0.1 24 +3.12 0.1 16 +3.11 0.1 16 +====== ==== ======== + + +Notes +----- + +All versions prior to Python 3.15 predate this PEP. The version support for +those versions is based on what was supported when this PEP was written. + +WASI was a tier 3 platform according to :pep:`11` for Python 3.11 and 3.12. +WASI became a tier 2 platform starting with Python 3.13. + +WASI 0.2 support has been skipped due to lack of time, to the point that it was +deemed better to go straight to WASI 0.3 instead. This is based on a +recommendation from the `Bytecode Alliance`_. + +WASI SDK 26 and 27 have a +`bug `__ which causes +CPython to hang in certain situations, and so they have been skipped. + + +Acknowledgements +================ + +Thanks to Joel Dice and Ben Brandt of the Python +`sub-group `__ +of the +`guest languages SIG `__ +of the `Bytecode Alliance`_ for discussing the specification of this PEP. + + +Footnotes +========= + +.. _WASI: https://wasi.dev +.. _WASI SDK: https://github.com/WebAssembly/wasi-sdk +.. _wasi-libc: https://github.com/WebAssembly/wasi-libc +.. _clang: https://clang.llvm.org +.. _Bytecode Alliance: https://bytecodealliance.org + + +Copyright +========= + +This document is placed in the public domain or under the +CC0-1.0-Universal license, whichever is more permissive. diff --git a/peps/pep-8107.rst b/peps/pep-8107.rst index 83199daf109..c6eade4448d 100644 --- a/peps/pep-8107.rst +++ b/peps/pep-8107.rst @@ -2,7 +2,7 @@ PEP: 8107 Title: 2026 Term Steering Council election Author: Ee Durbin Sponsor: Barry Warsaw -Status: Active +Status: Final Type: Informational Topic: Governance Created: 21-Oct-2025 @@ -28,13 +28,13 @@ Director of Infrastructure, Ee Durbin, to administer the election. Schedule ======== -There will be a two-week nomination period, followed by a two-week +There was be a two-week nomination period, followed by a two-week vote. -The nomination period will be: November 10, 2025 through `November 24, 2025 AoE +The nomination period was: November 10, 2025 through `November 24, 2025 AoE `_ [#note-aoe]_. -The voting period will be: November 28, 2025 through `December 12, 2025 AoE +The voting period was: November 28, 2025 through `December 12, 2025 AoE `_ [#note-aoe]_. @@ -46,7 +46,12 @@ is a core team member, they may nominate themselves. Nominees (in alphabetical order by first name): -- TBD +- `Barry Warsaw `_ +- `Donghee Na `_ +- `Gregory P. Smith `_ +- `Pablo Galindo Salgado `_ +- `Savannah Ostrowski `_ +- `Thomas Wouters `_ Withdrawn nominations: @@ -106,18 +111,22 @@ Election Description: ``Election for the Python steering council, as specified i Enable Start/End Times?: ``Check this box`` -Time Zone: ``Midway Island, Samoa`` +Time Zone: ``Baker Island`` -Start Date: ``11/28/2025, 01:00 AM`` +Start Date: ``11/28/2025, 12:00 AM`` -End Date: ``12/13/2025, 01:00 AM`` +End Date: ``12/13/2025, 12:00 AM`` Click "Save". +Select ``no limit`` under "Who can vote?" to allow users to return to their ballot from any device or network without a BetterVoting.com account. + Click "Extra Settings" Check "Randomize Candidate Order". +Check "Allow Voters To Edit Vote". + Ensure "Show Preliminary Results" is unchecked. Check "Confirm That Voter Read Instructions". @@ -147,13 +156,14 @@ Number of winners: ``5`` Which Voting Method: ``STAR Voting`` -Candidates (add each candidate): +Candidates (add each candidate, hyperlink to nomination statement using the 🔗 icon): -* TBD -* TBD -* TBD -* TBD -* TBD +- Barry Warsaw 🔗 https://discuss.python.org/t/steering-council-nomination-barry-warsaw-2026-term/104937 +- Donghee Na 🔗 https://discuss.python.org/t/steering-council-nomination-donghee-na-2026-term/104888 +- Gregory P. Smith 🔗 https://discuss.python.org/t/steering-council-nomination-gregory-p-smith-2026-term/105011 +- Pablo Galindo Salgado 🔗 https://discuss.python.org/t/steering-council-nomination-pablo-galindo-salgado-2026-term/104936 +- Savannah Ostrowski 🔗 https://discuss.python.org/t/steering-council-nomination-savannah-ostrowski-2026-term/104989 +- Thomas Wouters 🔗 https://discuss.python.org/t/steering-council-nomination-thomas-wouters-2026-term/104954 Now, use "Cast test ballot" section to preview the ballot and resolve any misconfigurations. @@ -165,25 +175,73 @@ Enter voter data using Email list from `Voter Roll`_ repository. Results ======= -Of NN eligible voters, MM cast ballots. +Of 106 eligible voters, 74 cast ballots. The five winners are: -* TBD -* TBD -* TBD -* TBD -* TBD +* Pablo Galindo Salgado +* Savannah Ostrowski +* Barry Warsaw +* Donghee Na +* Thomas Wouters No conflict of interest as defined in :pep:`13` were observed. -The full vote counts are as follows: +The full voting results are: + ++-----------------------+-------------+ +| Candidate | Total Stars | ++=======================+=============+ +| Pablo Galindo Salgado | 313 | ++-----------------------+-------------+ +| Savannah Ostrowski | 249 | ++-----------------------+-------------+ +| Barry Warsaw | 239 | ++-----------------------+-------------+ +| Donghee Na | 191 | ++-----------------------+-------------+ +| Thomas Wouters | 187 | ++-----------------------+-------------+ +| Gregory P. Smith | 173 | ++-----------------------+-------------+ + +Tabulation Steps +---------------- + +Winner 1 +^^^^^^^^ + +*Scoring Round*: Pablo Galindo Salgado and Savannah Ostrowski advance to runoff with 313 and 249 stars. + +*Automatic Runoff*: Pablo Galindo Salgado is preferred over Savannah Ostrowski, 36 to 19, with 19 voters showing equal support for both finalists. + +Winner 2 +^^^^^^^^ +*Scoring Round*: Savannah Ostrowski and Barry Warsaw advance to runoff with 249 and 239 stars. + +*Automatic Runoff*: Savannah Ostrowski is preferred over Barry Warsaw, 34 to 29, with 11 voters showing equal support for both finalists. + +Winner 3 +^^^^^^^^ + +*Scoring Round*: Barry Warsaw and Donghee Na advance to runoff with 239 and 191 stars. + +*Automatic Runoff*: Barry Warsaw is preferred over Donghee Na, 38 to 25, with 11 voters showing equal support for both finalists. + +Winner 4 +^^^^^^^^ + +*Scoring Round*: Donghee Na and Thomas Wouters advance to runoff with 191 and 187 stars. + +*Automatic Runoff*: Donghee Na is preferred over Thomas Wouters, 36 to 33, with 5 voters showing equal support for both finalists. + +Winner 5 +^^^^^^^^ + +*Scoring Round*: Thomas Wouters and Gregory P. Smith advance to runoff with 187 and 173 stars. + +*Automatic Runoff*: Thomas Wouters is preferred over Gregory P. Smith, 34 to 26, with 14 voters showing equal support for both finalists. -+-----------------------+----------------+ -| Candidate | Votes Received | -+=======================+================+ -| TBD | | -+-----------------------+----------------+ Complete Voter Roll =================== @@ -193,7 +251,113 @@ Active Python core developers .. code-block:: text - TBD + Adam Turner + Alex Gaynor + Alex Waygood + Alexander Belopolsky + Alyssa Coghlan + Ammar Askar + Andrew Svetlov + Antoine Pitrou + Armin Ronacher + Barney Gale + Barry Warsaw + Batuhan Taskaya + Bénédikt Tran + Benjamin Peterson + Berker Peksağ + Brandt Bucher + Brett Cannon + Brian Curtin + C.A.M. Gerlach + Carl Meyer + Carol Willing + CF Bolz-Tereick + Cheryl Sabella + Chris Withers + Dennis Sweeney + Diego Russo + Dino Viehland + Donghee Na + Emily Morehouse + Emma Smith + Éric Araujo + Eric Snow + Eric V. Smith + Erlend Egeberg Aasland + Ethan Furman + Ezio Melotti + Facundo Batista + Filipe Laíns + Giampaolo Rodolà + Gregory P. Smith + Guido van Rossum + Hugo van Kemenade + Hynek Schlawack + Inada Naoki + Irit Katriel + Ivan Levkivskyi + Jack Jansen + Jason R. Coombs + Jelle Zijlstra + Jeremy Hylton + Jeremy Kloth + Jesús Cea + Joannah Nanjekye + Julien Palard + Ken Jin + Kirill Podoprigora + Kumar Aditya + Kurt B. Kaiser + Kushal Das + Larry Hastings + Lisa Roach + Łukasz Langa + Lysandros Nikolaou + Marc-André Lemburg + Mariatta + Mark Hammond + Mark Shannon + Matt Page + Matthias Klose + Meador Inge + Michael Droettboom + Nathaniel J. Smith + Ned Batchelder + Ned Deily + Neil Schemenauer + Nikita Sobolev + Pablo Galindo + Paul Ganssle + Paul Moore + Peter Bierma + Petr Viktorin + Pradyun Gedam + R. David Murray + Raymond Hettinger + Ronald Oussoren + Russell Keith-Magee + Sam Gross + Sandro Tosi + Savannah Ostrowski + Senthil Kumaran + Serhiy Storchaka + Shantanu Jain + Stefan Behnel + Steve Dower + Terry Jan Reedy + Thomas Wouters + Tian Gao + Tim Golden + Tim Peters + Tomas Roun + Trent Nelson + Victor Stinner + Vinay Sajip + Xiang Zhang + Yury Selivanov + Zachary Ware + Copyright ========= diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 10404cc0b3b..00000000000 --- a/pytest.ini +++ /dev/null @@ -1,16 +0,0 @@ -[pytest] -# https://docs.pytest.org/en/7.3.x/reference/reference.html#command-line-flags -addopts = - -r a - --strict-config - --strict-markers - --import-mode=importlib - --cov check_peps --cov pep_sphinx_extensions - --cov-report html --cov-report xml -empty_parameter_set_mark = fail_at_collect -filterwarnings = - error -minversion = 6.0 -testpaths = pep_sphinx_extensions -xfail_strict = True -disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True diff --git a/release_management/.ruff.toml b/release_management/.ruff.toml new file mode 100644 index 00000000000..2aeb6ec8468 --- /dev/null +++ b/release_management/.ruff.toml @@ -0,0 +1,5 @@ +extend = "../.ruff.toml" # Inherit the project-wide settings + +[format] +preview = true +quote-style = "single" diff --git a/release_management/LICENCE.rst b/release_management/LICENCE.rst new file mode 100644 index 00000000000..d68de666acb --- /dev/null +++ b/release_management/LICENCE.rst @@ -0,0 +1,2 @@ +The files in this directory are placed in the public domain or under the +CC0-1.0-Universal license, whichever is more permissive. diff --git a/release_management/__init__.py b/release_management/__init__.py new file mode 100644 index 00000000000..9846219fdc0 --- /dev/null +++ b/release_management/__init__.py @@ -0,0 +1,81 @@ +from __future__ import annotations + +import sys +from dataclasses import dataclass +from pathlib import Path + +try: + import tomllib +except ImportError: + import tomli as tomllib + +TYPE_CHECKING = False +if TYPE_CHECKING: + import datetime as dt + from typing import Literal, TypeAlias + + ReleaseState: TypeAlias = Literal['actual', 'expected'] + ReleaseSchedules: TypeAlias = dict[tuple[str, ReleaseState], list['ReleaseInfo']] + VersionStatus: TypeAlias = Literal[ + 'feature', 'prerelease', 'bugfix', 'security', 'end-of-life' + ] + +RELEASE_DIR = Path(__file__).resolve().parent +ROOT_DIR = RELEASE_DIR.parent +PEP_ROOT = ROOT_DIR / 'peps' + +dc_kw = {'kw_only': True, 'slots': True} if sys.version_info[:2] >= (3, 10) else {} + + +@dataclass(frozen=True, **dc_kw) +class PythonReleases: + metadata: dict[str, VersionMetadata] + releases: dict[str, list[ReleaseInfo]] + + +@dataclass(frozen=True, **dc_kw) +class VersionMetadata: + """Metadata for a given interpreter version (MAJOR.MINOR).""" + + pep: int + status: VersionStatus + branch: str + release_manager: str + start_of_development: dt.date + feature_freeze: dt.date + first_release: dt.date + end_of_bugfix: dt.date # a.k.a. security mode or source-only releases + end_of_life: dt.date + + @classmethod + def from_toml(cls, data: dict[str, int | str | dt.date]): + return cls(**{k.replace('-', '_'): v for k, v in data.items()}) + + +@dataclass(frozen=True, **dc_kw) +class ReleaseInfo: + """Information about a release.""" + + stage: str + state: ReleaseState + date: dt.date + note: str = '' # optional note / comment, displayed in the schedule + + @property + def schedule_bullet(self): + """Return a formatted bullet point for the schedule list.""" + return f'- {self.stage}: {self.date:%A, %Y-%m-%d}' + + +def load_python_releases() -> PythonReleases: + with open(RELEASE_DIR / 'python-releases.toml', 'rb') as f: + python_releases = tomllib.load(f) + all_metadata = { + v: VersionMetadata.from_toml(metadata) + for v, metadata in python_releases['metadata'].items() + } + all_releases = { + v: [ReleaseInfo(**r) for r in releases] + for v, releases in python_releases['release'].items() + } + return PythonReleases(metadata=all_metadata, releases=all_releases) diff --git a/release_management/__main__.py b/release_management/__main__.py new file mode 100644 index 00000000000..458a00ef90d --- /dev/null +++ b/release_management/__main__.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +import argparse + +commands = ( + CMD_FULL_JSON := 'full-json', + CMD_UPDATE_PEPS := 'update-peps', + CMD_RELEASE_CYCLE := 'release-cycle', + CMD_CALENDAR := 'calendar', +) +parser = argparse.ArgumentParser(allow_abbrev=False) +parser.add_argument('COMMAND', choices=commands) + +args = parser.parse_args() +if args.COMMAND == CMD_UPDATE_PEPS: + from release_management.update_release_schedules import update_peps + + raise SystemExit(update_peps()) + +if args.COMMAND == CMD_FULL_JSON: + from release_management import ROOT_DIR + from release_management.serialize import create_release_json + + json_path = ROOT_DIR / 'python-releases.json' + json_path.write_text(create_release_json(), encoding='utf-8') + raise SystemExit(0) + +if args.COMMAND == CMD_RELEASE_CYCLE: + from release_management import ROOT_DIR + from release_management.serialize import create_release_cycle + + json_path = ROOT_DIR / 'release-cycle.json' + json_path.write_text(create_release_cycle(), encoding='utf-8') + raise SystemExit(0) + +if args.COMMAND == CMD_CALENDAR: + from release_management import ROOT_DIR + from release_management.serialize import create_release_schedule_calendar + + calendar_path = ROOT_DIR / 'release-schedule.ics' + calendar_path.write_text(create_release_schedule_calendar(), encoding='utf-8') + raise SystemExit(0) diff --git a/release_management/python-releases.toml b/release_management/python-releases.toml new file mode 100644 index 00000000000..4f2e25a5dbd --- /dev/null +++ b/release_management/python-releases.toml @@ -0,0 +1,3611 @@ +# This file is placed in the public domain or under the +# CC0-1.0-Universal licence, whichever is more permissive. +# +# This document contains the history of every release of the Python project, +# and specifically the CPython intepreter. The data in this file were initially +# compiled in 2025 by Adam Turner, with information primarily sourced from the +# release PEPs and supplemented by the 'releases' section of www.python.org. +# +# The release schedules for Python 3.8 onwards are created from data in this +# document. After editing this file, run the following command to regenerate +# the relevant PEPs: +# +# python -m release_management update-peps +# +# The PEP rendering system, via Sphinx, uses this document to regenerate the +# 'release-cycle' JSON file, found at https://peps.python.org/release-cycle.json, +# and a full JSON representation at https://peps.python.org/python-releases.json, +# This 'release-cycle' JSON file is intended for public consumption. The format +# of this TOML document is not guaranteed and may change without notice. + +# -- Python 1.6 -------------------------------------------------------------- + +# Drake wrote PEP 160, but Hylton served as release manager: +# https://mail.python.org/archives/list/python-dev@python.org/message/FCPLKMFDZUDOQGPAEKGKB2VQYTI4JT7Y/ +# https://jeremyhylton.blogspot.com/2006/05/contributing-to-python.html + +[metadata."1.6"] +pep = 160 +status = "end-of-life" +branch = "" # no branch or tag for 1.6 exists +release-manager = "Guido van Rossum & Jeremy Hylton" +start-of-development = 1999-06-09 +feature-freeze = 2000-08-04 +first-release = 2000-09-05 +end-of-bugfix = 2000-09-05 +end-of-life = 2000-09-05 + +# 1.6.0 alpha {1,2} are not in the PEP, but are found on python-dev at: +# https://mail.python.org/archives/list/python-dev@python.org/message/IOO74NZNEUAHNYJFVK7K3P7RCNMFBE5W/ +# https://mail.python.org/archives/list/python-dev@python.org/message/65J6CYRPPUNCUW523J3EO77RKAY3BS3F/ + +[[release."1.6"]] +stage = "1.6.0 alpha 1" +state = "actual" +date = 2000-03-31 + +[[release."1.6"]] +stage = "1.6.0 alpha 2" +state = "actual" +date = 2000-04-11 + +# PEP 160 records 3 August 2000, but the announcement and source archive date +# from the 4th. +# https://mail.python.org/archives/list/python-dev@python.org/message/IF3YORLE7PTOR2QLMHI6UACGHDHZCAVO/ +# https://legacy.python.org/download/releases/src/python-1.6b1.tar.gz + +[[release."1.6"]] +stage = "1.6.0 beta 1" +state = "actual" +date = 2000-08-04 + +[[release."1.6"]] +stage = "1.6.0 final" +state = "actual" +date = 2000-09-05 + +# 1.6.1 is not in the PEP, but is found on python-announce at: +# https://mail.python.org/archives/list/python-announce-list@python.org/message/NSMG65WRUF5QLJD54H63B57BJFAGI5Y3/ + +[[release."1.6"]] +stage = "1.6.1 final" +state = "actual" +date = 2001-02-25 + +# -- Python 2.0 -------------------------------------------------------------- + +# Python 2.0 feature freeze is listed separately in PEP 200, it isn't beta 1. + +[metadata."2.0"] +pep = 200 +status = "end-of-life" +branch = "2.0" +release-manager = "Guido van Rossum & Jeremy Hylton" +start-of-development = 2000-06-29 +feature-freeze = 2000-08-14 +first-release = 2000-10-16 +end-of-bugfix = 2001-06-22 +end-of-life = 2001-06-22 + +# PEP 200 records 5 September 2000, but the announcement was on the 6th (GMT): +# https://mail.python.org/archives/list/python-dev@python.org/message/3JDD34UGV2L7QP7S37S37RZXXMDOMMIX/ + +[[release."2.0"]] +stage = "2.0.0 beta 1" +state = "actual" +date = 2000-09-06 + +[[release."2.0"]] +stage = "2.0.0 beta 2" +state = "actual" +date = 2000-09-26 + +[[release."2.0"]] +stage = "2.0.0 candidate 1" +state = "actual" +date = 2000-10-09 + +[[release."2.0"]] +stage = "2.0.0 final" +state = "actual" +date = 2000-10-16 + +# 2.0.1 is not in the PEP, but is found on the website at: +# https://www.python.org/downloads/release/python-201/ +# https://www.python.org/ftp/python/2.0.1/ +# https://mail.python.org/archives/list/python-dev@python.org/message/23N7ECY4XTIE3CTTY6LK7K6QLAOD4GLK/ +# https://mail.python.org/archives/list/python-dev@python.org/message/CRIOLY7NPVJCOXWGS4JXQNTKQCUOL23N/ + +[[release."2.0"]] +stage = "2.0.1 candidate 1" +state = "actual" +date = 2001-06-14 + +[[release."2.0"]] +stage = "2.0.1 final" +state = "actual" +date = 2001-06-22 + +# -- Python 2.1 -------------------------------------------------------------- + +[metadata."2.1"] +pep = 226 +status = "end-of-life" +branch = "2.1" +release-manager = "Guido van Rossum & Jeremy Hylton" +start-of-development = 2000-10-16 +feature-freeze = 2001-03-02 +first-release = 2001-04-17 +end-of-bugfix = 2002-04-09 +end-of-life = 2002-04-09 + +[[release."2.1"]] +stage = "2.1.0 alpha 1" +state = "actual" +date = 2001-01-22 + +[[release."2.1"]] +stage = "2.1.0 alpha 2" +state = "actual" +date = 2001-02-02 + +[[release."2.1"]] +stage = "2.1.0 beta 1" +state = "actual" +date = 2001-03-02 + +[[release."2.1"]] +stage = "2.1.0 beta 2" +state = "actual" +date = 2001-03-23 + +[[release."2.1"]] +stage = "2.1.0 candidate 1" +state = "actual" +date = 2001-04-13 + +[[release."2.1"]] +stage = "2.1.0 candidate 2" +state = "actual" +date = 2001-04-15 + +[[release."2.1"]] +stage = "2.1.0 final" +state = "actual" +date = 2001-04-17 + +# 2.1.{1,2,3} are not in the PEP, but are found on the website at: +# https://www.python.org/downloads/release/python-213/ +# https://www.python.org/ftp/python/2.1.1/ +# https://www.python.org/ftp/python/2.1.2/ +# https://www.python.org/ftp/python/2.1.3/ + +[[release."2.1"]] +stage = "2.1.1 candidate 1" +state = "actual" +date = 2002-07-13 + +[[release."2.1"]] +stage = "2.1.1 final" +state = "actual" +date = 2002-07-20 + +[[release."2.1"]] +stage = "2.1.2 candidate 1" +state = "actual" +date = 2002-01-10 + +[[release."2.1"]] +stage = "2.1.2 final" +state = "actual" +date = 2002-01-15 + +[[release."2.1"]] +stage = "2.1.3 final" +state = "actual" +date = 2002-04-09 + +# -- Python 2.2 -------------------------------------------------------------- + +[metadata."2.2"] +pep = 251 +status = "end-of-life" +branch = "2.2" +release-manager = "Barry Warsaw" +start-of-development = 2001-04-18 +feature-freeze = 2001-10-19 +first-release = 2001-12-21 +end-of-bugfix = 2003-05-30 +end-of-life = 2003-05-30 + +[[release."2.2"]] +stage = "2.2.0 alpha 1" +state = "actual" +date = 2001-07-18 + +[[release."2.2"]] +stage = "2.2.0 alpha 2" +state = "actual" +date = 2001-08-22 + +[[release."2.2"]] +stage = "2.2.0 alpha 3" +state = "actual" +date = 2001-09-07 + +[[release."2.2"]] +stage = "2.2.0 alpha 4" +state = "actual" +date = 2001-09-28 + +[[release."2.2"]] +stage = "2.2.0 beta 1" +state = "actual" +date = 2001-10-19 + +[[release."2.2"]] +stage = "2.2.0 beta 2" +state = "actual" +date = 2001-11-14 + +[[release."2.2"]] +stage = "2.2.0 candidate 1" +state = "actual" +date = 2001-12-14 + +[[release."2.2"]] +stage = "2.2.0 final" +state = "actual" +date = 2001-12-21 + +# 2.2.{1,2,3} are not in the PEP, but are found on the website at: +# https://www.python.org/downloads/release/python-221/ +# https://www.python.org/downloads/release/python-222/ +# https://www.python.org/downloads/release/python-223/ +# https://www.python.org/ftp/python/2.2.1/ +# https://www.python.org/ftp/python/2.2.2/ +# https://www.python.org/ftp/python/2.2.3/ + +[[release."2.2"]] +stage = "2.2.1 candidate 1" +state = "actual" +date = 2002-03-18 + +[[release."2.2"]] +stage = "2.2.1 candidate 2" +state = "actual" +date = 2002-03-26 + +[[release."2.2"]] +stage = "2.2.1 final" +state = "actual" +date = 2002-04-10 + +[[release."2.2"]] +stage = "2.2.2 beta 1" +state = "actual" +date = 2002-10-07 + +[[release."2.2"]] +stage = "2.2.2 final" +state = "actual" +date = 2002-10-14 + +[[release."2.2"]] +stage = "2.2.3 candidate 1" +state = "actual" +date = 2003-05-22 + +[[release."2.2"]] +stage = "2.2.3 final" +state = "actual" +date = 2003-05-30 + +# -- Python 2.3 -------------------------------------------------------------- + +[metadata."2.3"] +pep = 283 +status = "end-of-life" +branch = "2.3" +release-manager = "Barry Warsaw, Jeremy Hylton, and Tim Peters" +start-of-development = 2001-12-21 +feature-freeze = 2003-04-25 +first-release = 2003-06-29 +end-of-bugfix = 2008-03-11 +end-of-life = 2008-03-11 + +[[release."2.3"]] +stage = "2.3.0 alpha 1" +state = "actual" +date = 2002-12-31 + +[[release."2.3"]] +stage = "2.3.0 alpha 2" +state = "actual" +date = 2003-02-19 + +[[release."2.3"]] +stage = "2.3.0 beta 1" +state = "actual" +date = 2003-04-25 + +[[release."2.3"]] +stage = "2.3.0 beta 2" +state = "actual" +date = 2003-06-29 + +[[release."2.3"]] +stage = "2.3.0 candidate 1" +state = "actual" +date = 2003-07-18 + +[[release."2.3"]] +stage = "2.3.0 candidate 2" +state = "actual" +date = 2003-07-24 + +[[release."2.3"]] +stage = "2.3.0 final" +state = "actual" +date = 2003-07-29 + +# 2.3.{1,2,3,4,5,6,7} are not in the PEP, but are found on the website at: +# https://www.python.org/downloads/release/python-231/ +# https://www.python.org/downloads/release/python-232/ +# https://www.python.org/downloads/release/python-233/ +# https://www.python.org/downloads/release/python-234/ +# https://www.python.org/downloads/release/python-235/ +# https://www.python.org/downloads/release/python-236/ +# https://www.python.org/downloads/release/python-237/ +# https://www.python.org/ftp/python/2.3.1/ +# https://www.python.org/ftp/python/2.3.2/ +# https://www.python.org/ftp/python/2.3.3/ +# https://www.python.org/ftp/python/2.3.4/ +# https://www.python.org/ftp/python/2.3.5/ +# https://www.python.org/ftp/python/2.3.6/ +# https://www.python.org/ftp/python/2.3.7/ + +[[release."2.3"]] +stage = "2.3.1 candidate 1" +state = "actual" +date = 2003-09-23 + +[[release."2.3"]] +stage = "2.3.1 final" +state = "actual" +date = 2003-09-23 + +[[release."2.3"]] +stage = "2.3.2 candidate 1" +state = "actual" +date = 2003-09-30 + +[[release."2.3"]] +stage = "2.3.2 final" +state = "actual" +date = 2003-10-03 + +[[release."2.3"]] +stage = "2.3.3 candidate 1" +state = "actual" +date = 2003-12-05 + +[[release."2.3"]] +stage = "2.3.3 final" +state = "actual" +date = 2003-12-19 + +[[release."2.3"]] +stage = "2.3.4 candidate 1" +state = "actual" +date = 2004-05-13 + +[[release."2.3"]] +stage = "2.3.4 final" +state = "actual" +date = 2004-05-27 + +[[release."2.3"]] +stage = "2.3.5 candidate 1" +state = "actual" +date = 2004-01-26 + +[[release."2.3"]] +stage = "2.3.5 final" +state = "actual" +date = 2004-02-08 + +[[release."2.3"]] +stage = "2.3.6 candidate 1" +state = "actual" +date = 2006-10-23 + +[[release."2.3"]] +stage = "2.3.6 final" +state = "actual" +date = 2006-11-01 + +[[release."2.3"]] +stage = "2.3.7 candidate 1" +state = "actual" +date = 2008-03-02 + +[[release."2.3"]] +stage = "2.3.7 final" +state = "actual" +date = 2008-03-11 + +# -- Python 2.4 -------------------------------------------------------------- + +[metadata."2.4"] +pep = 320 +status = "end-of-life" +branch = "2.4" +release-manager = "Anthony Baxter" +start-of-development = 2003-07-30 +feature-freeze = 2004-10-15 +first-release = 2004-11-30 +end-of-bugfix = 2008-12-19 +end-of-life = 2008-12-19 + +[[release."2.4"]] +stage = "2.4.0 alpha 1" +state = "actual" +date = 2004-07-09 + +[[release."2.4"]] +stage = "2.4.0 alpha 2" +state = "actual" +date = 2004-08-05 + +[[release."2.4"]] +stage = "2.4.0 alpha 3" +state = "actual" +date = 2004-09-03 + +[[release."2.4"]] +stage = "2.4.0 beta 1" +state = "actual" +date = 2004-10-15 + +[[release."2.4"]] +stage = "2.4.0 beta 2" +state = "actual" +date = 2004-11-03 + +[[release."2.4"]] +stage = "2.4.0 candidate 1" +state = "actual" +date = 2004-11-18 + +[[release."2.4"]] +stage = "2.4.0 final" +state = "actual" +date = 2004-11-30 + +# 2.4.{1,2,3,4,5,6} are not in the PEP, but are found on the website at: +# https://www.python.org/downloads/release/python-241/ +# https://www.python.org/downloads/release/python-242/ +# https://www.python.org/downloads/release/python-243/ +# https://www.python.org/downloads/release/python-244/ +# https://www.python.org/downloads/release/python-245/ +# https://www.python.org/downloads/release/python-246/ +# https://www.python.org/ftp/python/2.4.1/ +# https://www.python.org/ftp/python/2.4.2/ +# https://www.python.org/ftp/python/2.4.3/ +# https://www.python.org/ftp/python/2.4.4/ +# https://www.python.org/ftp/python/2.4.5/ +# https://www.python.org/ftp/python/2.4.6/ + +[[release."2.4"]] +stage = "2.4.1 candidate 1" +state = "actual" +date = 2005-03-10 + +[[release."2.4"]] +stage = "2.4.1 candidate 2" +state = "actual" +date = 2005-03-17 + +[[release."2.4"]] +stage = "2.4.1 final" +state = "actual" +date = 2005-03-30 + +[[release."2.4"]] +stage = "2.4.2 candidate 1" +state = "actual" +date = 2005-09-20 + +[[release."2.4"]] +stage = "2.4.2 final" +state = "actual" +date = 2005-09-27 + +[[release."2.4"]] +stage = "2.4.3 candidate 1" +state = "actual" +date = 2006-03-23 + +[[release."2.4"]] +stage = "2.4.3 final" +state = "actual" +date = 2006-04-15 + +[[release."2.4"]] +stage = "2.4.4 candidate 1" +state = "actual" +date = 2006-10-11 + +[[release."2.4"]] +stage = "2.4.4 final" +state = "actual" +date = 2006-10-18 + +[[release."2.4"]] +stage = "2.4.5 candidate 1" +state = "actual" +date = 2008-03-02 + +[[release."2.4"]] +stage = "2.4.5 final" +state = "actual" +date = 2008-03-11 + +[[release."2.4"]] +stage = "2.4.6 candidate 1" +state = "actual" +date = 2008-12-13 + +[[release."2.4"]] +stage = "2.4.6 final" +state = "actual" +date = 2008-12-19 + +# -- Python 2.5 -------------------------------------------------------------- + +[metadata."2.5"] +pep = 356 +status = "end-of-life" +branch = "2.5" +release-manager = "Anthony Baxter" +start-of-development = 2004-11-30 +feature-freeze = 2006-06-20 +first-release = 2006-09-19 +end-of-bugfix = 2011-05-26 +end-of-life = 2011-05-26 + +[[release."2.5"]] +stage = "2.5.0 alpha 1" +state = "actual" +date = 2006-04-05 + +[[release."2.5"]] +stage = "2.5.0 alpha 2" +state = "actual" +date = 2006-04-27 + +[[release."2.5"]] +stage = "2.5.0 beta 1" +state = "actual" +date = 2006-06-20 + +[[release."2.5"]] +stage = "2.5.0 beta 2" +state = "actual" +date = 2006-07-11 + +[[release."2.5"]] +stage = "2.5.0 beta 3" +state = "actual" +date = 2006-08-03 + +[[release."2.5"]] +stage = "2.5.0 candidate 1" +state = "actual" +date = 2006-08-17 + +[[release."2.5"]] +stage = "2.5.0 candidate 2" +state = "actual" +date = 2006-09-12 + +[[release."2.5"]] +stage = "2.5.0 final" +state = "actual" +date = 2006-09-19 + +# 2.5.{1,2,3,4,5,6} are not in the PEP, but are found on the website at: +# https://www.python.org/downloads/release/python-251/ +# https://www.python.org/downloads/release/python-252/ +# https://www.python.org/downloads/release/python-253/ +# https://www.python.org/downloads/release/python-254/ +# https://www.python.org/downloads/release/python-255/ +# https://www.python.org/downloads/release/python-256/ +# https://www.python.org/ftp/python/2.5.1/ +# https://www.python.org/ftp/python/2.5.2/ +# https://www.python.org/ftp/python/2.5.3/ +# https://www.python.org/ftp/python/2.5.4/ +# https://www.python.org/ftp/python/2.5.5/ +# https://www.python.org/ftp/python/2.5.6/ + +[[release."2.5"]] +stage = "2.5.1 candidate 1" +state = "actual" +date = 2007-04-10 + +[[release."2.5"]] +stage = "2.5.1 final" +state = "actual" +date = 2007-04-19 + +[[release."2.5"]] +stage = "2.5.2 candidate 1" +state = "actual" +date = 2008-02-14 + +[[release."2.5"]] +stage = "2.5.2 final" +state = "actual" +date = 2008-02-21 + +[[release."2.5"]] +stage = "2.5.3 candidate 1" +state = "actual" +date = 2008-12-13 + +[[release."2.5"]] +stage = "2.5.3 final" +state = "actual" +date = 2008-12-19 + +[[release."2.5"]] +stage = "2.5.4 final" +state = "actual" +date = 2008-12-23 + +[[release."2.5"]] +stage = "2.5.5 candidate 1" +state = "actual" +date = 2010-01-14 + +[[release."2.5"]] +stage = "2.5.5 candidate 2" +state = "actual" +date = 2010-01-24 + +[[release."2.5"]] +stage = "2.5.5 final" +state = "actual" +date = 2010-01-31 + +[[release."2.5"]] +stage = "2.5.6 candidate 1" +state = "actual" +date = 2011-04-17 + +[[release."2.5"]] +stage = "2.5.6 final" +state = "actual" +date = 2011-05-26 + +# -- Python 2.6 -------------------------------------------------------------- + +[metadata."2.6"] +pep = 361 +status = "end-of-life" +branch = "2.6" +release-manager = "Barry Warsaw" +start-of-development = 2006-08-18 +feature-freeze = 2008-06-18 +first-release = 2008-10-01 +end-of-bugfix = 2010-08-24 +end-of-life = 2013-10-29 + +[[release."2.6"]] +stage = "2.6.0 alpha 1" +state = "actual" +date = 2008-02-29 + +[[release."2.6"]] +stage = "2.6.0 alpha 2" +state = "actual" +date = 2008-04-02 + +[[release."2.6"]] +stage = "2.6.0 alpha 3" +state = "actual" +date = 2008-05-08 + +[[release."2.6"]] +stage = "2.6.0 beta 1" +state = "actual" +date = 2008-06-18 + +[[release."2.6"]] +stage = "2.6.0 beta 2" +state = "actual" +date = 2008-07-17 + +[[release."2.6"]] +stage = "2.6.0 beta 3" +state = "actual" +date = 2008-08-20 + +[[release."2.6"]] +stage = "2.6.0 candidate 1" +state = "actual" +date = 2008-09-12 + +[[release."2.6"]] +stage = "2.6.0 candidate 2" +state = "actual" +date = 2008-09-17 + +[[release."2.6"]] +stage = "2.6.0 final" +state = "actual" +date = 2008-10-01 + +[[release."2.6"]] +stage = "2.6.1 final" +state = "actual" +date = 2008-12-04 + +[[release."2.6"]] +stage = "2.6.2 candidate 1" +state = "actual" +date = 2009-04-08 + +[[release."2.6"]] +stage = "2.6.2 final" +state = "actual" +date = 2009-04-14 + +[[release."2.6"]] +stage = "2.6.3 candidate 1" +state = "actual" +date = 2009-09-29 + +[[release."2.6"]] +stage = "2.6.3 final" +state = "actual" +date = 2009-10-02 + +[[release."2.6"]] +stage = "2.6.4 candidate 1" +state = "actual" +date = 2009-10-06 + +[[release."2.6"]] +stage = "2.6.4 candidate 2" +state = "actual" +date = 2009-10-18 + +[[release."2.6"]] +stage = "2.6.4 final" +state = "actual" +date = 2009-10-25 + +[[release."2.6"]] +stage = "2.6.5 candidate 1" +state = "actual" +date = 2010-03-01 + +[[release."2.6"]] +stage = "2.6.5 candidate 2" +state = "actual" +date = 2010-03-10 + +[[release."2.6"]] +stage = "2.6.5 final" +state = "actual" +date = 2010-03-19 + +[[release."2.6"]] +stage = "2.6.6 candidate 1" +state = "actual" +date = 2010-08-03 + +[[release."2.6"]] +stage = "2.6.6 candidate 2" +state = "actual" +date = 2010-08-16 + +[[release."2.6"]] +stage = "2.6.6 final" +state = "actual" +date = 2010-08-24 + +[[release."2.6"]] +stage = "2.6.7 candidate 1" +state = "actual" +date = 2011-05-06 + +[[release."2.6"]] +stage = "2.6.7 candidate 2" +state = "actual" +date = 2011-05-21 + +[[release."2.6"]] +stage = "2.6.7 final" +state = "actual" +date = 2011-06-03 + +[[release."2.6"]] +stage = "2.6.8 candidate 1" +state = "actual" +date = 2012-02-23 + +[[release."2.6"]] +stage = "2.6.8 candidate 2" +state = "actual" +date = 2012-03-17 + +[[release."2.6"]] +stage = "2.6.8 final" +state = "actual" +date = 2012-04-10 + +[[release."2.6"]] +stage = "2.6.9 candidate 1" +state = "actual" +date = 2013-10-01 + +[[release."2.6"]] +stage = "2.6.9 final" +state = "actual" +date = 2013-10-29 + +# -- Python 2.7 -------------------------------------------------------------- + +# The end-of-life date is before the final release (2.7.18), per PEP 373: +# > Support officially stopped January 1, 2020, and 2.7.18 code freeze +# > occurred on January 1, 2020, but the final release occurred +# > after that date. + +[metadata."2.7"] +pep = 373 +status = "end-of-life" +branch = "2.7" +release-manager = "Benjamin Peterson" +start-of-development = 2008-10-02 +feature-freeze = 2010-04-03 +first-release = 2010-07-03 +end-of-bugfix = 2020-01-01 +end-of-life = 2020-01-01 + +[[release."2.7"]] +stage = "2.7.0 alpha 1" +state = "actual" +date = 2009-12-05 + +[[release."2.7"]] +stage = "2.7.0 alpha 2" +state = "actual" +date = 2010-01-09 + +[[release."2.7"]] +stage = "2.7.0 alpha 3" +state = "actual" +date = 2010-02-06 + +[[release."2.7"]] +stage = "2.7.0 alpha 4" +state = "actual" +date = 2010-03-06 + +[[release."2.7"]] +stage = "2.7.0 beta 1" +state = "actual" +date = 2010-04-03 + +[[release."2.7"]] +stage = "2.7.0 beta 2" +state = "actual" +date = 2010-05-08 + +[[release."2.7"]] +stage = "2.7.0 candidate 1" +state = "actual" +date = 2010-06-05 + +[[release."2.7"]] +stage = "2.7.0 candidate 2" +state = "actual" +date = 2010-06-19 + +[[release."2.7"]] +stage = "2.7.0 final" +state = "actual" +date = 2010-07-03 + +[[release."2.7"]] +stage = "2.7.1 candidate 1" +state = "actual" +date = 2010-11-13 + +[[release."2.7"]] +stage = "2.7.1 final" +state = "actual" +date = 2010-11-27 + +[[release."2.7"]] +stage = "2.7.2 candidate 1" +state = "actual" +date = 2011-05-29 + +[[release."2.7"]] +stage = "2.7.2 final" +state = "actual" +date = 2011-07-21 + +[[release."2.7"]] +stage = "2.7.3 candidate 1" +state = "actual" +date = 2012-02-23 + +[[release."2.7"]] +stage = "2.7.3 candidate 2" +state = "actual" +date = 2012-03-15 + +[[release."2.7"]] +stage = "2.7.3 final" +state = "actual" +date = 2012-03-09 + +[[release."2.7"]] +stage = "2.7.4 candidate 1" +state = "actual" +date = 2013-03-23 + +[[release."2.7"]] +stage = "2.7.4 final" +state = "actual" +date = 2013-04-06 + +[[release."2.7"]] +stage = "2.7.5 final" +state = "actual" +date = 2013-05-12 + +[[release."2.7"]] +stage = "2.7.6 candidate 1" +state = "actual" +date = 2013-10-26 + +[[release."2.7"]] +stage = "2.7.6 final" +state = "actual" +date = 2013-11-10 + +[[release."2.7"]] +stage = "2.7.7 candidate 1" +state = "actual" +date = 2014-05-17 + +[[release."2.7"]] +stage = "2.7.7 final" +state = "actual" +date = 2014-05-31 + +[[release."2.7"]] +stage = "2.7.8 final" +state = "actual" +date = 2014-06-30 + +[[release."2.7"]] +stage = "2.7.9 candidate 1" +state = "actual" +date = 2014-11-26 + +[[release."2.7"]] +stage = "2.7.9 final" +state = "actual" +date = 2014-12-10 + +[[release."2.7"]] +stage = "2.7.10 candidate 1" +state = "actual" +date = 2015-05-09 + +[[release."2.7"]] +stage = "2.7.10 final" +state = "actual" +date = 2015-05-23 + +[[release."2.7"]] +stage = "2.7.11 candidate 1" +state = "actual" +date = 2015-11-21 + +[[release."2.7"]] +stage = "2.7.11 final" +state = "actual" +date = 2015-12-05 + +[[release."2.7"]] +stage = "2.7.12 final" +state = "actual" +date = 2016-06-25 + +[[release."2.7"]] +stage = "2.7.13 candidate 1" +state = "actual" +date = 2016-12-03 + +[[release."2.7"]] +stage = "2.7.13 final" +state = "actual" +date = 2016-12-17 + +[[release."2.7"]] +stage = "2.7.14 candidate 1" +state = "actual" +date = 2017-08-26 + +[[release."2.7"]] +stage = "2.7.14 final" +state = "actual" +date = 2017-09-16 + +[[release."2.7"]] +stage = "2.7.15 candidate 1" +state = "actual" +date = 2018-04-14 + +[[release."2.7"]] +stage = "2.7.15 final" +state = "actual" +date = 2018-05-01 + +[[release."2.7"]] +stage = "2.7.16 candidate 1" +state = "actual" +date = 2019-02-16 + +[[release."2.7"]] +stage = "2.7.16 final" +state = "actual" +date = 2019-03-02 + +[[release."2.7"]] +stage = "2.7.17 candidate 1" +state = "actual" +date = 2019-10-05 + +[[release."2.7"]] +stage = "2.7.17 final" +state = "actual" +date = 2019-10-19 + +[[release."2.7"]] +stage = "2.7.18 candidate 1" +state = "actual" +date = 2020-04-04 + +[[release."2.7"]] +stage = "2.7.18 final" +state = "actual" +date = 2020-04-20 + +# -- Python 3.0 -------------------------------------------------------------- + +# PEP 361 does not list an end-of-life date. We use the statement from the +# website that 'Python 3.0 is end-of-lifed with the release of Python 3.1.'. +# The latter was first released on 27 June 2009. + +[metadata."3.0"] +pep = 361 +status = "end-of-life" +branch = "3.0" +release-manager = "Barry Warsaw" +start-of-development = 2006-03-14 +feature-freeze = 2008-06-18 +first-release = 2008-12-03 +end-of-bugfix = 2009-06-27 +end-of-life = 2009-06-27 + +[[release."3.0"]] +stage = "3.0.0 alpha 1" +state = "actual" +date = 2007-08-31 + +[[release."3.0"]] +stage = "3.0.0 alpha 2" +state = "actual" +date = 2007-12-06 + +[[release."3.0"]] +stage = "3.0.0 alpha 3" +state = "actual" +date = 2008-02-29 + +[[release."3.0"]] +stage = "3.0.0 alpha 4" +state = "actual" +date = 2008-04-02 + +[[release."3.0"]] +stage = "3.0.0 alpha 5" +state = "actual" +date = 2008-05-08 + +[[release."3.0"]] +stage = "3.0.0 beta 1" +state = "actual" +date = 2008-06-18 + +[[release."3.0"]] +stage = "3.0.0 beta 2" +state = "actual" +date = 2008-07-17 + +[[release."3.0"]] +stage = "3.0.0 beta 3" +state = "actual" +date = 2008-08-20 + +[[release."3.0"]] +stage = "3.0.0 candidate 1" +state = "actual" +date = 2008-09-17 + +[[release."3.0"]] +stage = "3.0.0 candidate 2" +state = "actual" +date = 2008-11-06 + +[[release."3.0"]] +stage = "3.0.0 candidate 3" +state = "actual" +date = 2008-11-21 + +[[release."3.0"]] +stage = "3.0.0 final" +state = "actual" +date = 2008-12-03 + +# 3.0.1 is not in the PEP, but is found on the website at: +# https://www.python.org/downloads/release/python-301/ +# https://www.python.org/ftp/python/3.0.1/ + +[[release."3.0"]] +stage = "3.0.1 final" +state = "actual" +date = 2009-02-13 + +# -- Python 3.1 -------------------------------------------------------------- + +[metadata."3.1"] +pep = 375 +status = "end-of-life" +branch = "3.1" +release-manager = "Benjamin Peterson" +start-of-development = 2008-12-03 +feature-freeze = 2009-05-06 +first-release = 2009-06-27 +end-of-bugfix = 2011-06-11 +end-of-life = 2012-04-09 + +[[release."3.1"]] +stage = "3.1.0 alpha 1" +state = "actual" +date = 2009-03-07 + +[[release."3.1"]] +stage = "3.1.0 alpha 2" +state = "actual" +date = 2009-04-04 + +[[release."3.1"]] +stage = "3.1.0 beta 1" +state = "actual" +date = 2009-05-06 + +[[release."3.1"]] +stage = "3.1.0 candidate 1" +state = "actual" +date = 2009-05-30 + +[[release."3.1"]] +stage = "3.1.0 candidate 2" +state = "actual" +date = 2009-06-13 + +[[release."3.1"]] +stage = "3.1.0 final" +state = "actual" +date = 2009-06-27 + +[[release."3.1"]] +stage = "3.1.1 candidate 1" +state = "actual" +date = 2009-08-13 + +[[release."3.1"]] +stage = "3.1.1 final" +state = "actual" +date = 2009-08-16 + +[[release."3.1"]] +stage = "3.1.2 candidate 1" +state = "actual" +date = 2010-03-06 + +[[release."3.1"]] +stage = "3.1.2 final" +state = "actual" +date = 2010-03-20 + +[[release."3.1"]] +stage = "3.1.3 candidate 1" +state = "actual" +date = 2010-11-13 + +[[release."3.1"]] +stage = "3.1.3 final" +state = "actual" +date = 2010-11-27 + +[[release."3.1"]] +stage = "3.1.4 candidate 1" +state = "actual" +date = 2011-05-29 + +[[release."3.1"]] +stage = "3.1.4 final" +state = "actual" +date = 2011-06-11 + +[[release."3.1"]] +stage = "3.1.5 candidate 1" +state = "actual" +date = 2012-02-23 + +[[release."3.1"]] +stage = "3.1.5 candidate 2" +state = "actual" +date = 2012-03-15 + +# PEP 375 states the date as 2012-04-06, but the website lists 2012-04-09: +# https://www.python.org/downloads/release/python-315/ + +[[release."3.1"]] +stage = "3.1.5 final" +state = "actual" +date = 2012-04-09 + +# -- Python 3.2 -------------------------------------------------------------- + +[metadata."3.2"] +pep = 392 +status = "end-of-life" +branch = "3.2" +release-manager = "Georg Brandl" +start-of-development = 2009-06-27 +feature-freeze = 2010-12-06 +first-release = 2011-02-20 +end-of-bugfix = 2013-05-13 +end-of-life = 2016-02-20 + +[[release."3.2"]] +stage = "3.2.0 alpha 1" +state = "actual" +date = 2010-08-01 + +[[release."3.2"]] +stage = "3.2.0 alpha 2" +state = "actual" +date = 2010-09-06 + +[[release."3.2"]] +stage = "3.2.0 alpha 3" +state = "actual" +date = 2010-10-12 + +[[release."3.2"]] +stage = "3.2.0 alpha 4" +state = "actual" +date = 2010-11-16 + +[[release."3.2"]] +stage = "3.2.0 beta 1" +state = "actual" +date = 2010-12-06 + +[[release."3.2"]] +stage = "3.2.0 beta 2" +state = "actual" +date = 2010-12-20 + +[[release."3.2"]] +stage = "3.2.0 candidate 1" +state = "actual" +date = 2011-01-16 + +[[release."3.2"]] +stage = "3.2.0 candidate 2" +state = "actual" +date = 2011-01-31 + +[[release."3.2"]] +stage = "3.2.0 candidate 3" +state = "actual" +date = 2011-02-14 + +[[release."3.2"]] +stage = "3.2.0 final" +state = "actual" +date = 2011-02-20 + +[[release."3.2"]] +stage = "3.2.1 beta 1" +state = "actual" +date = 2011-05-08 + +[[release."3.2"]] +stage = "3.2.1 candidate 1" +state = "actual" +date = 2011-05-17 + +[[release."3.2"]] +stage = "3.2.1 candidate 2" +state = "actual" +date = 2011-07-03 + +[[release."3.2"]] +stage = "3.2.1 final" +state = "actual" +date = 2011-07-11 + +[[release."3.2"]] +stage = "3.2.2 candidate 1" +state = "actual" +date = 2011-08-14 + +[[release."3.2"]] +stage = "3.2.2 final" +state = "actual" +date = 2011-09-04 + +[[release."3.2"]] +stage = "3.2.3 candidate 1" +state = "actual" +date = 2012-02-25 + +[[release."3.2"]] +stage = "3.2.3 candidate 2" +state = "actual" +date = 2012-03-18 + +[[release."3.2"]] +stage = "3.2.3 final" +state = "actual" +date = 2012-04-11 + +[[release."3.2"]] +stage = "3.2.4 candidate 1" +state = "actual" +date = 2013-03-23 + +[[release."3.2"]] +stage = "3.2.4 final" +state = "actual" +date = 2013-04-06 + +[[release."3.2"]] +stage = "3.2.5 final" +state = "actual" +date = 2013-05-13 + +[[release."3.2"]] +stage = "3.2.6 candidate 1" +state = "actual" +date = 2014-10-04 + +[[release."3.2"]] +stage = "3.2.6 final" +state = "actual" +date = 2014-10-11 + +# -- Python 3.3 -------------------------------------------------------------- + +[metadata."3.3"] +pep = 398 +status = "end-of-life" +branch = "3.3" +release-manager = "Georg Brandl & Ned Deily (3.3.7)" +start-of-development = 2011-02-20 +feature-freeze = 2012-06-27 +first-release = 2012-09-29 +end-of-bugfix = 2014-03-08 +end-of-life = 2017-09-29 + +[[release."3.3"]] +stage = "3.3.0 alpha 1" +state = "actual" +date = 2012-03-05 + +[[release."3.3"]] +stage = "3.3.0 alpha 2" +state = "actual" +date = 2012-04-02 + +[[release."3.3"]] +stage = "3.3.0 alpha 3" +state = "actual" +date = 2012-05-01 + +[[release."3.3"]] +stage = "3.3.0 alpha 4" +state = "actual" +date = 2012-05-31 + +[[release."3.3"]] +stage = "3.3.0 beta 1" +state = "actual" +date = 2012-06-27 + +[[release."3.3"]] +stage = "3.3.0 beta 2" +state = "actual" +date = 2012-08-12 + +[[release."3.3"]] +stage = "3.3.0 candidate 1" +state = "actual" +date = 2012-08-24 + +[[release."3.3"]] +stage = "3.3.0 candidate 2" +state = "actual" +date = 2012-09-09 + +[[release."3.3"]] +stage = "3.3.0 candidate 3" +state = "actual" +date = 2012-09-24 + +[[release."3.3"]] +stage = "3.3.0 final" +state = "actual" +date = 2012-09-29 + +[[release."3.3"]] +stage = "3.3.1 candidate 1" +state = "actual" +date = 2013-03-23 + +[[release."3.3"]] +stage = "3.3.1 final" +state = "actual" +date = 2013-04-06 + +[[release."3.3"]] +stage = "3.3.2 final" +state = "actual" +date = 2013-05-13 + +[[release."3.3"]] +stage = "3.3.3 candidate 1" +state = "actual" +date = 2013-10-27 + +[[release."3.3"]] +stage = "3.3.3 candidate 2" +state = "actual" +date = 2013-11-09 + +[[release."3.3"]] +stage = "3.3.3 final" +state = "actual" +date = 2013-11-16 + +[[release."3.3"]] +stage = "3.3.4 candidate 1" +state = "actual" +date = 2014-01-26 + +[[release."3.3"]] +stage = "3.3.4 final" +state = "actual" +date = 2014-02-09 + +[[release."3.3"]] +stage = "3.3.5 candidate 1" +state = "actual" +date = 2014-02-22 + +[[release."3.3"]] +stage = "3.3.5 candidate 2" +state = "actual" +date = 2014-03-01 + +[[release."3.3"]] +stage = "3.3.5 final" +state = "actual" +date = 2014-03-08 + +[[release."3.3"]] +stage = "3.3.6 candidate 1" +state = "actual" +date = 2014-10-04 + +[[release."3.3"]] +stage = "3.3.6 final" +state = "actual" +date = 2014-10-11 + +[[release."3.3"]] +stage = "3.3.7 candidate 1" +state = "actual" +date = 2017-09-06 + +[[release."3.3"]] +stage = "3.3.7 final" +state = "actual" +date = 2017-09-19 + +# -- Python 3.4 -------------------------------------------------------------- + +[metadata."3.4"] +pep = 429 +status = "end-of-life" +branch = "3.4" +release-manager = "Larry Hastings" +start-of-development = 2012-09-29 +feature-freeze = 2013-11-24 +first-release = 2014-03-16 +end-of-bugfix = 2017-08-09 +end-of-life = 2019-03-18 + +[[release."3.4"]] +stage = "3.4.0 alpha 1" +state = "actual" +date = 2013-08-03 + +[[release."3.4"]] +stage = "3.4.0 alpha 2" +state = "actual" +date = 2013-09-09 + +[[release."3.4"]] +stage = "3.4.0 alpha 3" +state = "actual" +date = 2013-09-29 + +[[release."3.4"]] +stage = "3.4.0 alpha 4" +state = "actual" +date = 2013-10-20 + +[[release."3.4"]] +stage = "3.4.0 beta 1" +state = "actual" +date = 2013-11-24 + +[[release."3.4"]] +stage = "3.4.0 beta 2" +state = "actual" +date = 2014-01-05 + +[[release."3.4"]] +stage = "3.4.0 beta 3" +state = "actual" +date = 2014-01-26 + +[[release."3.4"]] +stage = "3.4.0 candidate 1" +state = "actual" +date = 2014-02-10 + +[[release."3.4"]] +stage = "3.4.0 candidate 2" +state = "actual" +date = 2014-02-23 + +[[release."3.4"]] +stage = "3.4.0 candidate 3" +state = "actual" +date = 2014-03-09 + +[[release."3.4"]] +stage = "3.4.0 final" +state = "actual" +date = 2014-03-16 + +[[release."3.4"]] +stage = "3.4.1 candidate 1" +state = "actual" +date = 2014-05-05 + +[[release."3.4"]] +stage = "3.4.1 final" +state = "actual" +date = 2014-05-18 + +[[release."3.4"]] +stage = "3.4.2 candidate 1" +state = "actual" +date = 2014-09-22 + +[[release."3.4"]] +stage = "3.4.2 final" +state = "actual" +date = 2014-10-06 + +[[release."3.4"]] +stage = "3.4.3 candidate 1" +state = "actual" +date = 2015-02-08 + +[[release."3.4"]] +stage = "3.4.3 final" +state = "actual" +date = 2015-02-25 + +[[release."3.4"]] +stage = "3.4.4 candidate 1" +state = "actual" +date = 2015-12-06 + +[[release."3.4"]] +stage = "3.4.4 final" +state = "actual" +date = 2015-12-20 + +[[release."3.4"]] +stage = "3.4.5 candidate 1" +state = "actual" +date = 2016-06-12 + +[[release."3.4"]] +stage = "3.4.5 final" +state = "actual" +date = 2016-06-26 + +[[release."3.4"]] +stage = "3.4.6 candidate 1" +state = "actual" +date = 2017-01-02 + +[[release."3.4"]] +stage = "3.4.6 final" +state = "actual" +date = 2017-01-17 + +[[release."3.4"]] +stage = "3.4.7 candidate 1" +state = "actual" +date = 2017-07-25 + +[[release."3.4"]] +stage = "3.4.7 final" +state = "actual" +date = 2017-08-09 + +[[release."3.4"]] +stage = "3.4.8 candidate 1" +state = "actual" +date = 2018-01-23 + +[[release."3.4"]] +stage = "3.4.8 final" +state = "actual" +date = 2018-02-04 + +[[release."3.4"]] +stage = "3.4.9 candidate 1" +state = "actual" +date = 2018-07-19 + +[[release."3.4"]] +stage = "3.4.9 final" +state = "actual" +date = 2018-08-02 + +[[release."3.4"]] +stage = "3.4.10 candidate 1" +state = "actual" +date = 2019-03-04 + +[[release."3.4"]] +stage = "3.4.10 final" +state = "actual" +date = 2019-03-18 + +# -- Python 3.5 -------------------------------------------------------------- + +[metadata."3.5"] +pep = 478 +status = "end-of-life" +branch = "3.5" +release-manager = "Larry Hastings" +start-of-development = 2014-03-17 +feature-freeze = 2015-05-24 +first-release = 2015-09-13 +end-of-bugfix = 2017-08-08 +end-of-life = 2020-09-30 + +[[release."3.5"]] +stage = "3.5.0 alpha 1" +state = "actual" +date = 2015-02-08 + +[[release."3.5"]] +stage = "3.5.0 alpha 2" +state = "actual" +date = 2015-03-09 + +[[release."3.5"]] +stage = "3.5.0 alpha 3" +state = "actual" +date = 2015-03-29 + +[[release."3.5"]] +stage = "3.5.0 alpha 4" +state = "actual" +date = 2015-04-19 + +[[release."3.5"]] +stage = "3.5.0 beta 1" +state = "actual" +date = 2015-05-24 + +[[release."3.5"]] +stage = "3.5.0 beta 2" +state = "actual" +date = 2015-05-31 + +[[release."3.5"]] +stage = "3.5.0 beta 3" +state = "actual" +date = 2015-07-05 + +[[release."3.5"]] +stage = "3.5.0 beta 4" +state = "actual" +date = 2015-07-26 + +[[release."3.5"]] +stage = "3.5.0 candidate 1" +state = "actual" +date = 2015-08-10 + +[[release."3.5"]] +stage = "3.5.0 candidate 2" +state = "actual" +date = 2015-08-25 + +[[release."3.5"]] +stage = "3.5.0 candidate 3" +state = "actual" +date = 2015-09-07 + +[[release."3.5"]] +stage = "3.5.0 final" +state = "actual" +date = 2015-09-13 + +[[release."3.5"]] +stage = "3.5.1 candidate 1" +state = "actual" +date = 2015-11-22 + +[[release."3.5"]] +stage = "3.5.1 final" +state = "actual" +date = 2015-12-06 + +[[release."3.5"]] +stage = "3.5.2 candidate 1" +state = "actual" +date = 2016-06-12 + +[[release."3.5"]] +stage = "3.5.2 final" +state = "actual" +date = 2016-06-26 + +[[release."3.5"]] +stage = "3.5.3 candidate 1" +state = "actual" +date = 2017-01-02 + +[[release."3.5"]] +stage = "3.5.3 final" +state = "actual" +date = 2017-01-17 + +[[release."3.5"]] +stage = "3.5.4 candidate 1" +state = "actual" +date = 2017-07-25 + +[[release."3.5"]] +stage = "3.5.4 final" +state = "actual" +date = 2017-08-08 + +[[release."3.5"]] +stage = "3.5.5 candidate 1" +state = "actual" +date = 2018-01-23 + +[[release."3.5"]] +stage = "3.5.5 final" +state = "actual" +date = 2018-02-04 + +[[release."3.5"]] +stage = "3.5.6 candidate 1" +state = "actual" +date = 2018-07-19 + +[[release."3.5"]] +stage = "3.5.6 final" +state = "actual" +date = 2018-08-02 + +[[release."3.5"]] +stage = "3.5.7 candidate 1" +state = "actual" +date = 2019-03-04 + +[[release."3.5"]] +stage = "3.5.7 final" +state = "actual" +date = 2019-03-18 + +[[release."3.5"]] +stage = "3.5.8 candidate 1" +state = "actual" +date = 2019-09-09 + +[[release."3.5"]] +stage = "3.5.8 candidate 2" +state = "actual" +date = 2019-10-12 + +[[release."3.5"]] +stage = "3.5.8 final" +state = "actual" +date = 2019-10-29 + +[[release."3.5"]] +stage = "3.5.9 final" +state = "actual" +date = 2019-11-01 + +[[release."3.5"]] +stage = "3.5.10 candidate 1" +state = "actual" +date = 2020-08-21 + +[[release."3.5"]] +stage = "3.5.10 final" +state = "actual" +date = 2020-09-05 + +# -- Python 3.6 -------------------------------------------------------------- + +[metadata."3.6"] +pep = 494 +status = "end-of-life" +branch = "3.6" +release-manager = "Ned Deily" +start-of-development = 2015-05-24 +feature-freeze = 2016-09-12 +first-release = 2016-12-23 +end-of-bugfix = 2018-12-24 +end-of-life = 2021-12-23 + +[[release."3.6"]] +stage = "3.6.0 alpha 1" +state = "actual" +date = 2016-05-17 + +[[release."3.6"]] +stage = "3.6.0 alpha 2" +state = "actual" +date = 2016-06-13 + +[[release."3.6"]] +stage = "3.6.0 alpha 3" +state = "actual" +date = 2016-07-11 + +[[release."3.6"]] +stage = "3.6.0 alpha 4" +state = "actual" +date = 2016-08-15 + +[[release."3.6"]] +stage = "3.6.0 beta 1" +state = "actual" +date = 2016-09-12 + +[[release."3.6"]] +stage = "3.6.0 beta 2" +state = "actual" +date = 2016-10-10 + +[[release."3.6"]] +stage = "3.6.0 beta 3" +state = "actual" +date = 2016-10-31 + +[[release."3.6"]] +stage = "3.6.0 beta 4" +state = "actual" +date = 2016-11-21 + +[[release."3.6"]] +stage = "3.6.0 candidate 1" +state = "actual" +date = 2016-12-06 + +[[release."3.6"]] +stage = "3.6.0 candidate 2" +state = "actual" +date = 2016-12-16 + +[[release."3.6"]] +stage = "3.6.0 final" +state = "actual" +date = 2016-12-23 + +[[release."3.6"]] +stage = "3.6.1 candidate 1" +state = "actual" +date = 2017-03-05 + +[[release."3.6"]] +stage = "3.6.1 final" +state = "actual" +date = 2017-03-21 + +[[release."3.6"]] +stage = "3.6.2 candidate 1" +state = "actual" +date = 2017-06-17 + +[[release."3.6"]] +stage = "3.6.2 candidate 2" +state = "actual" +date = 2017-07-07 + +[[release."3.6"]] +stage = "3.6.2 final" +state = "actual" +date = 2017-07-17 + +[[release."3.6"]] +stage = "3.6.3 candidate 1" +state = "actual" +date = 2017-09-19 + +[[release."3.6"]] +stage = "3.6.3 final" +state = "actual" +date = 2017-10-03 + +[[release."3.6"]] +stage = "3.6.4 candidate 1" +state = "actual" +date = 2017-12-05 + +[[release."3.6"]] +stage = "3.6.4 final" +state = "actual" +date = 2017-12-19 + +[[release."3.6"]] +stage = "3.6.5 candidate 1" +state = "actual" +date = 2018-03-13 + +[[release."3.6"]] +stage = "3.6.5 final" +state = "actual" +date = 2018-03-28 + +[[release."3.6"]] +stage = "3.6.6 candidate 1" +state = "actual" +date = 2018-06-12 + +[[release."3.6"]] +stage = "3.6.6 final" +state = "actual" +date = 2018-06-27 + +[[release."3.6"]] +stage = "3.6.7 candidate 1" +state = "actual" +date = 2018-09-26 + +[[release."3.6"]] +stage = "3.6.7 candidate 2" +state = "actual" +date = 2018-10-13 + +[[release."3.6"]] +stage = "3.6.7 final" +state = "actual" +date = 2018-10-20 + +[[release."3.6"]] +stage = "3.6.8 candidate 1" +state = "actual" +date = 2018-12-11 + +[[release."3.6"]] +stage = "3.6.8 final" +state = "actual" +date = 2018-12-24 + +[[release."3.6"]] +stage = "3.6.9 candidate 1" +state = "actual" +date = 2019-06-18 + +[[release."3.6"]] +stage = "3.6.9 final" +state = "actual" +date = 2019-07-02 + +[[release."3.6"]] +stage = "3.6.10 candidate 1" +state = "actual" +date = 2019-12-11 + +[[release."3.6"]] +stage = "3.6.10 final" +state = "actual" +date = 2019-12-18 + +[[release."3.6"]] +stage = "3.6.11 candidate 1" +state = "actual" +date = 2020-06-15 + +[[release."3.6"]] +stage = "3.6.11 final" +state = "actual" +date = 2020-06-27 + +[[release."3.6"]] +stage = "3.6.12 final" +state = "actual" +date = 2020-08-17 + +[[release."3.6"]] +stage = "3.6.13 final" +state = "actual" +date = 2021-02-15 + +[[release."3.6"]] +stage = "3.6.14 final" +state = "actual" +date = 2021-06-28 + +[[release."3.6"]] +stage = "3.6.15 final" +state = "actual" +date = 2021-09-04 + +# -- Python 3.7 -------------------------------------------------------------- + +[metadata."3.7"] +pep = 537 +status = "end-of-life" +branch = "3.7" +release-manager = "Ned Deily" +start-of-development = 2016-09-12 +feature-freeze = 2018-01-31 +first-release = 2018-06-27 +end-of-bugfix = 2020-06-27 +end-of-life = 2023-06-27 + +[[release."3.7"]] +stage = "3.7.0 alpha 1" +state = "actual" +date = 2017-09-19 + +[[release."3.7"]] +stage = "3.7.0 alpha 2" +state = "actual" +date = 2017-10-17 + +[[release."3.7"]] +stage = "3.7.0 alpha 3" +state = "actual" +date = 2017-12-05 + +[[release."3.7"]] +stage = "3.7.0 alpha 4" +state = "actual" +date = 2018-01-09 + +[[release."3.7"]] +stage = "3.7.0 beta 1" +state = "actual" +date = 2018-01-31 + +[[release."3.7"]] +stage = "3.7.0 beta 2" +state = "actual" +date = 2018-02-27 + +[[release."3.7"]] +stage = "3.7.0 beta 3" +state = "actual" +date = 2018-03-29 + +[[release."3.7"]] +stage = "3.7.0 beta 4" +state = "actual" +date = 2018-05-02 + +[[release."3.7"]] +stage = "3.7.0 beta 5" +state = "actual" +date = 2018-05-30 + +[[release."3.7"]] +stage = "3.7.0 candidate 1" +state = "actual" +date = 2018-06-12 + +[[release."3.7"]] +stage = "3.7.0 final" +state = "actual" +date = 2018-06-27 + +[[release."3.7"]] +stage = "3.7.1 candidate 1" +state = "actual" +date = 2018-09-26 + +[[release."3.7"]] +stage = "3.7.1 candidate 2" +state = "actual" +date = 2018-10-13 + +[[release."3.7"]] +stage = "3.7.1 final" +state = "actual" +date = 2018-10-20 + +[[release."3.7"]] +stage = "3.7.2 candidate 1" +state = "actual" +date = 2018-12-11 + +[[release."3.7"]] +stage = "3.7.2 final" +state = "actual" +date = 2018-12-24 + +[[release."3.7"]] +stage = "3.7.3 candidate 1" +state = "actual" +date = 2019-03-12 + +[[release."3.7"]] +stage = "3.7.3 final" +state = "actual" +date = 2019-03-25 + +[[release."3.7"]] +stage = "3.7.4 candidate 1" +state = "actual" +date = 2019-06-18 + +[[release."3.7"]] +stage = "3.7.4 candidate 2" +state = "actual" +date = 2019-07-02 + +[[release."3.7"]] +stage = "3.7.4 final" +state = "actual" +date = 2019-07-08 + +[[release."3.7"]] +stage = "3.7.5 candidate 1" +state = "actual" +date = 2019-10-02 + +[[release."3.7"]] +stage = "3.7.5 final" +state = "actual" +date = 2019-10-15 + +[[release."3.7"]] +stage = "3.7.6 candidate 1" +state = "actual" +date = 2019-12-11 + +[[release."3.7"]] +stage = "3.7.6 final" +state = "actual" +date = 2019-12-18 + +[[release."3.7"]] +stage = "3.7.7 candidate 1" +state = "actual" +date = 2020-03-04 + +[[release."3.7"]] +stage = "3.7.7 final" +state = "actual" +date = 2020-03-10 + +[[release."3.7"]] +stage = "3.7.8 candidate 1" +state = "actual" +date = 2020-06-15 + +[[release."3.7"]] +stage = "3.7.8 final" +state = "actual" +date = 2020-06-27 + +[[release."3.7"]] +stage = "3.7.9 final" +state = "actual" +date = 2020-08-17 + +[[release."3.7"]] +stage = "3.7.10 final" +state = "actual" +date = 2021-02-15 + +[[release."3.7"]] +stage = "3.7.11 final" +state = "actual" +date = 2021-06-28 + +[[release."3.7"]] +stage = "3.7.12 final" +state = "actual" +date = 2021-09-04 + +[[release."3.7"]] +stage = "3.7.13 final" +state = "actual" +date = 2022-03-16 + +[[release."3.7"]] +stage = "3.7.14 final" +state = "actual" +date = 2022-09-06 + +[[release."3.7"]] +stage = "3.7.15 final" +state = "actual" +date = 2022-10-11 + +[[release."3.7"]] +stage = "3.7.16 final" +state = "actual" +date = 2022-12-06 + +[[release."3.7"]] +stage = "3.7.17 final" +state = "actual" +date = 2023-06-06 + +# -- Python 3.8 -------------------------------------------------------------- + +[metadata."3.8"] +pep = 569 +status = "end-of-life" +branch = "3.8" +release-manager = "Łukasz Langa" +start-of-development = 2018-01-29 +feature-freeze = 2019-06-04 +first-release = 2019-10-14 +end-of-bugfix = 2021-05-03 +end-of-life = 2024-10-07 + +[[release."3.8"]] +stage = "3.8.0 alpha 1" +state = "actual" +date = 2019-02-03 + +[[release."3.8"]] +stage = "3.8.0 alpha 2" +state = "actual" +date = 2019-02-25 + +[[release."3.8"]] +stage = "3.8.0 alpha 3" +state = "actual" +date = 2019-03-25 + +[[release."3.8"]] +stage = "3.8.0 alpha 4" +state = "actual" +date = 2019-05-06 + +[[release."3.8"]] +stage = "3.8.0 beta 1" +state = "actual" +date = 2019-06-04 + +[[release."3.8"]] +stage = "3.8.0 beta 2" +state = "actual" +date = 2019-07-04 + +[[release."3.8"]] +stage = "3.8.0 beta 3" +state = "actual" +date = 2019-07-29 + +[[release."3.8"]] +stage = "3.8.0 beta 4" +state = "actual" +date = 2019-08-30 + +[[release."3.8"]] +stage = "3.8.0 candidate 1" +state = "actual" +date = 2019-10-01 + +[[release."3.8"]] +stage = "3.8.0 final" +state = "actual" +date = 2019-10-14 + +[[release."3.8"]] +stage = "3.8.1 candidate 1" +state = "actual" +date = 2019-12-10 + +[[release."3.8"]] +stage = "3.8.1 final" +state = "actual" +date = 2019-12-18 + +[[release."3.8"]] +stage = "3.8.2 candidate 1" +state = "actual" +date = 2020-02-10 + +[[release."3.8"]] +stage = "3.8.2 candidate 2" +state = "actual" +date = 2020-02-17 + +[[release."3.8"]] +stage = "3.8.2 final" +state = "actual" +date = 2020-02-24 + +[[release."3.8"]] +stage = "3.8.3 candidate 1" +state = "actual" +date = 2020-04-29 + +[[release."3.8"]] +stage = "3.8.3 final" +state = "actual" +date = 2020-05-13 + +[[release."3.8"]] +stage = "3.8.4 candidate 1" +state = "actual" +date = 2020-06-30 + +[[release."3.8"]] +stage = "3.8.4 final" +state = "actual" +date = 2020-07-13 + +[[release."3.8"]] +stage = "3.8.5 final" +state = "actual" +date = 2020-07-20 +note = "security hotfix" + +[[release."3.8"]] +stage = "3.8.6 candidate 1" +state = "actual" +date = 2020-09-08 + +[[release."3.8"]] +stage = "3.8.6 final" +state = "actual" +date = 2020-09-24 + +[[release."3.8"]] +stage = "3.8.7 candidate 1" +state = "actual" +date = 2020-12-07 + +[[release."3.8"]] +stage = "3.8.7 final" +state = "actual" +date = 2020-12-21 + +[[release."3.8"]] +stage = "3.8.8 candidate 1" +state = "actual" +date = 2021-02-16 + +[[release."3.8"]] +stage = "3.8.8 final" +state = "actual" +date = 2021-02-19 + +[[release."3.8"]] +stage = "3.8.9 final" +state = "actual" +date = 2021-04-02 +note = "security hotfix" + +[[release."3.8"]] +stage = "3.8.10 final" +state = "actual" +date = 2021-05-03 + +[[release."3.8"]] +stage = "3.8.11 final" +state = "actual" +date = 2021-06-28 + +[[release."3.8"]] +stage = "3.8.12 final" +state = "actual" +date = 2021-08-30 + +[[release."3.8"]] +stage = "3.8.13 final" +state = "actual" +date = 2022-03-16 + +[[release."3.8"]] +stage = "3.8.14 final" +state = "actual" +date = 2022-09-06 + +[[release."3.8"]] +stage = "3.8.15 final" +state = "actual" +date = 2022-10-11 + +[[release."3.8"]] +stage = "3.8.16 final" +state = "actual" +date = 2022-12-06 + +[[release."3.8"]] +stage = "3.8.17 final" +state = "actual" +date = 2023-06-06 + + +[[release."3.8"]] +stage = "3.8.18 final" +state = "actual" +date = 2023-08-24 + +[[release."3.8"]] +stage = "3.8.19 final" +state = "actual" +date = 2024-03-19 + +[[release."3.8"]] +stage = "3.8.20 final" +state = "actual" +date = 2024-09-06 +note = "final security release" + +# -- Python 3.9 -------------------------------------------------------------- + +[metadata."3.9"] +pep = 596 +status = "end-of-life" +branch = "3.9" +release-manager = "Łukasz Langa" +start-of-development = 2019-06-04 +feature-freeze = 2020-05-18 +first-release = 2020-10-05 +end-of-bugfix = 2022-05-17 +end-of-life = 2025-10-31 + +[[release."3.9"]] +stage = "3.9.0 alpha 1" +state = "actual" +date = 2019-11-19 + +[[release."3.9"]] +stage = "3.9.0 alpha 2" +state = "actual" +date = 2019-12-18 + +[[release."3.9"]] +stage = "3.9.0 alpha 3" +state = "actual" +date = 2020-01-25 + +[[release."3.9"]] +stage = "3.9.0 alpha 4" +state = "actual" +date = 2020-02-26 + +[[release."3.9"]] +stage = "3.9.0 alpha 5" +state = "actual" +date = 2020-03-23 + +[[release."3.9"]] +stage = "3.9.0 alpha 6" +state = "actual" +date = 2020-04-28 + +[[release."3.9"]] +stage = "3.9.0 beta 1" +state = "actual" +date = 2020-05-18 + +# There was no beta 2, the PEP statest that it was recalled. + +[[release."3.9"]] +stage = "3.9.0 beta 3" +state = "actual" +date = 2020-06-09 +note = "beta 2 was recalled." + +[[release."3.9"]] +stage = "3.9.0 beta 4" +state = "actual" +date = 2020-07-03 + +[[release."3.9"]] +stage = "3.9.0 beta 5" +state = "actual" +date = 2020-07-20 + +[[release."3.9"]] +stage = "3.9.0 candidate 1" +state = "actual" +date = 2020-08-11 + +[[release."3.9"]] +stage = "3.9.0 candidate 2" +state = "actual" +date = 2020-09-17 + +[[release."3.9"]] +stage = "3.9.0 final" +state = "actual" +date = 2020-10-05 + +[[release."3.9"]] +stage = "3.9.1 candidate 1" +state = "actual" +date = 2020-11-24 + +[[release."3.9"]] +stage = "3.9.1 final" +state = "actual" +date = 2020-12-07 + +[[release."3.9"]] +stage = "3.9.2 candidate 1" +state = "actual" +date = 2021-02-16 + +[[release."3.9"]] +stage = "3.9.2 final" +state = "actual" +date = 2021-02-19 + +[[release."3.9"]] +stage = "3.9.3 final" +state = "actual" +date = 2021-04-02 +note = "security hotfix; recalled due to bpo-43710" + +[[release."3.9"]] +stage = "3.9.4 final" +state = "actual" +date = 2021-04-04 +note = "ABI compatibility hotfix" + +[[release."3.9"]] +stage = "3.9.5 final" +state = "actual" +date = 2021-05-03 + +[[release."3.9"]] +stage = "3.9.6 final" +state = "actual" +date = 2021-06-28 + +[[release."3.9"]] +stage = "3.9.7 final" +state = "actual" +date = 2021-08-30 + +[[release."3.9"]] +stage = "3.9.8 final" +state = "actual" +date = 2021-11-05 +note = "recalled due to bpo-45235" + +[[release."3.9"]] +stage = "3.9.9 final" +state = "actual" +date = 2021-11-15 + +[[release."3.9"]] +stage = "3.9.10 final" +state = "actual" +date = 2022-01-14 + +[[release."3.9"]] +stage = "3.9.11 final" +state = "actual" +date = 2022-03-16 + +[[release."3.9"]] +stage = "3.9.12 final" +state = "actual" +date = 2022-03-23 + +[[release."3.9"]] +stage = "3.9.13 final" +state = "actual" +date = 2022-05-17 + +[[release."3.9"]] +stage = "3.9.14 final" +state = "actual" +date = 2022-09-06 + +[[release."3.9"]] +stage = "3.9.15 final" +state = "actual" +date = 2022-10-11 + +[[release."3.9"]] +stage = "3.9.16 final" +state = "actual" +date = 2022-12-06 + +[[release."3.9"]] +stage = "3.9.17 final" +state = "actual" +date = 2023-06-06 + +[[release."3.9"]] +stage = "3.9.18 final" +state = "actual" +date = 2023-08-24 + +[[release."3.9"]] +stage = "3.9.19 final" +state = "actual" +date = 2024-03-19 + +[[release."3.9"]] +stage = "3.9.20 final" +state = "actual" +date = 2024-09-06 + +[[release."3.9"]] +stage = "3.9.21 final" +state = "actual" +date = 2024-12-03 + +[[release."3.9"]] +stage = "3.9.22 final" +state = "actual" +date = 2025-04-08 + +[[release."3.9"]] +stage = "3.9.23 final" +state = "actual" +date = 2025-06-03 + +[[release."3.9"]] +stage = "3.9.24 final" +state = "actual" +date = 2025-10-09 + +[[release."3.9"]] +stage = "3.9.25 final" +state = "actual" +date = 2025-10-31 + +# -- Python 3.10 -------------------------------------------------------------- + +[metadata."3.10"] +pep = 619 +status = "security" +branch = "3.10" +release-manager = "Pablo Galindo Salgado" +start-of-development = 2020-05-18 +feature-freeze = 2021-05-03 +first-release = 2021-10-04 +end-of-bugfix = 2023-04-05 +end-of-life = 2026-10-01 + +[[release."3.10"]] +stage = "3.10.0 alpha 1" +state = "actual" +date = 2020-10-05 + +[[release."3.10"]] +stage = "3.10.0 alpha 2" +state = "actual" +date = 2020-11-03 + +[[release."3.10"]] +stage = "3.10.0 alpha 3" +state = "actual" +date = 2020-12-07 + +[[release."3.10"]] +stage = "3.10.0 alpha 4" +state = "actual" +date = 2021-01-04 + +[[release."3.10"]] +stage = "3.10.0 alpha 5" +state = "actual" +date = 2021-02-03 + +[[release."3.10"]] +stage = "3.10.0 alpha 6" +state = "actual" +date = 2021-03-01 + +[[release."3.10"]] +stage = "3.10.0 alpha 7" +state = "actual" +date = 2021-04-06 + +[[release."3.10"]] +stage = "3.10.0 beta 1" +state = "actual" +date = 2021-05-03 + +[[release."3.10"]] +stage = "3.10.0 beta 2" +state = "actual" +date = 2021-05-31 + +[[release."3.10"]] +stage = "3.10.0 beta 3" +state = "actual" +date = 2021-06-17 + +[[release."3.10"]] +stage = "3.10.0 beta 4" +state = "actual" +date = 2021-07-10 + +[[release."3.10"]] +stage = "3.10.0 candidate 1" +state = "actual" +date = 2021-08-03 + +[[release."3.10"]] +stage = "3.10.0 candidate 2" +state = "actual" +date = 2021-09-07 + +[[release."3.10"]] +stage = "3.10.0 final" +state = "actual" +date = 2021-10-04 + +[[release."3.10"]] +stage = "3.10.1" +state = "actual" +date = 2021-12-06 + +[[release."3.10"]] +stage = "3.10.2" +state = "actual" +date = 2022-01-14 + +[[release."3.10"]] +stage = "3.10.3" +state = "actual" +date = 2022-03-16 + +[[release."3.10"]] +stage = "3.10.4" +state = "actual" +date = 2022-03-24 + +[[release."3.10"]] +stage = "3.10.5" +state = "actual" +date = 2022-06-06 + +[[release."3.10"]] +stage = "3.10.6" +state = "actual" +date = 2022-08-02 + +[[release."3.10"]] +stage = "3.10.7" +state = "actual" +date = 2022-09-06 + +[[release."3.10"]] +stage = "3.10.8" +state = "actual" +date = 2022-10-11 + +[[release."3.10"]] +stage = "3.10.9" +state = "actual" +date = 2022-12-06 + +[[release."3.10"]] +stage = "3.10.10" +state = "actual" +date = 2023-02-08 + +[[release."3.10"]] +stage = "3.10.11" +state = "actual" +date = 2023-04-05 + +[[release."3.10"]] +stage = "3.10.12" +state = "actual" +date = 2023-06-06 + +[[release."3.10"]] +stage = "3.10.13" +state = "actual" +date = 2023-08-24 + +[[release."3.10"]] +stage = "3.10.14" +state = "actual" +date = 2024-03-19 + +[[release."3.10"]] +stage = "3.10.15" +state = "actual" +date = 2024-09-07 + +[[release."3.10"]] +stage = "3.10.16" +state = "actual" +date = 2024-12-03 + +[[release."3.10"]] +stage = "3.10.17" +state = "actual" +date = 2025-04-08 + +[[release."3.10"]] +stage = "3.10.18" +state = "actual" +date = 2025-06-03 + +[[release."3.10"]] +stage = "3.10.19" +state = "actual" +date = 2025-10-09 + +# -- Python 3.11 -------------------------------------------------------------- + +[metadata."3.11"] +pep = 664 +status = "security" +branch = "3.11" +release-manager = "Pablo Galindo Salgado" +start-of-development = 2021-05-03 +feature-freeze = 2022-05-08 +first-release = 2022-10-24 +end-of-bugfix = 2024-04-24 +end-of-life = 2027-10-01 + +[[release."3.11"]] +stage = "3.11.0 alpha 1" +state = "actual" +date = 2021-10-05 + +[[release."3.11"]] +stage = "3.11.0 alpha 2" +state = "actual" +date = 2021-11-02 + +[[release."3.11"]] +stage = "3.11.0 alpha 3" +state = "actual" +date = 2021-12-08 + +[[release."3.11"]] +stage = "3.11.0 alpha 4" +state = "actual" +date = 2022-01-14 + +[[release."3.11"]] +stage = "3.11.0 alpha 5" +state = "actual" +date = 2022-02-03 + +[[release."3.11"]] +stage = "3.11.0 alpha 6" +state = "actual" +date = 2022-03-07 + +[[release."3.11"]] +stage = "3.11.0 alpha 7" +state = "actual" +date = 2022-04-05 + +[[release."3.11"]] +stage = "3.11.0 beta 1" +state = "actual" +date = 2022-05-08 + +[[release."3.11"]] +stage = "3.11.0 beta 2" +state = "actual" +date = 2022-05-31 + +[[release."3.11"]] +stage = "3.11.0 beta 3" +state = "actual" +date = 2022-06-01 + +[[release."3.11"]] +stage = "3.11.0 beta 4" +state = "actual" +date = 2022-07-11 + +[[release."3.11"]] +stage = "3.11.0 beta 5" +state = "actual" +date = 2022-07-26 + +[[release."3.11"]] +stage = "3.11.0 candidate 1" +state = "actual" +date = 2022-08-08 + +[[release."3.11"]] +stage = "3.11.0 candidate 2" +state = "actual" +date = 2022-09-12 + +[[release."3.11"]] +stage = "3.11.0 final" +state = "actual" +date = 2022-10-24 + +[[release."3.11"]] +stage = "3.11.1" +state = "actual" +date = 2022-12-06 + +[[release."3.11"]] +stage = "3.11.2" +state = "actual" +date = 2023-02-08 + +[[release."3.11"]] +stage = "3.11.3" +state = "actual" +date = 2023-04-05 + +[[release."3.11"]] +stage = "3.11.4" +state = "actual" +date = 2023-06-06 + +[[release."3.11"]] +stage = "3.11.5" +state = "actual" +date = 2023-08-24 + +[[release."3.11"]] +stage = "3.11.6" +state = "actual" +date = 2023-10-02 + +[[release."3.11"]] +stage = "3.11.7" +state = "actual" +date = 2023-12-04 + +[[release."3.11"]] +stage = "3.11.8" +state = "actual" +date = 2024-02-06 + +[[release."3.11"]] +stage = "3.11.9" +state = "actual" +date = 2024-04-02 + +[[release."3.11"]] +stage = "3.11.10" +state = "actual" +date = 2024-09-07 + +[[release."3.11"]] +stage = "3.11.11" +state = "actual" +date = 2024-12-03 + +[[release."3.11"]] +stage = "3.11.12" +state = "actual" +date = 2025-04-08 + +[[release."3.11"]] +stage = "3.11.13" +state = "actual" +date = 2025-06-03 + +[[release."3.11"]] +stage = "3.11.14" +state = "actual" +date = 2025-10-09 + +# -- Python 3.12 -------------------------------------------------------------- + +[metadata."3.12"] +pep = 693 +status = "security" +branch = "3.12" +release-manager = "Thomas Wouters" +start-of-development = 2022-05-08 +feature-freeze = 2023-05-22 +first-release = 2023-10-02 +end-of-bugfix = 2025-04-08 +end-of-life = 2028-10-01 + +[[release."3.12"]] +stage = "3.12.0 alpha 1" +state = "actual" +date = 2022-10-24 + +[[release."3.12"]] +stage = "3.12.0 alpha 2" +state = "actual" +date = 2022-11-14 + +[[release."3.12"]] +stage = "3.12.0 alpha 3" +state = "actual" +date = 2022-12-06 + +[[release."3.12"]] +stage = "3.12.0 alpha 4" +state = "actual" +date = 2023-01-10 + +[[release."3.12"]] +stage = "3.12.0 alpha 5" +state = "actual" +date = 2023-02-07 + +[[release."3.12"]] +stage = "3.12.0 alpha 6" +state = "actual" +date = 2023-03-07 + +[[release."3.12"]] +stage = "3.12.0 alpha 7" +state = "actual" +date = 2023-04-04 + +[[release."3.12"]] +stage = "3.12.0 beta 1" +state = "actual" +date = 2023-05-22 + +[[release."3.12"]] +stage = "3.12.0 beta 2" +state = "actual" +date = 2023-06-06 + +[[release."3.12"]] +stage = "3.12.0 beta 3" +state = "actual" +date = 2023-06-19 + +[[release."3.12"]] +stage = "3.12.0 beta 4" +state = "actual" +date = 2023-07-11 + +[[release."3.12"]] +stage = "3.12.0 candidate 1" +state = "actual" +date = 2023-08-06 + +[[release."3.12"]] +stage = "3.12.0 candidate 2" +state = "actual" +date = 2023-09-06 + +[[release."3.12"]] +stage = "3.12.0 candidate 3" +state = "actual" +date = 2023-09-19 + +[[release."3.12"]] +stage = "3.12.0 final" +state = "actual" +date = 2023-10-02 + +[[release."3.12"]] +stage = "3.12.1" +state = "actual" +date = 2023-12-07 + +[[release."3.12"]] +stage = "3.12.2" +state = "actual" +date = 2024-02-06 + +[[release."3.12"]] +stage = "3.12.3" +state = "actual" +date = 2024-04-09 + +[[release."3.12"]] +stage = "3.12.4" +state = "actual" +date = 2024-06-06 + +[[release."3.12"]] +stage = "3.12.5" +state = "actual" +date = 2024-08-06 + +[[release."3.12"]] +stage = "3.12.6" +state = "actual" +date = 2024-09-06 + +[[release."3.12"]] +stage = "3.12.7" +state = "actual" +date = 2024-10-01 + +[[release."3.12"]] +stage = "3.12.8" +state = "actual" +date = 2024-12-03 + +[[release."3.12"]] +stage = "3.12.9" +state = "actual" +date = 2025-02-04 + +[[release."3.12"]] +stage = "3.12.10" +state = "actual" +date = 2025-04-08 + +[[release."3.12"]] +stage = "3.12.11" +state = "actual" +date = 2025-06-03 + +[[release."3.12"]] +stage = "3.12.12" +state = "actual" +date = 2025-10-09 + +# -- Python 3.13 -------------------------------------------------------------- + +[metadata."3.13"] +pep = 719 +status = "bugfix" +branch = "3.13" +release-manager = "Thomas Wouters" +start-of-development = 2023-05-22 +feature-freeze = 2024-05-08 +first-release = 2024-10-07 +end-of-bugfix = 2026-10-07 +end-of-life = 2029-10-01 + +[[release."3.13"]] +stage = "3.13.0 alpha 1" +state = "actual" +date = 2023-10-13 + +[[release."3.13"]] +stage = "3.13.0 alpha 2" +state = "actual" +date = 2023-11-22 + +[[release."3.13"]] +stage = "3.13.0 alpha 3" +state = "actual" +date = 2024-01-17 + +[[release."3.13"]] +stage = "3.13.0 alpha 4" +state = "actual" +date = 2024-02-15 + +[[release."3.13"]] +stage = "3.13.0 alpha 5" +state = "actual" +date = 2024-03-12 + +[[release."3.13"]] +stage = "3.13.0 alpha 6" +state = "actual" +date = 2024-04-09 + +[[release."3.13"]] +stage = "3.13.0 beta 1" +state = "actual" +date = 2024-05-08 + +[[release."3.13"]] +stage = "3.13.0 beta 2" +state = "actual" +date = 2024-06-05 + +[[release."3.13"]] +stage = "3.13.0 beta 3" +state = "actual" +date = 2024-06-27 + +[[release."3.13"]] +stage = "3.13.0 beta 4" +state = "actual" +date = 2024-07-18 + +[[release."3.13"]] +stage = "3.13.0 candidate 1" +state = "actual" +date = 2024-08-01 + +[[release."3.13"]] +stage = "3.13.0 candidate 2" +state = "actual" +date = 2024-09-06 + +[[release."3.13"]] +stage = "3.13.0 candidate 3" +state = "actual" +date = 2024-10-01 + +[[release."3.13"]] +stage = "3.13.0 final" +state = "actual" +date = 2024-10-07 + +[[release."3.13"]] +stage = "3.13.1" +state = "actual" +date = 2024-12-03 + +[[release."3.13"]] +stage = "3.13.2" +state = "actual" +date = 2025-02-04 + +[[release."3.13"]] +stage = "3.13.3" +state = "actual" +date = 2025-04-08 + +[[release."3.13"]] +stage = "3.13.4" +state = "actual" +date = 2025-06-03 + +[[release."3.13"]] +stage = "3.13.5" +state = "actual" +date = 2025-06-11 +note = "hotfix" + +[[release."3.13"]] +stage = "3.13.6" +state = "actual" +date = 2025-08-06 + +[[release."3.13"]] +stage = "3.13.7" +state = "actual" +date = 2025-08-14 + +[[release."3.13"]] +stage = "3.13.8" +state = "actual" +date = 2025-10-07 + +[[release."3.13"]] +stage = "3.13.9" +state = "actual" +date = 2025-10-14 + +[[release."3.13"]] +stage = "3.13.10" +state = "actual" +date = 2025-12-02 + +[[release."3.13"]] +stage = "3.13.11" +state = "actual" +date = 2025-12-05 + +[[release."3.13"]] +stage = "3.13.12" +state = "expected" +date = 2026-02-03 + +[[release."3.13"]] +stage = "3.13.13" +state = "expected" +date = 2026-04-07 + +[[release."3.13"]] +stage = "3.13.14" +state = "expected" +date = 2026-06-09 + +[[release."3.13"]] +stage = "3.13.15" +state = "expected" +date = 2026-08-04 + +[[release."3.13"]] +stage = "3.13.16" +state = "expected" +date = 2026-10-06 + +# -- Python 3.14 -------------------------------------------------------------- + +[metadata."3.14"] +pep = 745 +status = "bugfix" +branch = "3.14" +release-manager = "Hugo van Kemenade" +start-of-development = 2024-05-08 +feature-freeze = 2025-05-07 +first-release = 2025-10-07 +end-of-bugfix = 2027-10-07 +end-of-life = 2030-10-01 + +[[release."3.14"]] +stage = "3.14.0 alpha 1" +state = "actual" +date = 2024-10-15 + +[[release."3.14"]] +stage = "3.14.0 alpha 2" +state = "actual" +date = 2024-11-19 + +[[release."3.14"]] +stage = "3.14.0 alpha 3" +state = "actual" +date = 2024-12-17 + +[[release."3.14"]] +stage = "3.14.0 alpha 4" +state = "actual" +date = 2025-01-14 + +[[release."3.14"]] +stage = "3.14.0 alpha 5" +state = "actual" +date = 2025-02-11 + +[[release."3.14"]] +stage = "3.14.0 alpha 6" +state = "actual" +date = 2025-03-14 + +[[release."3.14"]] +stage = "3.14.0 alpha 7" +state = "actual" +date = 2025-04-08 + +[[release."3.14"]] +stage = "3.14.0 beta 1" +state = "actual" +date = 2025-05-07 + +[[release."3.14"]] +stage = "3.14.0 beta 2" +state = "actual" +date = 2025-05-26 + +[[release."3.14"]] +stage = "3.14.0 beta 3" +state = "actual" +date = 2025-06-17 + +[[release."3.14"]] +stage = "3.14.0 beta 4" +state = "actual" +date = 2025-07-08 + +[[release."3.14"]] +stage = "3.14.0 candidate 1" +state = "actual" +date = 2025-07-22 + +[[release."3.14"]] +stage = "3.14.0 candidate 2" +state = "actual" +date = 2025-08-14 + +[[release."3.14"]] +stage = "3.14.0 candidate 3" +state = "actual" +date = 2025-09-18 + +[[release."3.14"]] +stage = "3.14.0 final" +state = "actual" +date = 2025-10-07 + +[[release."3.14"]] +stage = "3.14.1" +state = "actual" +date = 2025-12-02 + +[[release."3.14"]] +stage = "3.14.2" +state = "actual" +date = 2025-12-05 + +[[release."3.14"]] +stage = "3.14.3" +state = "expected" +date = 2026-02-03 + +[[release."3.14"]] +stage = "3.14.4" +state = "expected" +date = 2026-04-07 + +[[release."3.14"]] +stage = "3.14.5" +state = "expected" +date = 2026-06-09 + +[[release."3.14"]] +stage = "3.14.6" +state = "expected" +date = 2026-08-04 + +[[release."3.14"]] +stage = "3.14.7" +state = "expected" +date = 2026-10-06 + +[[release."3.14"]] +stage = "3.14.8" +state = "expected" +date = 2026-12-01 + +[[release."3.14"]] +stage = "3.14.9" +state = "expected" +date = 2027-02-02 + +[[release."3.14"]] +stage = "3.14.10" +state = "expected" +date = 2027-04-06 + +[[release."3.14"]] +stage = "3.14.11" +state = "expected" +date = 2027-06-01 + +[[release."3.14"]] +stage = "3.14.12" +state = "expected" +date = 2027-08-03 + +[[release."3.14"]] +stage = "3.14.13" +state = "expected" +date = 2027-10-05 + +# -- Python 3.15 -------------------------------------------------------------- + +[metadata."3.15"] +pep = 790 +status = "feature" +branch = "main" +release-manager = "Hugo van Kemenade" +start-of-development = 2025-05-07 +feature-freeze = 2026-05-05 +first-release = 2026-10-01 +end-of-bugfix = 2028-10-01 +end-of-life = 2031-10-01 + +[[release."3.15"]] +stage = "3.15.0 alpha 1" +state = "actual" +date = 2025-10-14 + +[[release."3.15"]] +stage = "3.15.0 alpha 2" +state = "actual" +date = 2025-11-19 + +[[release."3.15"]] +stage = "3.15.0 alpha 3" +state = "expected" +date = 2025-12-16 + +[[release."3.15"]] +stage = "3.15.0 alpha 4" +state = "expected" +date = 2026-01-13 + +[[release."3.15"]] +stage = "3.15.0 alpha 5" +state = "expected" +date = 2026-02-10 + +[[release."3.15"]] +stage = "3.15.0 alpha 6" +state = "expected" +date = 2026-03-10 + +[[release."3.15"]] +stage = "3.15.0 alpha 7" +state = "expected" +date = 2026-04-07 + +[[release."3.15"]] +stage = "3.15.0 beta 1" +state = "expected" +date = 2026-05-05 + +[[release."3.15"]] +stage = "3.15.0 beta 2" +state = "expected" +date = 2026-05-26 + +[[release."3.15"]] +stage = "3.15.0 beta 3" +state = "expected" +date = 2026-06-16 + +[[release."3.15"]] +stage = "3.15.0 beta 4" +state = "expected" +date = 2026-07-14 + +[[release."3.15"]] +stage = "3.15.0 candidate 1" +state = "expected" +date = 2026-07-28 + +[[release."3.15"]] +stage = "3.15.0 candidate 2" +state = "expected" +date = 2026-09-01 + +[[release."3.15"]] +stage = "3.15.0 final" +state = "expected" +date = 2026-10-01 diff --git a/release_management/serialize.py b/release_management/serialize.py new file mode 100644 index 00000000000..051ed77c3ec --- /dev/null +++ b/release_management/serialize.py @@ -0,0 +1,115 @@ +from __future__ import annotations + +import datetime as dt +import dataclasses +import json + +from release_management import ROOT_DIR, load_python_releases + +TYPE_CHECKING = False +if TYPE_CHECKING: + from release_management import ReleaseInfo, VersionMetadata + +# Seven years captures the full lifecycle from prereleases to end-of-life +TODAY = dt.date.today() +SEVEN_YEARS_AGO = TODAY.replace(year=TODAY.year - 7) + +# https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.11 +CALENDAR_ESCAPE_TEXT = str.maketrans({ + '\\': r'\\', + ';': r'\;', + ',': r'\,', + '\n': r'\n', +}) + + +def create_release_json() -> str: + python_releases = dataclasses.asdict(load_python_releases()) + return json.dumps( + python_releases, + indent=2, + sort_keys=False, + ensure_ascii=False, + default=str, + ) + + +def create_release_cycle() -> str: + metadata = load_python_releases().metadata + all_versions = sorted( + ((d.first_release, v) for v, d in metadata.items()), reverse=True + ) + versions = [v for _date, v in all_versions if version_to_tuple(v) >= (2, 6)] + release_cycle = {version: version_info(metadata[version]) for version in versions} + rc_json = json.dumps(release_cycle, indent=2, sort_keys=False, ensure_ascii=False) + return f'{rc_json}\n' + + +def version_to_tuple(version: str, /) -> tuple[int, ...]: + return tuple(map(int, version.split('.'))) + + +def version_info(metadata: VersionMetadata, /) -> dict[str, str | int]: + end_of_life = metadata.end_of_life.isoformat() + if metadata.status != 'end-of-life': + end_of_life = end_of_life.removesuffix('-01') + return { + 'branch': metadata.branch, + 'pep': metadata.pep, + 'status': metadata.status, + 'first_release': metadata.first_release.isoformat(), + 'end_of_life': end_of_life, + 'release_manager': metadata.release_manager, + } + + +def create_release_schedule_calendar() -> str: + python_releases = load_python_releases() + releases = [] + for version, all_releases in python_releases.releases.items(): + pep_number = python_releases.metadata[version].pep + for release in all_releases: + # Keep size reasonable by omitting releases older than 7 years + if release.date < SEVEN_YEARS_AGO: + continue + releases.append((pep_number, release)) + releases.sort(key=lambda r: r[1].date) + lines = release_schedule_calendar_lines(releases) + return '\r\n'.join(lines) + + +def release_schedule_calendar_lines( + releases: list[tuple[int, ReleaseInfo]], / +) -> list[str]: + dtstamp = dt.datetime.now(dt.timezone.utc).strftime('%Y%m%dT%H%M%SZ') + + lines = [ + 'BEGIN:VCALENDAR', + 'VERSION:2.0', + 'PRODID:-//Python Software Foundation//Python release schedule//EN', + 'X-WR-CALDESC:Python releases schedule from https://peps.python.org', + 'X-WR-CALNAME:Python releases schedule', + ] + for pep_number, release in releases: + normalised_stage = release.stage.replace(' ', '') + normalised_stage = normalised_stage.translate(CALENDAR_ESCAPE_TEXT) + if release.note: + normalised_note = release.note.translate(CALENDAR_ESCAPE_TEXT) + note = (f'DESCRIPTION:Note: {normalised_note}',) + else: + note = () + lines += ( + 'BEGIN:VEVENT', + f'DTSTAMP:{dtstamp}', + f'UID:python-{normalised_stage}@releases.python.org', + f'DTSTART;VALUE=DATE:{release.date.strftime("%Y%m%d")}', + f'SUMMARY:Python {release.stage}', + *note, + f'URL:https://peps.python.org/pep-{pep_number:04d}/', + 'END:VEVENT', + ) + lines += ( + 'END:VCALENDAR', + '', + ) + return lines diff --git a/release_management/tests/test_release_schedule_calendar.py b/release_management/tests/test_release_schedule_calendar.py new file mode 100644 index 00000000000..0d1fcff61bf --- /dev/null +++ b/release_management/tests/test_release_schedule_calendar.py @@ -0,0 +1,49 @@ +import datetime as dt + +from release_management import ReleaseInfo, serialize + +FAKE_RELEASE = ReleaseInfo( + stage='X.Y.Z final', + state='actual', + date=dt.date(2000, 1, 1), + note='These characters need escaping: \\ , ; \n', +) + + +def test_create_release_calendar_has_calendar_metadata() -> None: + # Act + cal_lines = serialize.create_release_schedule_calendar().split('\r\n') + + # Assert + + # Check calendar metadata + assert cal_lines[:5] == [ + 'BEGIN:VCALENDAR', + 'VERSION:2.0', + 'PRODID:-//Python Software Foundation//Python release schedule//EN', + 'X-WR-CALDESC:Python releases schedule from https://peps.python.org', + 'X-WR-CALNAME:Python releases schedule', + ] + assert cal_lines[-2:] == [ + 'END:VCALENDAR', + '', + ] + + +def test_create_release_calendar_first_event() -> None: + # Act + releases = [(9999, FAKE_RELEASE)] + cal_lines = serialize.release_schedule_calendar_lines(releases) + + # Assert + assert cal_lines[5] == 'BEGIN:VEVENT' + assert cal_lines[6].startswith('DTSTAMP:') + assert cal_lines[6].endswith('Z') + assert cal_lines[7] == 'UID:python-X.Y.Zfinal@releases.python.org' + assert cal_lines[8] == 'DTSTART;VALUE=DATE:20000101' + assert cal_lines[9] == 'SUMMARY:Python X.Y.Z final' + assert cal_lines[10] == ( + 'DESCRIPTION:Note: These characters need escaping: \\\\ \\, \\; \\n' + ) + assert cal_lines[11] == 'URL:https://peps.python.org/pep-9999/' + assert cal_lines[12] == 'END:VEVENT' diff --git a/release_management/update_release_schedules.py b/release_management/update_release_schedules.py new file mode 100644 index 00000000000..aeb33583dac --- /dev/null +++ b/release_management/update_release_schedules.py @@ -0,0 +1,179 @@ +"""Update release schedules in PEPs. + +The ``python-releases.toml`` data is treated as authoritative for the given +versions in ``VERSIONS_TO_REGENERATE``. Each PEP must contain markers for the +start and end of each release schedule (feature, bugfix, and security, as +appropriate). These are: + + .. release schedule: feature + .. release schedule: bugfix + .. release schedule: security + .. release schedule: ends + +This script will use the dates in the [[release."{version}"]] tables to create +and update the release schedule lists in each PEP. + +Optionally, to add a comment or note to a particular release, use the ``note`` +field, which will append the given text in brackets to the relevant line. + +Usage: + + $ python -m release_management update-peps + $ # or + $ make regen-all +""" + +from __future__ import annotations + +import datetime as dt + +from release_management import ( + PEP_ROOT, + ReleaseInfo, + VersionMetadata, + load_python_releases, +) + +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Iterator + + from release_management import ReleaseSchedules, ReleaseState, VersionMetadata + +TODAY = dt.date.today() + +SKIPPED_VERSIONS = frozenset({ + '1.6', + '2.0', + '2.1', + '2.2', + '2.3', + '2.4', + '2.5', + '2.6', + '2.7', + '3.0', + '3.1', + '3.2', + '3.3', + '3.4', + '3.5', + '3.6', + '3.7', +}) + + +def update_peps() -> None: + python_releases = load_python_releases() + for version, metadata in python_releases.metadata.items(): + if version in SKIPPED_VERSIONS: + continue + schedules = create_schedules( + version, + python_releases.releases[version], + metadata.start_of_development, + metadata.end_of_bugfix, + ) + update_pep(metadata, schedules) + + +def create_schedules( + version: str, + releases: list[ReleaseInfo], + start_of_development: dt.date, + bugfix_ends: dt.date, +) -> ReleaseSchedules: + schedules: ReleaseSchedules = { + ('feature', 'actual'): [], + ('feature', 'expected'): [], + ('bugfix', 'actual'): [], + ('bugfix', 'expected'): [], + ('security', 'actual'): [], + } + + # first entry into the dictionary + db_state: ReleaseState = 'actual' if TODAY >= start_of_development else 'expected' + schedules['feature', db_state].append( + ReleaseInfo( + stage=f'{version} development begins', + state=db_state, + date=start_of_development, + ) + ) + + for release_info in releases: + if release_info.stage.startswith(f'{version}.0'): + schedules['feature', release_info.state].append(release_info) + elif release_info.date <= bugfix_ends: + schedules['bugfix', release_info.state].append(release_info) + else: + assert release_info.state == 'actual', release_info + schedules['security', release_info.state].append(release_info) + + return schedules + + +def update_pep(metadata: VersionMetadata, schedules: ReleaseSchedules) -> None: + pep_path = PEP_ROOT.joinpath(f'pep-{metadata.pep:0>4}.rst') + pep_lines = iter(pep_path.read_text(encoding='utf-8').splitlines()) + output_lines: list[str] = [] + schedule_name = '' + for line in pep_lines: + output_lines.append(line) + if line.startswith('.. ') and 'schedule' in line: + assert line.startswith('.. release schedule: ') + schedule_name = line.removeprefix('.. release schedule: ') + assert schedule_name in {'feature', 'bugfix', 'security'} + output_lines += generate_schedule_lists( + schedules, + schedule_name=schedule_name, + feature_freeze_date=metadata.feature_freeze, + ) + + # skip source lines until the end of schedule marker + while True: + line = next(pep_lines, None) + if line == '.. release schedule: ends': + output_lines.append(line) + break + if line is None: + raise ValueError('No end of schedule marker found!') + + if not schedule_name: + raise ValueError('No schedule markers found!') + + output_lines.append('') # trailing newline + with open(pep_path, 'wb') as f: + f.write(b'\n'.join(line.encode('utf-8') for line in output_lines)) + + +def generate_schedule_lists( + schedules: ReleaseSchedules, + *, + schedule_name: str, + feature_freeze_date: dt.date = dt.date.min, +) -> Iterator[str]: + state: ReleaseState + for state in 'actual', 'expected': + if not schedules.get((schedule_name, state)): + continue + + yield '' + if schedule_name != 'security': + yield f'{state.title()}:' + yield '' + for release_info in schedules[schedule_name, state]: + yield release_info.schedule_bullet + if release_info.note: + yield f' ({release_info.note})' + if release_info.date == feature_freeze_date: + yield ' (No new features beyond this point.)' + + if schedule_name == 'bugfix': + yield ' (Final regular bugfix release with binary installers)' + + yield '' + + +if __name__ == '__main__': + update_peps() diff --git a/requirements.txt b/requirements.txt index ffcbe1a8aa6..2a94f6db6c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ # Requirements for building PEPs with Sphinx -Pygments >= 2.9.0 +# Sphinx requires >= 2.17. JSON5 support added in 2.19 +Pygments >= 2.19 # Sphinx 6.1.0 broke copying images in parallel builds; fixed in 6.1.2 # See https://github.com/sphinx-doc/sphinx/pull/11100 Sphinx >= 5.1.1, != 6.1.0, != 6.1.1, < 8.1.0 @@ -7,5 +8,8 @@ docutils >= 0.19.0 sphinx-notfound-page >= 1.0.2 # For tests -pytest +pytest>=9 pytest-cov + +# For python-releases.toml +tomli >= 1.1.0 ; python_version < "3.11" diff --git a/tox.ini b/tox.ini index c54968afebe..d31e9b379bb 100644 --- a/tox.ini +++ b/tox.ini @@ -12,3 +12,12 @@ pass_env = FORCE_COLOR commands = python -bb -X dev -W error -m pytest {posargs} + +[coverage:run] +omit = + */__main__.py + peps/* + +[coverage:report] +exclude_also = + if __name__ == .__main__.: