diff --git a/.gitignore b/.gitignore
index df4dc9415a..b712492014 100644
--- a/.gitignore
+++ b/.gitignore
@@ -91,3 +91,4 @@ celerybeat-schedule
include/branches.csv
include/end-of-life.csv
include/release-cycle.svg
+include/release-cycle-all.svg
diff --git a/Makefile b/Makefile
index 5a33d50897..6baf33b325 100644
--- a/Makefile
+++ b/Makefile
@@ -22,6 +22,7 @@ REQUIREMENTS = requirements.txt
_ALL_SPHINX_OPTS = --jobs $(JOBS) $(SPHINXOPTS)
_RELEASE_CYCLE = include/branches.csv \
include/end-of-life.csv \
+ include/release-cycle-all.svg \
include/release-cycle.svg
.PHONY: help
diff --git a/_static/devguide_overrides.css b/_static/devguide_overrides.css
index 8e2c7c6fca..a048e1c360 100644
--- a/_static/devguide_overrides.css
+++ b/_static/devguide_overrides.css
@@ -48,35 +48,66 @@
fill: white;
}
-.release-cycle-chart .release-cycle-blob-label.release-cycle-blob-security,
-.release-cycle-chart .release-cycle-blob-label.release-cycle-blob-bugfix {
+.release-cycle-chart .release-cycle-blob-label.release-cycle-status-security,
+.release-cycle-chart .release-cycle-blob-label.release-cycle-status-bugfix {
/* but use black to improve contrast for lighter backgrounds */
fill: black;
}
-.release-cycle-chart .release-cycle-blob.release-cycle-blob-end-of-life {
- fill: #DD2200;
- stroke: #FF8888;
+.release-cycle-chart .release-cycle-blob-label.release-cycle-status-end-of-life,
+.release-cycle-chart .release-cycle-blob-label.release-cycle-status-feature {
+ /* and FG when it's not in a blob */
+ fill: var(--color-foreground-primary);
+}
+
+.release-cycle-chart .release-cycle-status-end-of-life {
+ --status-bg-color: #DD2200;
+ --status-border-color: #FF8888;
+}
+
+.release-cycle-chart .release-cycle-status-security {
+ --status-bg-color: #FFDD44;
+ --status-border-color: #FF8800;
+}
+
+.release-cycle-chart .release-cycle-status-bugfix {
+ --status-bg-color: #00DD22;
+ --status-border-color: #008844;
+}
+
+.release-cycle-chart .release-cycle-status-prerelease {
+ --status-bg-color: teal;
+ --status-border-color: darkgreen;
}
-.release-cycle-chart .release-cycle-blob.release-cycle-blob-security {
- fill: #FFDD44;
- stroke: #FF8800;
+.release-cycle-chart .release-cycle-status-feature {
+ --status-bg-color: #2222EE;
+ --status-border-color: #008888;
}
-.release-cycle-chart .release-cycle-blob.release-cycle-blob-bugfix {
- fill: #00DD22;
- stroke: #008844;
+.release-cycle-chart .release-cycle-blob {
+ fill: var(--status-bg-color);
+ stroke: transparent;
}
-.release-cycle-chart .release-cycle-blob.release-cycle-blob-prerelease {
- fill: teal;
- stroke: darkgreen;
+.release-cycle-chart .release-cycle-border {
+ fill: transparent;
+ stroke: var(--status-border-color);
+ stroke-width: 1.6px;
}
-.release-cycle-chart .release-cycle-blob.release-cycle-blob-feature {
- fill: #2222EE;
- stroke: #008888;
+.release-cycle-chart .release-cycle-shade {
+ fill: transparent;
+ stroke: transparent;
+
+ &.release-cycle-status-end-of-life {
+ fill: #DD2200;
+ stroke: #FF8888;
+ }
+ &.release-cycle-status-feature {
+ fill: var(--color-background-primary);
+ opacity: 50%;
+ }
}
.good pre {
diff --git a/_tools/generate_release_cycle.py b/_tools/generate_release_cycle.py
index 3a8fefec02..0c401ebbef 100644
--- a/_tools/generate_release_cycle.py
+++ b/_tools/generate_release_cycle.py
@@ -28,21 +28,52 @@ def parse_date(date_str: str) -> dt.date:
class Versions:
"""For converting JSON to CSV and SVG."""
- def __init__(self) -> None:
+ def __init__(self, limit_to_active=False, special_py27=False) -> None:
with open("include/release-cycle.json", encoding="UTF-8") as in_file:
self.versions = json.load(in_file)
# Generate a few additional fields
for key, version in self.versions.items():
version["key"] = key
- version["first_release_date"] = parse_date(version["first_release"])
+ version["first_release_date"] = r1 = parse_date(version["first_release"])
+ version["start_security_date"] = r1 + dt.timedelta(days=2 * 365)
version["end_of_life_date"] = parse_date(version["end_of_life"])
+
+ self.cutoff = min(ver["first_release_date"] for ver in self.versions.values())
+
+ if limit_to_active:
+ self.cutoff = min(
+ version["first_release_date"]
+ for version in self.versions.values()
+ if version["status"] != 'end-of-life'
+ )
+ self.versions = {
+ key: version
+ for key, version in self.versions.items()
+ if version["end_of_life_date"] >= self.cutoff
+ or (special_py27 and key == '2.7')
+ }
+ if special_py27:
+ self.cutoff = min(self.cutoff, dt.date(2019, 8, 1))
+ self.id_key = 'active'
+ else:
+ self.id_key = 'all'
+
self.sorted_versions = sorted(
self.versions.values(),
key=lambda v: [int(i) for i in v["key"].split(".")],
reverse=True,
)
+ # Set the row (y-coordinate) for the chart, to allow a gap between 2.7
+ # and the rest
+ y = len(self.sorted_versions) + (1 if special_py27 else 0)
+ for version in self.sorted_versions:
+ if special_py27 and version["key"] == '2.7':
+ y -= 1
+ version["y"] = y
+ y -= 1
+
def write_csv(self) -> None:
"""Output CSV files."""
now_str = str(dt.datetime.now(dt.timezone.utc))
@@ -68,7 +99,7 @@ def write_csv(self) -> None:
csv_file.writeheader()
csv_file.writerows(versions.values())
- def write_svg(self, today: str) -> None:
+ def write_svg(self, today: str, out_path: str) -> None:
"""Output SVG file."""
env = jinja2.Environment(
loader=jinja2.FileSystemLoader("_tools/"),
@@ -96,7 +127,7 @@ def write_svg(self, today: str) -> None:
# some positioning numbers in the template as well.
LINE_HEIGHT = 1.5
- first_date = min(ver["first_release_date"] for ver in self.sorted_versions)
+ first_date = self.cutoff
last_date = max(ver["end_of_life_date"] for ver in self.sorted_versions)
def date_to_x(date: dt.date) -> float:
@@ -115,20 +146,21 @@ def format_year(year: int) -> str:
"""Format year number for display"""
return f"'{year % 100:02}"
- with open(
- "include/release-cycle.svg", "w", encoding="UTF-8", newline="\n"
- ) as f:
+ with open(out_path, "w", encoding="UTF-8", newline="\n") as f:
template.stream(
SCALE=SCALE,
diagram_width=DIAGRAM_WIDTH,
- diagram_height=(len(self.sorted_versions) + 2) * LINE_HEIGHT,
+ diagram_height=(self.sorted_versions[0]["y"] + 2) * LINE_HEIGHT,
years=range(first_date.year, last_date.year + 1),
LINE_HEIGHT=LINE_HEIGHT,
+ LEGEND_WIDTH=LEGEND_WIDTH,
+ RIGHT_MARGIN=RIGHT_MARGIN,
versions=list(reversed(self.sorted_versions)),
today=dt.datetime.strptime(today, "%Y-%m-%d").date(),
year_to_x=year_to_x,
date_to_x=date_to_x,
format_year=format_year,
+ id_key=self.id_key,
).dump(f)
@@ -145,8 +177,13 @@ def main() -> None:
args = parser.parse_args()
versions = Versions()
+ print(versions.versions.keys())
+ assert len(versions.versions) > 10
versions.write_csv()
- versions.write_svg(args.today)
+ versions.write_svg(args.today, "include/release-cycle-all.svg")
+
+ versions = Versions(limit_to_active=True, special_py27=True)
+ versions.write_svg(args.today, "include/release-cycle.svg")
if __name__ == "__main__":
diff --git a/_tools/release_cycle_template.svg.jinja b/_tools/release_cycle_template.svg.jinja
index 5d39d307a5..b39d425f8e 100644
--- a/_tools/release_cycle_template.svg.jinja
+++ b/_tools/release_cycle_template.svg.jinja
@@ -4,11 +4,17 @@
class="release-cycle-chart"
viewBox="0 0 {{ diagram_width * SCALE }} {{ diagram_height * SCALE }}"
>
+
+
+
+
+
+
{% for version in versions %}
- {% set y = loop.index * LINE_HEIGHT %}
+ {% set y = version.y * LINE_HEIGHT %}
- {% if loop.index % 2 %}
+ {% if version.y % 2 %}
+
+
+
+
+
-
-
- Python {{ version.key }}
-
+ {% for version in versions %}
+ {% set y = version.y * LINE_HEIGHT %}
{% set start_x = date_to_x(version.first_release_date) %}
{% set end_x = date_to_x(version.end_of_life_date) %}
- {% set mid_x = (start_x + end_x) / 2 %}
+
+
+ {% set half_x = [end_x, date_to_x(version.start_security_date)]|min %}
+ {% set height = 1.25 * SCALE %}
+ {% set left_width = (half_x - start_x) * SCALE %}
+ {% set right_width = (end_x - half_x) * SCALE %}
+ {% set left_x = start_x * SCALE %}
+ {% set middle_x = half_x * SCALE %}
+ {% set right_x = half_x * SCALE %}
+ {% set recty = (y - 1) * SCALE %}
+ {% set radius_value = 0.25 * SCALE %}
+
+ {% if version.status != "end-of-life" %}
+
+
+
+ {% endif %}
+
+ {% if version.status == "bugfix" %}
+
+ bugfix
+
+ {% elif version.status == "security" %}
+
+ security
+
+ {% elif version.status == "end-of-life" %}
+
+ end-of-life
+
+ {% else %}
+
+ {{ version.status }}
+
+ {% endif %}
+
+
- {{ version.status }}
+ Python {{ version.key }}
{% endfor %}
diff --git a/make.ps1 b/make.ps1
index 71a8f56f4c..4cdc6b20cf 100644
--- a/make.ps1
+++ b/make.ps1
@@ -64,7 +64,8 @@ if ($target -Eq "clean") {
$ToClean = @(
$BUILDDIR,
$_VENV_DIR,
- "include/branches.csv", "include/end-of-life.csv", "include/release-cycle.svg"
+ "include/branches.csv", "include/end-of-life.csv",
+ "include/release-cycle.svg", "include/release-cycle-all.svg"
)
foreach ($item in $ToClean) {
if (Test-Path -Path $item) {
diff --git a/versions.rst b/versions.rst
index db7f946829..d6aac12b3d 100644
--- a/versions.rst
+++ b/versions.rst
@@ -10,13 +10,12 @@ branch that accepts new features. The latest release for each Python
version can be found on the `download page `_.
-Python release cycle
-====================
-
.. raw:: html
:file: include/release-cycle.svg
-Another useful visualization is `endoflife.date/python `_.
+(See :ref:`below ` for a chart with older versions.
+Another useful visualization is `endoflife.date/python `_.)
+
Supported versions
==================
@@ -40,6 +39,15 @@ Unsupported versions
:file: include/end-of-life.csv
+.. _versions-chart-all:
+
+Full chart
+==========
+
+.. raw:: html
+ :file: include/release-cycle-all.svg
+
+
Status key
==========