Skip to content

Commit 63d8277

Browse files
Add workflow for Trusted Publishing (#395)
Co-authored-by: Sebastian Rittau <[email protected]>
1 parent 074d053 commit 63d8277

File tree

5 files changed

+211
-79
lines changed

5 files changed

+211
-79
lines changed

.github/workflows/package.yml

-76
This file was deleted.

.github/workflows/publish.yml

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Based on
2+
# https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/
3+
4+
name: Test builds and publish Python distribution to PyPI
5+
6+
on:
7+
release:
8+
types: [published]
9+
push:
10+
branches: [main]
11+
pull_request:
12+
13+
permissions:
14+
contents: read
15+
16+
concurrency:
17+
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
18+
cancel-in-progress: true
19+
20+
jobs:
21+
build:
22+
name: Build distribution
23+
runs-on: ubuntu-latest
24+
25+
steps:
26+
- uses: actions/checkout@v4
27+
- name: Set up Python
28+
uses: actions/setup-python@v5
29+
with:
30+
python-version: "3.x"
31+
- name: Check package metadata
32+
run: python scripts/check_package.py ${{ github.ref }}
33+
- name: Install pypa/build
34+
run: |
35+
# Be wary of running `pip install` here, since it becomes easy for us to
36+
# accidentally pick up typing_extensions as installed by a dependency
37+
python -m pip install --upgrade build
38+
python -m pip list
39+
- name: Build a binary wheel and a source tarball
40+
run: python -m build
41+
- name: Store the distribution packages
42+
uses: actions/upload-artifact@v4
43+
with:
44+
name: python-package-distributions
45+
path: dist/
46+
47+
test-wheel:
48+
name: Test wheel
49+
needs:
50+
- build
51+
runs-on: ubuntu-latest
52+
53+
steps:
54+
- uses: actions/checkout@v4
55+
- name: Set up Python
56+
uses: actions/setup-python@v5
57+
with:
58+
python-version: "3.x"
59+
- name: Download all the dists
60+
uses: actions/download-artifact@v4
61+
with:
62+
name: python-package-distributions
63+
path: dist/
64+
- name: Install wheel
65+
run: |
66+
export path_to_file=$(find dist -type f -name "typing_extensions-*.whl")
67+
echo "::notice::Installing wheel: $path_to_file"
68+
python -m pip install --user $path_to_file
69+
python -m pip list
70+
- name: Run typing_extensions tests against installed package
71+
run: rm src/typing_extensions.py && python src/test_typing_extensions.py
72+
73+
test-sdist:
74+
name: Test source distribution
75+
needs:
76+
- build
77+
runs-on: ubuntu-latest
78+
79+
steps:
80+
- uses: actions/checkout@v4
81+
- name: Set up Python
82+
uses: actions/setup-python@v5
83+
with:
84+
python-version: "3.x"
85+
- name: Download all the dists
86+
uses: actions/download-artifact@v4
87+
with:
88+
name: python-package-distributions
89+
path: dist/
90+
- name: Unpack and test source distribution
91+
run: |
92+
export path_to_file=$(find dist -type f -name "typing_extensions-*.tar.gz")
93+
echo "::notice::Unpacking source distribution: $path_to_file"
94+
tar xzf $path_to_file -C dist/
95+
cd ${path_to_file%.tar.gz}
96+
python src/test_typing_extensions.py
97+
98+
test-sdist-installed:
99+
name: Test installed source distribution
100+
needs:
101+
- build
102+
runs-on: ubuntu-latest
103+
104+
steps:
105+
- uses: actions/checkout@v4
106+
- name: Set up Python
107+
uses: actions/setup-python@v4
108+
with:
109+
python-version: "3.x"
110+
- name: Download all the dists
111+
uses: actions/download-artifact@v4
112+
with:
113+
name: python-package-distributions
114+
path: dist/
115+
- name: Install source distribution
116+
run: |
117+
export path_to_file=$(find dist -type f -name "typing_extensions-*.tar.gz")
118+
echo "::notice::Installing source distribution: $path_to_file"
119+
python -m pip install --user $path_to_file
120+
python -m pip list
121+
- name: Run typing_extensions tests against installed package
122+
run: rm src/typing_extensions.py && python src/test_typing_extensions.py
123+
124+
publish-to-pypi:
125+
name: >-
126+
Publish Python distribution to PyPI
127+
if: github.event_name == 'release' # only publish to PyPI on releases
128+
needs:
129+
- test-sdist
130+
- test-sdist-installed
131+
- test-wheel
132+
- build
133+
runs-on: ubuntu-latest
134+
environment:
135+
name: publish
136+
url: https://pypi.org/p/typing-extensions
137+
permissions:
138+
id-token: write # IMPORTANT: mandatory for trusted publishing
139+
140+
steps:
141+
- name: Download all the dists
142+
uses: actions/download-artifact@v4
143+
with:
144+
name: python-package-distributions
145+
path: dist/
146+
- name: Ensure exactly one sdist and one wheel have been downloaded
147+
run: test $(ls *.tar.gz | wc -l) = 1 && test $(ls *.whl | wc -l) = 1
148+
- name: Publish distribution to PyPI
149+
uses: pypa/gh-action-pypi-publish@release/v1

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ classifiers = [
3939
"Programming Language :: Python :: 3.10",
4040
"Programming Language :: Python :: 3.11",
4141
"Programming Language :: Python :: 3.12",
42+
"Programming Language :: Python :: 3.13",
4243
"Topic :: Software Development",
4344
]
4445

scripts/check_package.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import argparse
2+
import re
3+
import sys
4+
import tomllib
5+
from pathlib import Path
6+
7+
8+
class ValidationError(Exception):
9+
pass
10+
11+
12+
def check(github_ref: str | None) -> None:
13+
pyproject = Path(__file__).parent.parent / "pyproject.toml"
14+
if not pyproject.exists():
15+
raise ValidationError("pyproject.toml not found")
16+
with pyproject.open("rb") as f:
17+
data = tomllib.load(f)
18+
pyproject_version = data["project"]["version"]
19+
20+
if github_ref is not None and github_ref.startswith("refs/tags/"):
21+
version = github_ref.removeprefix("refs/tags/")
22+
if version != pyproject_version:
23+
raise ValidationError(
24+
f"Version mismatch: GitHub ref is {version}, "
25+
f"but pyproject.toml is {pyproject_version}"
26+
)
27+
28+
requires_python = data["project"]["requires-python"]
29+
assert sys.version_info[0] == 3, "Rewrite this script when Python 4 comes out"
30+
match = re.fullmatch(r">=3\.(\d+)", requires_python)
31+
if not match:
32+
raise ValidationError(f"Invalid requires-python: {requires_python!r}")
33+
lowest_minor = int(match.group(1))
34+
35+
description = data["project"]["description"]
36+
if not description.endswith(f"3.{lowest_minor}+"):
37+
raise ValidationError(f"Description should mention Python 3.{lowest_minor}+")
38+
39+
classifiers = set(data["project"]["classifiers"])
40+
for should_be_supported in range(lowest_minor, sys.version_info[1] + 1):
41+
if (
42+
f"Programming Language :: Python :: 3.{should_be_supported}"
43+
not in classifiers
44+
):
45+
raise ValidationError(
46+
f"Missing classifier for Python 3.{should_be_supported}"
47+
)
48+
49+
50+
if __name__ == "__main__":
51+
parser = argparse.ArgumentParser("Script to check the package metadata")
52+
parser.add_argument(
53+
"github_ref", type=str, help="The current GitHub ref", nargs="?"
54+
)
55+
args = parser.parse_args()
56+
try:
57+
check(args.github_ref)
58+
except ValidationError as e:
59+
print(e)
60+
sys.exit(1)

src/test_typing_extensions.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import sys
2-
import os
32
import abc
43
import gc
54
import io
@@ -5718,8 +5717,7 @@ def test_typing_extensions_defers_when_possible(self):
57185717
getattr(typing, item))
57195718

57205719
def test_typing_extensions_compiles_with_opt(self):
5721-
file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
5722-
'typing_extensions.py')
5720+
file_path = typing_extensions.__file__
57235721
try:
57245722
subprocess.check_output(f'{sys.executable} -OO {file_path}',
57255723
stderr=subprocess.STDOUT,

0 commit comments

Comments
 (0)