Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions release.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import datetime
import glob
import hashlib
import json
import optparse
import os
import re
Expand All @@ -31,6 +32,7 @@
Self,
overload,
)
from urllib.request import urlopen

COMMASPACE = ", "
SPACE = " "
Expand Down Expand Up @@ -68,6 +70,11 @@ def get(
@overload
def get(self, key: Literal["sign_gpg"], default: bool | None = None) -> bool: ...

@overload
def get(
self, key: Literal["security_release"], default: bool | None = None
) -> bool: ...

@overload
def get(self, key: Literal["release"], default: Tag | None = None) -> Tag: ...

Expand Down Expand Up @@ -95,6 +102,9 @@ def __getitem__(self, key: Literal["ssh_key"]) -> str | None: ...
@overload
def __getitem__(self, key: Literal["sign_gpg"]) -> bool: ...

@overload
def __getitem__(self, key: Literal["security_release"]) -> bool: ...

@overload
def __getitem__(self, key: Literal["release"]) -> Tag: ...

Expand Down Expand Up @@ -124,6 +134,9 @@ def __setitem__(self, key: Literal["ssh_key"], value: str | None) -> None: ...
@overload
def __setitem__(self, key: Literal["sign_gpg"], value: bool) -> None: ...

@overload
def __setitem__(self, key: Literal["security_release"], value: bool) -> None: ...

@overload
def __setitem__(self, key: Literal["release"], value: Tag) -> None: ...

Expand Down Expand Up @@ -193,6 +206,13 @@ def is_release_candidate(self) -> bool:
def is_feature_freeze_release(self) -> bool:
return self.level == "b" and self.serial == 1

@property
def is_security_release(self) -> bool:
url = "https://peps.python.org/api/release-cycle.json"
with urlopen(url) as response:
data = json.loads(response.read())
return str(data[self.basic_version]["status"]) == "security"

@property
def nickname(self) -> str:
return self.text.replace(".", "")
Expand Down
31 changes: 22 additions & 9 deletions run_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,10 @@ def __init__(
self.db["ssh_key"] = ssh_key
if not self.db.get("sign_gpg"):
self.db["sign_gpg"] = sign_gpg

if not self.db.get("release"):
self.db["release"] = release_tag
if not self.db.get("security_release"):
self.db["security_release"] = self.db["release"].is_security_release

print("Release data: ")
print(f"- Branch: {release_tag.branch}")
Expand All @@ -260,6 +261,7 @@ def __init__(
print(f"- SSH username: {self.db['ssh_user']}")
print(f"- SSH key: {self.db['ssh_key'] or 'Default'}")
print(f"- Sign with GPG: {self.db['sign_gpg']}")
print(f"- Security release: {self.db['security_release']}")
print()

def checkpoint(self) -> None:
Expand Down Expand Up @@ -1000,18 +1002,29 @@ def wait_until_all_files_are_in_folder(db: ReleaseShelf) -> None:
are_windows_files_there = f"python-{release}.exe" in all_files
are_macos_files_there = f"python-{release}-macos11.pkg" in all_files
are_linux_files_there = f"Python-{release}.tgz" in all_files
are_all_files_there = (
are_linux_files_there and are_windows_files_there and are_macos_files_there
)

if db["security_release"]:
# For security releases, only check Linux files
are_all_files_there = are_linux_files_there
else:
# For regular releases, check all platforms
are_all_files_there = (
are_linux_files_there
and are_windows_files_there
and are_macos_files_there
)

if not are_all_files_there:
linux_tick = "✅" if are_linux_files_there else "❌"
windows_tick = "✅" if are_windows_files_there else "❌"
macos_tick = "✅" if are_macos_files_there else "❌"
print(
f"\rWaiting for files: Linux {linux_tick} Windows {windows_tick} Mac {macos_tick} ",
flush=True,
end="",
)

if db["security_release"]:
waiting = f"\rWaiting for files: Linux {linux_tick} (security release - only checking Linux)"
else:
waiting = f"\rWaiting for files: Linux {linux_tick} Windows {windows_tick} Mac {macos_tick} "

print(waiting, flush=True, end="")
time.sleep(1)
print()

Expand Down
31 changes: 31 additions & 0 deletions tests/test_release_tag.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import io
from subprocess import CompletedProcess

import pytest
Expand Down Expand Up @@ -140,3 +141,33 @@ def test_tag_long_name() -> None:
assert rc.long_name == "3.13.0 release candidate 3"
assert final_zero.long_name == "3.13.0"
assert final_3.long_name == "3.13.3"


@pytest.mark.parametrize(
["version", "expected"],
[
("3.12.10", True),
("3.13.3", False),
],
)
def test_tag_is_security_release(
version: str, expected: str, mocker: MockerFixture
) -> None:
# Arrange
mock_response = b"""
{
"3.13": {
"status": "bugfix"
},
"3.12": {
"status": "security"
}
}
"""
mocker.patch("urllib.request.urlopen", return_value=io.BytesIO(mock_response))

# Act
tag = release.Tag(version)

# Assert
assert tag.is_security_release is expected