Repository navigation
Publish Python distribution to PyPI #27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish Python distribution to PyPI | |
| on: | |
| workflow_dispatch: | |
| push: | |
| tags: | |
| - 'v[0-9]+.[0-9]+.[0-9]+*' | |
| permissions: {} | |
| jobs: | |
| build: | |
| name: Build distribution 📦 | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| submodules: 'recursive' | |
| persist-credentials: false | |
| - name: Set up Python 3.12 | |
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 | |
| id: setup-python | |
| with: | |
| python-version: 3.12 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 | |
| with: | |
| enable-cache: false | |
| - name: Install dependencies | |
| run: uv sync --extra crypto --extra dev | |
| - name: Generate rest sync code and tests | |
| run: uv run unasync | |
| # All three distributions build into one directory and are uploaded in a | |
| # single request, so that a release is all of them or none of them. The | |
| # wrappers pin the core exactly, so a partial release is an unusable one. | |
| - name: Build a binary wheel and a source tarball | |
| run: | | |
| uv build --out-dir dist | |
| uv build packages/ably-pubsub-server --out-dir dist | |
| uv build packages/ably-pubsub-device --out-dir dist | |
| - name: Store the distribution packages | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: python-package-distributions | |
| path: dist/ | |
| # The wrappers normalise to ably_pubsub_*, so ably-* selects the core alone | |
| - name: Check that wheel and tarball contains ably/sync/ | |
| run: | | |
| # Check wheel | |
| WHEEL=$(ls dist/ably-*.whl | head -n 1) | |
| echo "Checking wheel: $WHEEL" | |
| if unzip -l "$WHEEL" | grep -q "ably/sync/"; then | |
| echo "✅ Found ably/sync/ in wheel" | |
| else | |
| unzip -l "$WHEEL" | |
| echo "❌ ably/sync/ not found in wheel" | |
| exit 1 | |
| fi | |
| # Check tarball | |
| TARBALL=$(ls dist/ably-*.tar.gz | head -n 1) | |
| echo "Checking tarball: $TARBALL" | |
| if tar -tzf "$TARBALL" | grep -q "ably/sync/"; then | |
| echo "✅ Found ably/sync/ in tarball" | |
| else | |
| tar -tzf "$TARBALL" | |
| echo "❌ ably/sync/ not found in tarball" | |
| exit 1 | |
| fi | |
| - name: Check that all three distributions were built | |
| run: | | |
| # Publishing is a single upload, so a distribution missing here would | |
| # silently ship a release that the other two cannot be installed with. | |
| for NAME in ably ably_pubsub_server ably_pubsub_device; do | |
| for EXT in tar.gz whl; do | |
| COUNT=$(ls -1 dist/"$NAME"-*."$EXT" 2>/dev/null | wc -l) | |
| if [ "$COUNT" -ne 1 ]; then | |
| ls dist/ | |
| echo "❌ expected exactly one $NAME .$EXT, found $COUNT" | |
| exit 1 | |
| fi | |
| done | |
| echo "✅ $NAME" | |
| done | |
| # ably, ably-pubsub-server and ably-pubsub-device go up in one upload. The | |
| # short-lived token PyPI mints from an OIDC request carries every project that | |
| # trusts the requesting configuration, so one job publishes all three — which | |
| # requires each of the three projects to register this repository, workflow | |
| # and environment as a trusted publisher. | |
| publish-to-pypi: | |
| name: Publish Python distributions to PyPI | |
| if: startsWith(github.ref, 'refs/tags/v') # only publish to PyPI on tag pushes | |
| needs: | |
| - build | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/p/ably | |
| permissions: | |
| id-token: write # IMPORTANT: mandatory for trusted publishing | |
| steps: | |
| - name: Download all the dists | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| name: python-package-distributions | |
| path: dist/ | |
| - name: Extract tag | |
| id: tag | |
| run: | | |
| TAG=${GITHUB_REF#refs/tags/v} | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| - name: Compare every distribution's version with the tag | |
| run: | | |
| # sdist names are <normalised name>-<version>.tar.gz, and a normalised | |
| # version never contains a hyphen, so the last one starts the version. | |
| # Checking all three also catches a version that drifted out of lockstep. | |
| for TARBALL in dist/*.tar.gz; do | |
| VERSION=$(basename "$TARBALL" | sed -E 's/^.*-([^-]+)\.tar\.gz$/\1/') | |
| if [ "$VERSION" != "$TAG" ]; then | |
| echo "❌ $(basename "$TARBALL"): version ($VERSION) does not match tag ($TAG)." | |
| exit 1 | |
| fi | |
| echo "✅ $(basename "$TARBALL") matches tag $TAG" | |
| done | |
| env: | |
| TAG: ${{ steps.tag.outputs.tag }} | |
| - name: Publish distributions 📦 to PyPI | |
| uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 | |
| publish-to-testpypi: | |
| name: Publish Python distributions to TestPyPI | |
| needs: | |
| - build | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: testpypi | |
| url: https://test.pypi.org/p/ably | |
| permissions: | |
| id-token: write # IMPORTANT: mandatory for trusted publishing | |
| steps: | |
| - name: Download all the dists | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| name: python-package-distributions | |
| path: dist/ | |
| - name: Publish distributions 📦 to TestPyPI | |
| uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 | |
| with: | |
| repository-url: https://test.pypi.org/legacy/ |