|
4 | 4 | workflow_dispatch: |
5 | 5 | release: |
6 | 6 | types: [published] |
7 | | - push: |
8 | | - tags: |
9 | | - - "v*" |
| 7 | + |
| 8 | +concurrency: |
| 9 | + group: pypi-${{ github.event.release.tag_name || github.ref_name }} |
| 10 | + cancel-in-progress: true |
10 | 11 |
|
11 | 12 | permissions: |
12 | 13 | contents: read |
|
59 | 60 | name: pypi-dist |
60 | 61 | path: dist |
61 | 62 |
|
| 63 | + - name: Check whether this version is already on PyPI |
| 64 | + id: pypi_version |
| 65 | + shell: bash |
| 66 | + run: | |
| 67 | + python - <<'PY' >> "$GITHUB_OUTPUT" |
| 68 | + import glob |
| 69 | + import re |
| 70 | + import urllib.error |
| 71 | + import urllib.parse |
| 72 | + import urllib.request |
| 73 | + import zipfile |
| 74 | +
|
| 75 | + wheels = glob.glob("dist/*.whl") |
| 76 | + if not wheels: |
| 77 | + raise SystemExit("No wheel artifact found in dist/") |
| 78 | +
|
| 79 | + with zipfile.ZipFile(wheels[0]) as zf: |
| 80 | + metadata_name = next( |
| 81 | + name for name in zf.namelist() |
| 82 | + if name.endswith(".dist-info/METADATA") |
| 83 | + ) |
| 84 | + metadata = zf.read(metadata_name).decode("utf-8", errors="replace") |
| 85 | +
|
| 86 | + project = version = "" |
| 87 | + for line in metadata.splitlines(): |
| 88 | + if line.startswith("Name: "): |
| 89 | + project = line.split(":", 1)[1].strip() |
| 90 | + elif line.startswith("Version: "): |
| 91 | + version = line.split(":", 1)[1].strip() |
| 92 | +
|
| 93 | + if not re.fullmatch(r"[A-Za-z0-9_.-]+", project or ""): |
| 94 | + raise SystemExit(f"Invalid project name from wheel metadata: {project!r}") |
| 95 | + if not version: |
| 96 | + raise SystemExit("Missing version in wheel metadata") |
| 97 | +
|
| 98 | + url = f"https://pypi.org/pypi/{urllib.parse.quote(project)}/{urllib.parse.quote(version)}/json" |
| 99 | + try: |
| 100 | + with urllib.request.urlopen(url, timeout=20) as response: |
| 101 | + exists = response.status == 200 |
| 102 | + except urllib.error.HTTPError as exc: |
| 103 | + if exc.code == 404: |
| 104 | + exists = False |
| 105 | + else: |
| 106 | + raise |
| 107 | +
|
| 108 | + print(f"project={project}") |
| 109 | + print(f"version={version}") |
| 110 | + print(f"exists={str(exists).lower()}") |
| 111 | + PY |
| 112 | +
|
62 | 113 | - name: Publish to PyPI |
| 114 | + if: steps.pypi_version.outputs.exists != 'true' |
63 | 115 | uses: pypa/gh-action-pypi-publish@release/v1 |
| 116 | + |
| 117 | + - name: Skip existing PyPI version |
| 118 | + if: steps.pypi_version.outputs.exists == 'true' |
| 119 | + run: | |
| 120 | + echo "${{ steps.pypi_version.outputs.project }} ${{ steps.pypi_version.outputs.version }} already exists on PyPI; skipping publish." |
0 commit comments