Skip to content

Commit e6c06b6

Browse files
authored
Merge branch 'main' into nojs-notice-reword
2 parents e1b0229 + 9bb0af6 commit e6c06b6

32 files changed

Lines changed: 414 additions & 10948 deletions

.github/workflows/ci.yml

Lines changed: 63 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,46 @@
11
name: CI
2-
on: [push, pull_request]
2+
3+
on: [push, pull_request, workflow_dispatch]
4+
35
jobs:
6+
migrations:
7+
if: github.event_name != 'push' || github.event.repository.fork == true || github.ref == 'refs/heads/main'
8+
runs-on: ubuntu-latest
9+
services:
10+
postgres:
11+
image: postgres:15.3
12+
env:
13+
POSTGRES_USER: postgres
14+
POSTGRES_PASSWORD: postgres
15+
POSTGRES_DB: pythonorg
16+
ports:
17+
- 5432:5432
18+
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
19+
20+
steps:
21+
- uses: actions/checkout@v6
22+
23+
- uses: actions/setup-python@v6
24+
with:
25+
python-version-file: '.python-version'
26+
27+
- name: Cache pip
28+
uses: actions/cache@v5
29+
with:
30+
path: ~/.cache/pip
31+
key: ${{ runner.os }}-pip-${{ hashFiles('*-requirements.txt') }}
32+
restore-keys: ${{ runner.os }}-pip-
33+
34+
- name: Install dependencies
35+
run: pip install -r dev-requirements.txt
36+
37+
- name: Check for ungenerated migrations
38+
run: python manage.py makemigrations --check --dry-run
39+
env:
40+
DATABASE_URL: postgres://postgres:postgres@localhost:5432/pythonorg
41+
442
test:
5-
# Avoid running CI more than once on pushes to main repo open PRs
6-
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
43+
if: github.event_name != 'push' || github.event.repository.fork == true || github.ref == 'refs/heads/main'
744
runs-on: ubuntu-latest
845
services:
946
postgres:
@@ -13,52 +50,46 @@ jobs:
1350
POSTGRES_PASSWORD: postgres
1451
POSTGRES_DB: pythonorg
1552
ports:
16-
- 5432:5432
17-
# needed because the postgres container does not provide a healthcheck
53+
- 5432:5432
1854
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
55+
1956
steps:
20-
- name: Check out repository
21-
uses: actions/checkout@v6
57+
- uses: actions/checkout@v6
58+
2259
- name: Install platform dependencies
2360
run: |
2461
sudo apt -y update
2562
sudo apt -y install --no-install-recommends \
26-
texlive-latex-base \
27-
texlive-latex-recommended \
28-
texlive-plain-generic \
29-
lmodern
63+
texlive-latex-base \
64+
texlive-latex-recommended \
65+
texlive-plain-generic \
66+
lmodern
67+
3068
- name: Install pandoc
3169
run: |
3270
wget https://github.com/jgm/pandoc/releases/download/2.17.1.1/pandoc-2.17.1.1-1-amd64.deb
3371
sudo dpkg -i pandoc-2.17.1.1-1-amd64.deb
72+
3473
- uses: actions/setup-python@v6
3574
with:
36-
python-version-file: '.python-version'
37-
- name: Cache Python dependencies
75+
python-version-file: '.python-version'
76+
77+
- name: Cache pip
3878
uses: actions/cache@v5
39-
env:
40-
cache-name: pythondotorg-cache-pip
4179
with:
4280
path: ~/.cache/pip
43-
key: ${{ runner.os }}-${{ github.job }}-${{ env.cache-name }}-${{ hashFiles('requirements.txt', '*-requirements.txt') }}
44-
restore-keys: |
45-
${{ runner.os }}-${{ github.job }}-${{ env.cache-name }}-
46-
${{ runner.os }}-${{ github.job }}-
47-
${{ runner.os }}-
48-
- name: Install Python dependencies
81+
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt', '*-requirements.txt') }}
82+
restore-keys: ${{ runner.os }}-pip-
83+
84+
- name: Install dependencies
4985
run: |
5086
pip install -U pip setuptools wheel
5187
pip install -r dev-requirements.txt
52-
- name: Check for ungenerated database migrations
53-
run: |
54-
python manage.py makemigrations --check --dry-run
55-
env:
56-
DATABASE_URL: postgres://postgres:postgres@localhost:5432/pythonorg
57-
- name: Run Tests
58-
run: |
59-
python -Wd -m coverage run manage.py test -v2
88+
89+
- name: Run tests
90+
run: python -Wd -m coverage run manage.py test -v2
6091
env:
6192
DATABASE_URL: postgres://postgres:postgres@localhost:5432/pythonorg
62-
- name: Coverage
63-
run: |
64-
coverage report -m --fail-under=75
93+
94+
- name: Check coverage
95+
run: coverage report -m --fail-under=75

.github/workflows/static.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
name: Check collectstatic
2-
on: [push, pull_request]
2+
3+
on: [push, pull_request, workflow_dispatch]
4+
35
jobs:
46
collectstatic:
5-
# Avoid running CI more than once on pushes to main repo open PRs
6-
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
7+
if: github.event_name != 'push' || github.event.repository.fork == true || github.ref == 'refs/heads/main'
78
runs-on: ubuntu-latest
89
steps:
910
- name: Check out repository
1011
uses: actions/checkout@v6
12+
1113
- uses: actions/setup-python@v6
1214
with:
1315
python-version-file: '.python-version'

downloads/templatetags/download_tags.py

Lines changed: 89 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,16 @@
44
import requests
55
from django import template
66
from django.core.cache import cache
7+
from django.utils.html import format_html
8+
9+
from downloads.models import Release
710

811
register = template.Library()
912
logger = 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:
@@ -96,6 +80,16 @@ def has_sbom(files):
9680
return any(f.sbom_spdx2_file for f in files)
9781

9882

83+
@register.filter
84+
def has_md5(files):
85+
return any(f.md5_sum for f in files)
86+
87+
88+
@register.filter
89+
def has_sha256(files):
90+
return any(f.sha256_sum for f in files)
91+
92+
9993
@register.filter
10094
def sort_windows(files):
10195
if not files:
@@ -128,3 +122,73 @@ def sort_windows(files):
128122
other_files.append(file)
129123

130124
return other_files + windows_files
125+
126+
127+
def get_release_cycle_data() -> dict | None:
128+
"""Fetch and cache the release cycle data from PEPs API."""
129+
data = cache.get(RELEASE_CYCLE_CACHE_KEY)
130+
if data is not None:
131+
return data
132+
133+
try:
134+
response = requests.get(RELEASE_CYCLE_URL, timeout=5)
135+
response.raise_for_status()
136+
data = response.json()
137+
cache.set(RELEASE_CYCLE_CACHE_KEY, data, RELEASE_CYCLE_CACHE_TIMEOUT)
138+
return data
139+
except (requests.RequestException, ValueError) as e:
140+
logger.warning("Failed to fetch release cycle data: %s", e)
141+
return None
142+
143+
144+
@register.inclusion_tag("downloads/active-releases.html")
145+
def render_active_releases():
146+
"""Render the active Python releases table from PEPs API data."""
147+
releases = []
148+
release_cycle = get_release_cycle_data()
149+
150+
if release_cycle:
151+
# Sort releases in descending order (newest first)
152+
sorted_releases = sorted(
153+
release_cycle.keys(),
154+
key=lambda v: [int(x) for x in v.split(".")],
155+
reverse=True,
156+
)
157+
158+
found_eol = False
159+
for release in sorted_releases:
160+
info = release_cycle[release]
161+
status = info.get("status", "")
162+
first_release = info.get("first_release", "")
163+
164+
if status == "feature" and first_release:
165+
first_release = f"{first_release} (planned)"
166+
167+
if status == "feature":
168+
status = "pre-release"
169+
170+
if status == "end-of-life":
171+
# Include only the most recent EOL release
172+
if found_eol:
173+
continue
174+
found_eol = True
175+
176+
# Get last release for EOL versions
177+
minor = int(release.split(".")[1])
178+
last_release = Release.objects.latest_python3(minor)
179+
if last_release:
180+
status = format_html(
181+
'end-of-life, last release was <a href="{}">{}</a>',
182+
last_release.get_absolute_url(),
183+
last_release.get_version(),
184+
)
185+
186+
releases.append({
187+
"version": release,
188+
"status": status,
189+
"first_release": first_release,
190+
"end_of_life": info.get("end_of_life", ""),
191+
"pep": info.get("pep"),
192+
})
193+
194+
return {"releases": releases}

0 commit comments

Comments
 (0)