44import requests
55from django import template
66from django .core .cache import cache
7+ from django .utils .html import format_html
8+
9+ from downloads .models import Release
710
811register = template .Library ()
912logger = logging .getLogger (__name__ )
1013
11- PYTHON_RELEASES_URL = "https://peps.python.org/api/python-releases.json"
12- PYTHON_RELEASES_CACHE_KEY = "python_python_releases"
13- PYTHON_RELEASES_CACHE_TIMEOUT = 3600 # 1 hour
14-
15-
16- def get_python_releases_data () -> dict | None :
17- """Fetch and cache the Python release cycle data from PEPs API."""
18- data = cache .get (PYTHON_RELEASES_CACHE_KEY )
19- if data is not None :
20- return data
21-
22- try :
23- response = requests .get (PYTHON_RELEASES_URL , timeout = 5 )
24- response .raise_for_status ()
25- data = response .json ()
26- cache .set (PYTHON_RELEASES_CACHE_KEY , data , PYTHON_RELEASES_CACHE_TIMEOUT )
27- return data
28- except (requests .RequestException , ValueError ) as e :
29- logger .warning ("Failed to fetch release cycle data: %s" , e )
30- return None
14+ RELEASE_CYCLE_URL = "https://peps.python.org/api/release-cycle.json"
15+ RELEASE_CYCLE_CACHE_KEY = "python_release_cycle"
16+ RELEASE_CYCLE_CACHE_TIMEOUT = 3600 # 1 hour
3117
3218
3319@register .simple_tag
@@ -52,14 +38,12 @@ def get_eol_info(release) -> dict:
5238 major = int (match .group (1 ))
5339 minor_version = f"{ match .group (1 )} .{ match .group (2 )} "
5440
55- python_releases = get_python_releases_data ()
56- if python_releases is None :
41+ release_cycle = get_release_cycle_data ()
42+ if release_cycle is None :
5743 # Can't determine EOL status, don't show warning
5844 return result
5945
60- metadata = python_releases .get ("metadata" , {})
61- version_info = metadata .get (minor_version )
62-
46+ version_info = release_cycle .get (minor_version )
6347 if version_info is None :
6448 # Python 2 releases not in the list are EOL
6549 if major <= 2 :
@@ -128,3 +112,73 @@ def sort_windows(files):
128112 other_files .append (file )
129113
130114 return other_files + windows_files
115+
116+
117+ def get_release_cycle_data () -> dict | None :
118+ """Fetch and cache the release cycle data from PEPs API."""
119+ data = cache .get (RELEASE_CYCLE_CACHE_KEY )
120+ if data is not None :
121+ return data
122+
123+ try :
124+ response = requests .get (RELEASE_CYCLE_URL , timeout = 5 )
125+ response .raise_for_status ()
126+ data = response .json ()
127+ cache .set (RELEASE_CYCLE_CACHE_KEY , data , RELEASE_CYCLE_CACHE_TIMEOUT )
128+ return data
129+ except (requests .RequestException , ValueError ) as e :
130+ logger .warning ("Failed to fetch release cycle data: %s" , e )
131+ return None
132+
133+
134+ @register .inclusion_tag ("downloads/active-releases.html" )
135+ def render_active_releases ():
136+ """Render the active Python releases table from PEPs API data."""
137+ releases = []
138+ release_cycle = get_release_cycle_data ()
139+
140+ if release_cycle :
141+ # Sort releases in descending order (newest first)
142+ sorted_releases = sorted (
143+ release_cycle .keys (),
144+ key = lambda v : [int (x ) for x in v .split ("." )],
145+ reverse = True ,
146+ )
147+
148+ found_eol = False
149+ for release in sorted_releases :
150+ info = release_cycle [release ]
151+ status = info .get ("status" , "" )
152+ first_release = info .get ("first_release" , "" )
153+
154+ if status == "feature" and first_release :
155+ first_release = f"{ first_release } (planned)"
156+
157+ if status == "feature" :
158+ status = "pre-release"
159+
160+ if status == "end-of-life" :
161+ # Include only the most recent EOL release
162+ if found_eol :
163+ continue
164+ found_eol = True
165+
166+ # Get last release for EOL versions
167+ minor = int (release .split ("." )[1 ])
168+ last_release = Release .objects .latest_python3 (minor )
169+ if last_release :
170+ status = format_html (
171+ 'end-of-life, last release was <a href="{}">{}</a>' ,
172+ last_release .get_absolute_url (),
173+ last_release .get_version (),
174+ )
175+
176+ releases .append ({
177+ "version" : release ,
178+ "status" : status ,
179+ "first_release" : first_release ,
180+ "end_of_life" : info .get ("end_of_life" , "" ),
181+ "pep" : info .get ("pep" ),
182+ })
183+
184+ return {"releases" : releases }
0 commit comments