-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Add python-releases.toml #4331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
AA-Turner
wants to merge
16
commits into
python:main
Choose a base branch
from
AA-Turner:release-toml
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Add python-releases.toml #4331
Changes from 1 commit
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
13bf22c
Add python-releases.toml
AA-Turner 813ac0d
Add initial scripts
AA-Turner 1615b1d
Add release schedule markers
AA-Turner 3414c2e
Integrate into the build process
AA-Turner d81a00d
Regenerate release schedules
AA-Turner 27be901
Standardise versions
AA-Turner 7836259
Regenerate release schedules
AA-Turner 9a70c0a
Temporary workaround for tests on 3.9 & 3.10
AA-Turner 2207db2
Add 1.6.1
AA-Turner 674eba1
Standardise versions
AA-Turner 3dc6277
Add explicit licence (Public Domain or CC0)
AA-Turner a72c1f9
Initial review notes
AA-Turner e4f42ac
Update for 3.15, from PEP 790
AA-Turner 73cf41e
Merge remote-tracking branch 'upstream/main' into release-toml
AA-Turner 7b18f8d
fixup! Update for 3.15, from PEP 790
AA-Turner 1f1c73d
8/4/25 releases
AA-Turner File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add initial scripts
- Loading branch information
commit 813ac0d2eaa1a43f5a80a3a5d2bed9dff830decc
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
from __future__ import annotations | ||
|
||
import dataclasses | ||
import datetime as dt | ||
import tomllib | ||
from pathlib import Path | ||
|
||
TYPE_CHECKING = False | ||
if TYPE_CHECKING: | ||
from typing import Literal, TypeAlias | ||
|
||
ReleaseState: TypeAlias = Literal['actual', 'expected'] | ||
ReleaseSchedules: TypeAlias = dict[tuple[str, ReleaseState], list['ReleaseInfo']] | ||
|
||
RELEASE_DIR = Path(__file__).resolve().parent | ||
ROOT_DIR = RELEASE_DIR.parent | ||
PEP_ROOT = ROOT_DIR / 'peps' | ||
|
||
|
||
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True) | ||
class PythonReleases: | ||
metadata: dict[str, 'VersionMetadata'] | ||
releases: dict[str, list['ReleaseInfo']] | ||
|
||
|
||
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True) | ||
class VersionMetadata: | ||
"""Metadata for a given interpreter version (MAJOR.MINOR).""" | ||
|
||
pep: int | ||
status: str | ||
branch: str | ||
release_manager: str | ||
start_of_development: dt.date | ||
first_release: dt.date | ||
feature_freeze: 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()}) | ||
|
||
|
||
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True) | ||
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,21 @@ | ||||
from __future__ import annotations | ||||
|
||||
Comment on lines
+1
to
+2
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
nit: this isn't needed |
||||
import argparse | ||||
|
||||
CMD_UPDATE_PEPS = 'update-peps' | ||||
CMD_RELEASE_CYCLE = 'release-cycle' | ||||
|
||||
PARSER = argparse.ArgumentParser(allow_abbrev=False) | ||||
PARSER.add_argument('COMMAND', choices=[CMD_UPDATE_PEPS, CMD_RELEASE_CYCLE]) | ||||
|
||||
args = PARSER.parse_args() | ||||
if args.COMMAND == CMD_UPDATE_PEPS: | ||||
from release_engineering.update_release_schedules import update_peps | ||||
|
||||
update_peps() | ||||
elif args.COMMAND == CMD_RELEASE_CYCLE: | ||||
from pathlib import Path | ||||
|
||||
from release_engineering.generate_release_cycle import create_release_cycle | ||||
|
||||
Path('release-cycle.json').write_text(create_release_cycle(), encoding='utf-8') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,41 @@ | ||||||
from __future__ import annotations | ||||||
|
||||||
import json | ||||||
|
||||||
from release_engineering import load_python_releases | ||||||
|
||||||
TYPE_CHECKING = False | ||||||
if TYPE_CHECKING: | ||||||
from release_engineering import VersionMetadata | ||||||
|
||||||
|
||||||
def create_release_cycle() -> str: | ||||||
metadata = load_python_releases().metadata | ||||||
versions = [v for v in metadata if version_to_tuple(v) >= (2, 6)] | ||||||
versions.sort(key=version_to_tuple, reverse=True) | ||||||
if '2.7' in versions: | ||||||
versions.remove('2.7') | ||||||
versions.insert(versions.index('3.1'), '2.7') | ||||||
|
||||||
release_cycle = {version: version_info(metadata[version]) for version in versions} | ||||||
return ( | ||||||
json.dumps(release_cycle, indent=2, sort_keys=False, ensure_ascii=False) + '\n' | ||||||
) | ||||||
|
||||||
|
||||||
def version_to_tuple(version: str, /) -> tuple[int, ...]: | ||||||
return tuple(map(int, version.split('.'))) | ||||||
|
||||||
|
||||||
def version_info(metadata: VersionMetadata, /) -> dict[str, str]: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
PEP ID is an |
||||||
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, | ||||||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
"""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: | ||
|
||
.. feature release schedule | ||
.. bugfix release schedule | ||
.. security release schedule | ||
.. end of schedule | ||
|
||
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_engineering update-peps | ||
$ # or | ||
$ make regen-all | ||
""" | ||
|
||
from __future__ import annotations | ||
|
||
import datetime as dt | ||
|
||
from release_engineering import ( | ||
PEP_ROOT, | ||
ReleaseInfo, | ||
VersionMetadata, | ||
load_python_releases, | ||
) | ||
|
||
TYPE_CHECKING = False | ||
if TYPE_CHECKING: | ||
from collections.abc import Iterator | ||
|
||
from release_engineering import ReleaseSchedules, ReleaseState, VersionMetadata | ||
|
||
TODAY = dt.date.today() | ||
|
||
VERSIONS_TO_REGENERATE = ( | ||
AA-Turner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
'3.8', | ||
'3.9', | ||
'3.10', | ||
'3.11', | ||
'3.12', | ||
'3.13', | ||
'3.14', | ||
) | ||
|
||
|
||
def update_peps() -> None: | ||
python_releases = load_python_releases() | ||
for version in VERSIONS_TO_REGENERATE: | ||
metadata = python_releases.metadata[version] | ||
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: | ||
schedule_name = line.split()[1] | ||
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 == '.. end of schedule': | ||
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!') | ||
|
||
with open(pep_path, 'w', encoding='utf-8') as f: | ||
f.write('\n'.join(output_lines)) | ||
f.write('\n') # trailing newline | ||
|
||
|
||
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() |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: with
from future import annotations
we can simplify here