diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml deleted file mode 100644 index 6b55f10e..00000000 --- a/.github/workflows/package.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Test packaging - -on: - push: - branches: - - main - pull_request: - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - wheel: - name: Test wheel install - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: 3 - - - name: Install pypa/build - run: | - # Be wary of running `pip install` here, since it becomes easy for us to - # accidentally pick up typing_extensions as installed by a dependency - python -m pip install --upgrade build - python -m pip list - - - name: Build and install wheel - run: | - python -m build . - export path_to_file=$(find dist -type f -name "typing_extensions-*.whl") - echo "::notice::Installing wheel: $path_to_file" - pip install -vvv $path_to_file - python -m pip list - - - name: Attempt to import typing_extensions - run: python -c "import typing_extensions; print(typing_extensions.__all__)" - - sdist: - name: Test sdist install - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: 3 - - - name: Install pypa/build - run: | - # Be wary of running `pip install` here, since it becomes easy for us to - # accidentally pick up typing_extensions as installed by a dependency - python -m pip install --upgrade build - python -m pip list - - - name: Build and install sdist - run: | - python -m build . - export path_to_file=$(find dist -type f -name "typing_extensions-*.tar.gz") - echo "::notice::Installing sdist: $path_to_file" - pip install -vvv $path_to_file - python -m pip list - - - name: Attempt to import typing_extensions - run: python -c "import typing_extensions; print(typing_extensions.__all__)" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..9b69e73e --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,149 @@ +# Based on +# https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ + +name: Test builds and publish Python distribution to PyPI + +on: + release: + types: [published] + push: + branches: [main] + pull_request: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Check package metadata + run: python scripts/check_package.py ${{ github.ref }} + - name: Install pypa/build + run: | + # Be wary of running `pip install` here, since it becomes easy for us to + # accidentally pick up typing_extensions as installed by a dependency + python -m pip install --upgrade build + python -m pip list + - name: Build a binary wheel and a source tarball + run: python -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + test-wheel: + name: Test wheel + needs: + - build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Install wheel + run: | + export path_to_file=$(find dist -type f -name "typing_extensions-*.whl") + echo "::notice::Installing wheel: $path_to_file" + python -m pip install --user $path_to_file + python -m pip list + - name: Run typing_extensions tests against installed package + run: rm src/typing_extensions.py && python src/test_typing_extensions.py + + test-sdist: + name: Test source distribution + needs: + - build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Unpack and test source distribution + run: | + export path_to_file=$(find dist -type f -name "typing_extensions-*.tar.gz") + echo "::notice::Unpacking source distribution: $path_to_file" + tar xzf $path_to_file -C dist/ + cd ${path_to_file%.tar.gz} + python src/test_typing_extensions.py + + test-sdist-installed: + name: Test installed source distribution + needs: + - build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Install source distribution + run: | + export path_to_file=$(find dist -type f -name "typing_extensions-*.tar.gz") + echo "::notice::Installing source distribution: $path_to_file" + python -m pip install --user $path_to_file + python -m pip list + - name: Run typing_extensions tests against installed package + run: rm src/typing_extensions.py && python src/test_typing_extensions.py + + publish-to-pypi: + name: >- + Publish Python distribution to PyPI + if: github.event_name == 'release' # only publish to PyPI on releases + needs: + - test-sdist + - test-sdist-installed + - test-wheel + - build + runs-on: ubuntu-latest + environment: + name: publish + url: https://pypi.org/p/typing-extensions + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Ensure exactly one sdist and one wheel have been downloaded + run: test $(ls *.tar.gz | wc -l) = 1 && test $(ls *.whl | wc -l) = 1 + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/pyproject.toml b/pyproject.toml index 4b1a7601..a59c9a0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Software Development", ] diff --git a/scripts/check_package.py b/scripts/check_package.py new file mode 100644 index 00000000..f52df411 --- /dev/null +++ b/scripts/check_package.py @@ -0,0 +1,60 @@ +import argparse +import re +import sys +import tomllib +from pathlib import Path + + +class ValidationError(Exception): + pass + + +def check(github_ref: str | None) -> None: + pyproject = Path(__file__).parent.parent / "pyproject.toml" + if not pyproject.exists(): + raise ValidationError("pyproject.toml not found") + with pyproject.open("rb") as f: + data = tomllib.load(f) + pyproject_version = data["project"]["version"] + + if github_ref is not None and github_ref.startswith("refs/tags/"): + version = github_ref.removeprefix("refs/tags/") + if version != pyproject_version: + raise ValidationError( + f"Version mismatch: GitHub ref is {version}, " + f"but pyproject.toml is {pyproject_version}" + ) + + requires_python = data["project"]["requires-python"] + assert sys.version_info[0] == 3, "Rewrite this script when Python 4 comes out" + match = re.fullmatch(r">=3\.(\d+)", requires_python) + if not match: + raise ValidationError(f"Invalid requires-python: {requires_python!r}") + lowest_minor = int(match.group(1)) + + description = data["project"]["description"] + if not description.endswith(f"3.{lowest_minor}+"): + raise ValidationError(f"Description should mention Python 3.{lowest_minor}+") + + classifiers = set(data["project"]["classifiers"]) + for should_be_supported in range(lowest_minor, sys.version_info[1] + 1): + if ( + f"Programming Language :: Python :: 3.{should_be_supported}" + not in classifiers + ): + raise ValidationError( + f"Missing classifier for Python 3.{should_be_supported}" + ) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser("Script to check the package metadata") + parser.add_argument( + "github_ref", type=str, help="The current GitHub ref", nargs="?" + ) + args = parser.parse_args() + try: + check(args.github_ref) + except ValidationError as e: + print(e) + sys.exit(1) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index ae6ad076..5d37b5bf 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -1,5 +1,4 @@ import sys -import os import abc import gc import io @@ -5718,8 +5717,7 @@ def test_typing_extensions_defers_when_possible(self): getattr(typing, item)) def test_typing_extensions_compiles_with_opt(self): - file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), - 'typing_extensions.py') + file_path = typing_extensions.__file__ try: subprocess.check_output(f'{sys.executable} -OO {file_path}', stderr=subprocess.STDOUT,