Skip to content

Commit 07454ed

Browse files
nedbatencukouhugovk
authored
"Status of Python versions" page: more details in charts, more descriptive text (#1531)
* Python version status: show when bugfix releases become security releases * Make them look all the same * Show EOL ones as red * Two graphs * Remove unacceptable newline * Join an unacceptably split line * Hide the starts of EOL versions * Special-case 2.7 * Remove 3.5 * Format * Put labels on top, if the mask doesn't work * simplify the svg paths * Add more explanation to the Python versions Status key * clarify what a feature fix is. * Update _tools/generate_release_cycle.py Co-authored-by: Hugo van Kemenade <[email protected]> * Update _tools/generate_release_cycle.py Co-authored-by: Hugo van Kemenade <[email protected]> * Update _tools/generate_release_cycle.py Co-authored-by: Hugo van Kemenade <[email protected]> * Update _tools/generate_release_cycle.py Co-authored-by: Hugo van Kemenade <[email protected]> * Update _tools/generate_release_cycle.py Co-authored-by: Hugo van Kemenade <[email protected]> * Apply suggestions from code review Co-authored-by: Hugo van Kemenade <[email protected]> * 3.13 gets two years of bug fixes, earlier gets 1.5 years * last changes from the review * use a consistent anchor for the full chart * more tweaking of the phase descriptions * Use `height` consistently * Clean up the path code * Remove the "shade" rectangle, keep left+right+border or a single rect * Use the same radius everywhere * Rearrange comments/assignments * No mask for active branches * Restore bold red color * Update _tools/release_cycle_template.svg.jinja --------- Co-authored-by: Petr Viktorin <[email protected]> Co-authored-by: Hugo van Kemenade <[email protected]>
1 parent 85eda4a commit 07454ed

7 files changed

+280
-86
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,4 @@ celerybeat-schedule
9191
include/branches.csv
9292
include/end-of-life.csv
9393
include/release-cycle.svg
94+
include/release-cycle-all.svg

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ REQUIREMENTS = requirements.txt
2222
_ALL_SPHINX_OPTS = --jobs $(JOBS) $(SPHINXOPTS)
2323
_RELEASE_CYCLE = include/branches.csv \
2424
include/end-of-life.csv \
25+
include/release-cycle-all.svg \
2526
include/release-cycle.svg
2627

2728
.PHONY: help

_static/devguide_overrides.css

+39-17
Original file line numberDiff line numberDiff line change
@@ -48,35 +48,57 @@
4848
fill: white;
4949
}
5050

51-
.release-cycle-chart .release-cycle-blob-label.release-cycle-blob-security,
52-
.release-cycle-chart .release-cycle-blob-label.release-cycle-blob-bugfix {
51+
.release-cycle-chart .release-cycle-blob-label.release-cycle-status-security,
52+
.release-cycle-chart .release-cycle-blob-label.release-cycle-status-bugfix {
5353
/* but use black to improve contrast for lighter backgrounds */
5454
fill: black;
5555
}
5656

57-
.release-cycle-chart .release-cycle-blob.release-cycle-blob-end-of-life {
58-
fill: #DD2200;
59-
stroke: #FF8888;
57+
.release-cycle-chart .release-cycle-blob-label.release-cycle-status-end-of-life,
58+
.release-cycle-chart .release-cycle-blob-label.release-cycle-status-feature {
59+
/* and FG when it's not in a blob */
60+
fill: var(--color-foreground-primary);
61+
}
62+
63+
.release-cycle-chart .release-cycle-status-end-of-life {
64+
--status-bg-color: #DD2200;
65+
--status-border-color: #FF8888;
6066
}
6167

62-
.release-cycle-chart .release-cycle-blob.release-cycle-blob-security {
63-
fill: #FFDD44;
64-
stroke: #FF8800;
68+
.release-cycle-chart .release-cycle-status-security {
69+
--status-bg-color: #FFDD44;
70+
--status-border-color: #FF8800;
6571
}
6672

67-
.release-cycle-chart .release-cycle-blob.release-cycle-blob-bugfix {
68-
fill: #00DD22;
69-
stroke: #008844;
73+
.release-cycle-chart .release-cycle-status-bugfix {
74+
--status-bg-color: #00DD22;
75+
--status-border-color: #008844;
7076
}
7177

72-
.release-cycle-chart .release-cycle-blob.release-cycle-blob-prerelease {
73-
fill: teal;
74-
stroke: darkgreen;
78+
.release-cycle-chart .release-cycle-status-prerelease {
79+
--status-bg-color: teal;
80+
--status-border-color: darkgreen;
7581
}
7682

77-
.release-cycle-chart .release-cycle-blob.release-cycle-blob-feature {
78-
fill: #2222EE;
79-
stroke: #008888;
83+
.release-cycle-chart .release-cycle-status-feature {
84+
--status-bg-color: #2222EE;
85+
--status-border-color: #008888;
86+
}
87+
88+
.release-cycle-chart .release-cycle-blob {
89+
fill: var(--status-bg-color);
90+
stroke: transparent;
91+
}
92+
93+
.release-cycle-chart .release-cycle-blob-full {
94+
fill: var(--status-bg-color);
95+
stroke: var(--status-border-color);
96+
}
97+
98+
.release-cycle-chart .release-cycle-border {
99+
fill: transparent;
100+
stroke: var(--status-border-color);
101+
stroke-width: 1.6px;
80102
}
81103

82104
.good pre {

_tools/generate_release_cycle.py

+60-13
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,64 @@ def parse_date(date_str: str) -> dt.date:
2525
return dt.date.fromisoformat(date_str)
2626

2727

28+
def parse_version(ver: str) -> list[int]:
29+
return [int(i) for i in ver["key"].split(".")]
30+
31+
2832
class Versions:
2933
"""For converting JSON to CSV and SVG."""
3034

31-
def __init__(self) -> None:
35+
def __init__(self, *, limit_to_active=False, special_py27=False) -> None:
3236
with open("include/release-cycle.json", encoding="UTF-8") as in_file:
3337
self.versions = json.load(in_file)
3438

3539
# Generate a few additional fields
3640
for key, version in self.versions.items():
3741
version["key"] = key
38-
version["first_release_date"] = parse_date(version["first_release"])
42+
ver_info = parse_version(version)
43+
if ver_info >= [3, 13]:
44+
full_years = 2
45+
else:
46+
full_years = 1.5
47+
version["first_release_date"] = r1 = parse_date(version["first_release"])
48+
version["start_security_date"] = r1 + dt.timedelta(days=full_years * 365)
3949
version["end_of_life_date"] = parse_date(version["end_of_life"])
50+
51+
self.cutoff = min(ver["first_release_date"] for ver in self.versions.values())
52+
53+
if limit_to_active:
54+
self.cutoff = min(
55+
version["first_release_date"]
56+
for version in self.versions.values()
57+
if version["status"] != "end-of-life"
58+
)
59+
self.versions = {
60+
key: version
61+
for key, version in self.versions.items()
62+
if version["end_of_life_date"] >= self.cutoff
63+
or (special_py27 and key == "2.7")
64+
}
65+
if special_py27:
66+
self.cutoff = min(self.cutoff, dt.date(2019, 8, 1))
67+
self.id_key = "active"
68+
else:
69+
self.id_key = "all"
70+
4071
self.sorted_versions = sorted(
4172
self.versions.values(),
42-
key=lambda v: [int(i) for i in v["key"].split(".")],
73+
key=parse_version,
4374
reverse=True,
4475
)
4576

77+
# Set the row (Y coordinate) for the chart, to allow a gap between 2.7
78+
# and the rest
79+
y = len(self.sorted_versions) + (1 if special_py27 else 0)
80+
for version in self.sorted_versions:
81+
if special_py27 and version["key"] == "2.7":
82+
y -= 1
83+
version["y"] = y
84+
y -= 1
85+
4686
def write_csv(self) -> None:
4787
"""Output CSV files."""
4888
now_str = str(dt.datetime.now(dt.timezone.utc))
@@ -68,7 +108,7 @@ def write_csv(self) -> None:
68108
csv_file.writeheader()
69109
csv_file.writerows(versions.values())
70110

71-
def write_svg(self, today: str) -> None:
111+
def write_svg(self, today: str, out_path: str) -> None:
72112
"""Output SVG file."""
73113
env = jinja2.Environment(
74114
loader=jinja2.FileSystemLoader("_tools/"),
@@ -85,6 +125,8 @@ def write_svg(self, today: str) -> None:
85125
# CSS.
86126
# (Ideally we'd actually use `em` units, but SVG viewBox doesn't take
87127
# those.)
128+
129+
# Uppercase sizes are un-scaled
88130
SCALE = 18
89131

90132
# Width of the drawing and main parts
@@ -96,7 +138,7 @@ def write_svg(self, today: str) -> None:
96138
# some positioning numbers in the template as well.
97139
LINE_HEIGHT = 1.5
98140

99-
first_date = min(ver["first_release_date"] for ver in self.sorted_versions)
141+
first_date = self.cutoff
100142
last_date = max(ver["end_of_life_date"] for ver in self.sorted_versions)
101143

102144
def date_to_x(date: dt.date) -> float:
@@ -105,7 +147,7 @@ def date_to_x(date: dt.date) -> float:
105147
total_days = (last_date - first_date).days
106148
ratio = num_days / total_days
107149
x = ratio * (DIAGRAM_WIDTH - LEGEND_WIDTH - RIGHT_MARGIN)
108-
return x + LEGEND_WIDTH
150+
return (x + LEGEND_WIDTH) * SCALE
109151

110152
def year_to_x(year: int) -> float:
111153
"""Convert year number to an SVG X coordinate of 1st January"""
@@ -115,20 +157,21 @@ def format_year(year: int) -> str:
115157
"""Format year number for display"""
116158
return f"'{year % 100:02}"
117159

118-
with open(
119-
"include/release-cycle.svg", "w", encoding="UTF-8", newline="\n"
120-
) as f:
160+
with open(out_path, "w", encoding="UTF-8", newline="\n") as f:
121161
template.stream(
122162
SCALE=SCALE,
123-
diagram_width=DIAGRAM_WIDTH,
124-
diagram_height=(len(self.sorted_versions) + 2) * LINE_HEIGHT,
163+
diagram_width=DIAGRAM_WIDTH * SCALE,
164+
diagram_height=(self.sorted_versions[0]["y"] + 2) * LINE_HEIGHT * SCALE,
125165
years=range(first_date.year, last_date.year + 1),
126-
LINE_HEIGHT=LINE_HEIGHT,
166+
line_height=LINE_HEIGHT * SCALE,
167+
legend_width=LEGEND_WIDTH * SCALE,
168+
right_margin=RIGHT_MARGIN * SCALE,
127169
versions=list(reversed(self.sorted_versions)),
128170
today=dt.datetime.strptime(today, "%Y-%m-%d").date(),
129171
year_to_x=year_to_x,
130172
date_to_x=date_to_x,
131173
format_year=format_year,
174+
id_key=self.id_key,
132175
).dump(f)
133176

134177

@@ -145,8 +188,12 @@ def main() -> None:
145188
args = parser.parse_args()
146189

147190
versions = Versions()
191+
assert len(versions.versions) > 10
148192
versions.write_csv()
149-
versions.write_svg(args.today)
193+
versions.write_svg(args.today, "include/release-cycle-all.svg")
194+
195+
versions = Versions(limit_to_active=True, special_py27=True)
196+
versions.write_svg(args.today, "include/release-cycle.svg")
150197

151198

152199
if __name__ == "__main__":

0 commit comments

Comments
 (0)