From b4c3862498ea44ef4eda78685d0883baa8593f9b Mon Sep 17 00:00:00 2001 From: sburman Date: Wed, 22 Jul 2026 11:56:11 +0530 Subject: [PATCH 01/33] add all workflows for gated release --- .github/artifactory-oidc/action.yml | 70 ++++++++++++++++++++++++ .github/deploy.yml | 84 +++++++++++++++++++++++++++++ requirements.in | 8 +++ requirements.txt | 60 ++++++++++++++++++--- 4 files changed, 215 insertions(+), 7 deletions(-) create mode 100644 .github/artifactory-oidc/action.yml create mode 100644 .github/deploy.yml create mode 100644 requirements.in diff --git a/.github/artifactory-oidc/action.yml b/.github/artifactory-oidc/action.yml new file mode 100644 index 0000000000..98a7ed450b --- /dev/null +++ b/.github/artifactory-oidc/action.yml @@ -0,0 +1,70 @@ +name: "Artifactory OIDC Auth" +description: "Exchange GitHub OIDC token for Artifactory access and configure pip indexes" + +inputs: + artifactory-url: + description: "Base Artifactory platform URL. Falls back to ARTIFACTORY_URL env var if not provided." + required: false + default: "" + oidc-provider-name: + description: "OIDC provider name configured in Artifactory" + required: false + default: "github-actions" + pypi-repo: + description: "Artifactory virtual PyPI repository name" + required: false + default: "virtual-pypi-thirdparty" + +runs: + using: "composite" + steps: + - name: Exchange GitHub OIDC token for Artifactory token + shell: bash + env: + INPUT_ARTIFACTORY_URL: ${{ inputs.artifactory-url }} + OIDC_PROVIDER_NAME: ${{ inputs.oidc-provider-name }} + PYPI_REPO: ${{ inputs.pypi-repo }} + run: | + set -euo pipefail + ARTIFACTORY_URL="${INPUT_ARTIFACTORY_URL:-${ARTIFACTORY_URL:-}}" + if [ -z "${ARTIFACTORY_URL}" ]; then + echo "::error::ARTIFACTORY_URL is not set (pass as input or set as env var)"; exit 1 + fi + + OIDC_JWT=$(curl -sS \ + "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=${ARTIFACTORY_URL}" \ + -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" | jq -r '.value') + + if [ -z "$OIDC_JWT" ] || [ "$OIDC_JWT" = "null" ]; then + echo "::error::Failed to obtain GitHub OIDC token"; exit 1 + fi + + decode_seg() { local s="${1}"; local m=$(( ${#s} % 4 )); [ $m -ne 0 ] && s="${s}$(printf '=%.0s' $(seq 1 $((4-m))))"; echo "$s" | tr '_-' '/+' | base64 -d 2>/dev/null; } + PAYLOAD=$(decode_seg "$(echo "$OIDC_JWT" | cut -d. -f2)") + echo "OIDC token claims:" + echo " sub = $(echo "$PAYLOAD" | jq -r '.sub')" + echo " aud = $(echo "$PAYLOAD" | jq -r '.aud')" + echo " iss = $(echo "$PAYLOAD" | jq -r '.iss')" + + RESP=$(curl -sS "${ARTIFACTORY_URL}/access/api/v1/oidc/token" \ + -H 'Content-Type: application/json' \ + -d "{\"grant_type\":\"urn:ietf:params:oauth:grant-type:token-exchange\", + \"subject_token_type\":\"urn:ietf:params:oauth:token-type:id_token\", + \"subject_token\":\"${OIDC_JWT}\", + \"provider_name\":\"${OIDC_PROVIDER_NAME}\"}") + + ART_TOKEN=$(echo "$RESP" | jq -r '.access_token // empty') + + if [ -z "$ART_TOKEN" ]; then + echo "::error::OIDC token exchange failed. Raw response (token field redacted):" + echo "$RESP" | jq 'if .access_token then .access_token="" else . end' 2>/dev/null || echo "$RESP" + exit 1 + fi + echo "::add-mask::$ART_TOKEN" + + HOST=$(echo "${ARTIFACTORY_URL}" | sed -E 's#^https?://##') + INDEX_URL="https://:${ART_TOKEN}@${HOST}/artifactory/api/pypi/${PYPI_REPO}/simple/" + + echo "::add-mask::${INDEX_URL}" + echo "PIP_INDEX_URL=${INDEX_URL}" >> "$GITHUB_ENV" + echo "PIP_TRUSTED_HOST=${HOST}" >> "$GITHUB_ENV" diff --git a/.github/deploy.yml b/.github/deploy.yml new file mode 100644 index 0000000000..21a1f983b3 --- /dev/null +++ b/.github/deploy.yml @@ -0,0 +1,84 @@ +name: Test and Deploy to PyPI + +on: + release: + types: [published] + workflow_dispatch: + +env: + ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }} + +jobs: + test: + name: Test - Python ${{ matrix.python-version }} + runs-on: ${{ github.repository_owner == 'twilio-internal' && 'ubuntu-x64' || 'ubuntu-latest' }} + timeout-minutes: 20 + permissions: + contents: read + id-token: write + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Authenticate with Artifactory + uses: ./.github/actions/artifactory-oidc + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + pip install virtualenv --upgrade + make install test-install + + - name: Run tests + run: make test-with-coverage + + deploy: + name: Publish to PyPI + needs: [test] + runs-on: ${{ github.repository_owner == 'twilio-internal' && 'ubuntu-x64' || 'ubuntu-latest' }} + environment: pypi + permissions: + contents: read + id-token: write + attestations: write + steps: + - uses: actions/checkout@v4 + + - name: Authenticate with Artifactory + uses: ./.github/actions/artifactory-oidc + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Validate tag matches package version + run: | + TAG="${GITHUB_REF#refs/tags/}" + # Strip leading 'v' if present + VERSION="${TAG#v}" + PKG_VERSION=$(python setup.py --version) + if [ "$VERSION" != "$PKG_VERSION" ]; then + echo "::error::Tag $TAG does not match setup.py version $PKG_VERSION" + exit 1 + fi + + - name: Build package + run: | + pip install build + python -m build + + - name: Verify package + run: | + pip install twine + twine check dist/* + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@v1 diff --git a/requirements.in b/requirements.in new file mode 100644 index 0000000000..7bc9e41806 --- /dev/null +++ b/requirements.in @@ -0,0 +1,8 @@ +pygments>=2.7.4 # not directly required, pinned by Snyk to avoid a vulnerability +requests>=2.32.2 +PyJWT>=2.0.0, <3.0.0 +aiohttp>=3.10.2 +aiohttp-retry==2.8.3 +certifi>=2023.7.22 # not directly required, pinned by Snyk to avoid a vulnerability +urllib3>=2.2.2 # not directly required, pinned by Snyk to avoid a vulnerability +zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability diff --git a/requirements.txt b/requirements.txt index 7bc9e41806..19de7b687c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,54 @@ -pygments>=2.7.4 # not directly required, pinned by Snyk to avoid a vulnerability -requests>=2.32.2 -PyJWT>=2.0.0, <3.0.0 -aiohttp>=3.10.2 +# +# This file is autogenerated by pip-compile with Python 3.14 +# by the following command: +# +# pip-compile --output-file=requirements.txt requirements.in +# +aiohappyeyeballs==2.6.2 + # via aiohttp +aiohttp==3.14.1 + # via + # -r requirements.in + # aiohttp-retry aiohttp-retry==2.8.3 -certifi>=2023.7.22 # not directly required, pinned by Snyk to avoid a vulnerability -urllib3>=2.2.2 # not directly required, pinned by Snyk to avoid a vulnerability -zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability + # via -r requirements.in +aiosignal==1.4.0 + # via aiohttp +attrs==26.1.0 + # via aiohttp +certifi==2026.5.20 + # via + # -r requirements.in + # requests +charset-normalizer==3.4.7 + # via requests +frozenlist==1.8.0 + # via + # aiohttp + # aiosignal +idna==3.18 + # via + # requests + # yarl +multidict==6.7.1 + # via + # aiohttp + # yarl +propcache==0.5.2 + # via + # aiohttp + # yarl +pygments==2.20.0 + # via -r requirements.in +pyjwt==2.13.0 + # via -r requirements.in +requests==2.34.2 + # via -r requirements.in +urllib3==2.7.0 + # via + # -r requirements.in + # requests +yarl==1.24.2 + # via aiohttp +zipp==4.1.0 + # via -r requirements.in From 91a47513855052650a091832a295f0d96a09d4cc Mon Sep 17 00:00:00 2001 From: sburman Date: Wed, 22 Jul 2026 12:36:10 +0530 Subject: [PATCH 02/33] add a test workflow --- .github/workflows/test-release.yml | 90 ++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 .github/workflows/test-release.yml diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml new file mode 100644 index 0000000000..29808ad2cb --- /dev/null +++ b/.github/workflows/test-release.yml @@ -0,0 +1,90 @@ +name: Test Release (Dry Run) + +on: + workflow_dispatch: + inputs: + python-version: + description: "Python version to test with (or 'all' for full matrix)" + required: false + default: "3.12" + +env: + ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }} + +jobs: + test: + name: Test - Python ${{ matrix.python-version }} + runs-on: ${{ github.repository_owner == 'twilio-internal' && 'ubuntu-x64' || 'ubuntu-latest' }} + timeout-minutes: 20 + permissions: + contents: read + id-token: write + strategy: + fail-fast: false + matrix: + python-version: ${{ github.event.inputs.python-version == 'all' && fromJson('["3.8","3.9","3.10","3.11","3.12","3.13"]') || fromJson(format('["{0}"]', github.event.inputs.python-version)) }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Authenticate with Artifactory + uses: ./.github/actions/artifactory-oidc + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + pip install virtualenv --upgrade + make install test-install + + - name: Run tests + run: make test-with-coverage + + deploy-dry-run: + name: Deploy (Dry Run - No Publish) + needs: [test] + runs-on: ${{ github.repository_owner == 'twilio-internal' && 'ubuntu-x64' || 'ubuntu-latest' }} + environment: pypi + permissions: + contents: read + id-token: write + attestations: write + steps: + - uses: actions/checkout@v4 + + - name: Authenticate with Artifactory + uses: ./.github/actions/artifactory-oidc + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Validate version in setup.py + run: | + PKG_VERSION=$(python setup.py --version) + echo "Package version: $PKG_VERSION" + echo "PKG_VERSION=$PKG_VERSION" >> "$GITHUB_ENV" + + - name: Build package + run: | + pip install build + python -m build + + - name: Verify package + run: | + pip install twine + twine check dist/* + + - name: List built artifacts + run: | + echo "Built artifacts:" + ls -lh dist/ + echo "" + echo "Package version: $PKG_VERSION" + echo "" + echo "--- DRY RUN COMPLETE ---" + echo "To publish for real, create a GitHub Release with a matching tag" + echo "and use the deploy.yml workflow." From 8741919bb271fea0a2585c5bfde9a34fc5d7077a Mon Sep 17 00:00:00 2001 From: shrutiburman <87537688+shrutiburman@users.noreply.github.com> Date: Wed, 22 Jul 2026 12:37:54 +0530 Subject: [PATCH 03/33] Apply suggestion from @semgrep-code-twilio[bot] Co-authored-by: semgrep-code-twilio[bot] <242513856+semgrep-code-twilio[bot]@users.noreply.github.com> --- .github/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/deploy.yml b/.github/deploy.yml index 21a1f983b3..29c7cc3636 100644 --- a/.github/deploy.yml +++ b/.github/deploy.yml @@ -21,7 +21,7 @@ jobs: matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: fetch-depth: 0 From e4e70c65643908d7e7fd68c91f4a0daa71c1db74 Mon Sep 17 00:00:00 2001 From: sburman Date: Wed, 22 Jul 2026 14:27:44 +0530 Subject: [PATCH 04/33] add a test workflow --- .github/deploy.yml | 10 +++++----- .github/workflows/test-release.yml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/deploy.yml b/.github/deploy.yml index 29c7cc3636..6e4d9101fe 100644 --- a/.github/deploy.yml +++ b/.github/deploy.yml @@ -21,14 +21,14 @@ jobs: matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: fetch-depth: 0 - name: Authenticate with Artifactory uses: ./.github/actions/artifactory-oidc - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: ${{ matrix.python-version }} @@ -50,12 +50,12 @@ jobs: id-token: write attestations: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Authenticate with Artifactory uses: ./.github/actions/artifactory-oidc - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.12" @@ -81,4 +81,4 @@ jobs: twine check dist/* - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@v1 + uses: pypa/gh-action-pypi-publish@f8f40c50f8a38deb44a60a2a2d2f2a9e5b2f7b16 # v1 diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 29808ad2cb..5cf01fccb6 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -24,14 +24,14 @@ jobs: matrix: python-version: ${{ github.event.inputs.python-version == 'all' && fromJson('["3.8","3.9","3.10","3.11","3.12","3.13"]') || fromJson(format('["{0}"]', github.event.inputs.python-version)) }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: fetch-depth: 0 - name: Authenticate with Artifactory uses: ./.github/actions/artifactory-oidc - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: ${{ matrix.python-version }} @@ -44,7 +44,7 @@ jobs: run: make test-with-coverage deploy-dry-run: - name: Deploy (Dry Run - No Publish) + name: Deploy (Dry Run - NO Publish) needs: [test] runs-on: ${{ github.repository_owner == 'twilio-internal' && 'ubuntu-x64' || 'ubuntu-latest' }} environment: pypi @@ -53,12 +53,12 @@ jobs: id-token: write attestations: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Authenticate with Artifactory uses: ./.github/actions/artifactory-oidc - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.12" From 61e0cdd6fa23df669d0278b5af7bbb5e6d836e68 Mon Sep 17 00:00:00 2001 From: shrutiburman <87537688+shrutiburman@users.noreply.github.com> Date: Thu, 23 Jul 2026 11:45:33 +0530 Subject: [PATCH 05/33] Update runner conditions for GitHub Actions workflow --- .github/workflows/test-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 5cf01fccb6..c51be3704b 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -14,7 +14,7 @@ env: jobs: test: name: Test - Python ${{ matrix.python-version }} - runs-on: ${{ github.repository_owner == 'twilio-internal' && 'ubuntu-x64' || 'ubuntu-latest' }} + runs-on: ${{ github.repository_owner == 'twilio' && 'ubuntu-latest' || 'ubuntu-x64' }} timeout-minutes: 20 permissions: contents: read @@ -46,7 +46,7 @@ jobs: deploy-dry-run: name: Deploy (Dry Run - NO Publish) needs: [test] - runs-on: ${{ github.repository_owner == 'twilio-internal' && 'ubuntu-x64' || 'ubuntu-latest' }} + runs-on: ${{ github.repository_owner == 'twilio' && 'ubuntu-latest' || 'ubuntu-x64' }} environment: pypi permissions: contents: read From 180114f0776b5ad6c2d1434f4a4b6b008b8f9b3d Mon Sep 17 00:00:00 2001 From: shrutiburman <87537688+shrutiburman@users.noreply.github.com> Date: Thu, 23 Jul 2026 11:48:12 +0530 Subject: [PATCH 06/33] Change runs-on to 'ubuntu-x64' in workflows Updated the runs-on condition to use 'ubuntu-x64' for both test and deploy-dry-run jobs. --- .github/workflows/test-release.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index c51be3704b..03c33f2591 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -14,7 +14,8 @@ env: jobs: test: name: Test - Python ${{ matrix.python-version }} - runs-on: ${{ github.repository_owner == 'twilio' && 'ubuntu-latest' || 'ubuntu-x64' }} + runs-on: ${{ 'ubuntu-x64' }} + # github.repository_owner == 'twilio' && 'ubuntu-latest' || timeout-minutes: 20 permissions: contents: read @@ -46,7 +47,8 @@ jobs: deploy-dry-run: name: Deploy (Dry Run - NO Publish) needs: [test] - runs-on: ${{ github.repository_owner == 'twilio' && 'ubuntu-latest' || 'ubuntu-x64' }} + runs-on: ${{ 'ubuntu-x64' }} + #runs-on: ${{ github.repository_owner == 'twilio' && 'ubuntu-latest' || 'ubuntu-x64' }} environment: pypi permissions: contents: read From 5becd711294a4fa91f1de01e41cddb104259866b Mon Sep 17 00:00:00 2001 From: sburman Date: Thu, 23 Jul 2026 11:55:26 +0530 Subject: [PATCH 07/33] updates --- .github/workflows/test-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 03c33f2591..a20de4b476 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -30,7 +30,7 @@ jobs: fetch-depth: 0 - name: Authenticate with Artifactory - uses: ./.github/actions/artifactory-oidc + uses: ./.github/artifactory-oidc - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: @@ -58,7 +58,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Authenticate with Artifactory - uses: ./.github/actions/artifactory-oidc + uses: ./.github/artifactory-oidc - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: From 2f2060b48a125015eb3f7bb4b665c096c484580e Mon Sep 17 00:00:00 2001 From: shrutiburman <87537688+shrutiburman@users.noreply.github.com> Date: Thu, 23 Jul 2026 14:36:16 +0530 Subject: [PATCH 08/33] Comment out aiohttp dependency to try out Comment out aiohttp dependency in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 09672cd205..9bbb3c2cca 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ install_requires=[ "requests >= 2.0.0", "PyJWT >= 2.0.0, < 3.0.0", - "aiohttp>=3.8.4", + # "aiohttp>=3.8.4", "aiohttp-retry>=2.8.3", ], packages=find_packages(exclude=["tests", "tests.*"]), From ce4d5f418563ee6d8327c90751b6e83bb806e0b2 Mon Sep 17 00:00:00 2001 From: shrutiburman <87537688+shrutiburman@users.noreply.github.com> Date: Thu, 23 Jul 2026 14:38:27 +0530 Subject: [PATCH 09/33] Comment out aiohttp-retry dependency Comment out the aiohttp-retry dependency in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9bbb3c2cca..825557d77b 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ "requests >= 2.0.0", "PyJWT >= 2.0.0, < 3.0.0", # "aiohttp>=3.8.4", - "aiohttp-retry>=2.8.3", + # "aiohttp-retry>=2.8.3", ], packages=find_packages(exclude=["tests", "tests.*"]), include_package_data=True, From b86c97442e8cbde1454210b7a530ecfd4ead5b9e Mon Sep 17 00:00:00 2001 From: sburman Date: Thu, 23 Jul 2026 15:24:44 +0530 Subject: [PATCH 10/33] test push from token locally From ba4246c4644e09ade75f80bd52bd6ca934eeda4c Mon Sep 17 00:00:00 2001 From: TwilioDX_local Date: Thu, 23 Jul 2026 15:42:58 +0530 Subject: [PATCH 11/33] test the pat from local testing From d62ff0e75dd04fe109e7c944623aa800c0a91056 Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 23 Jul 2026 05:40:47 +0000 Subject: [PATCH 12/33] test dummy commit from Librarian pod ssh From 7b1183c5069f993e3c0dcab644a5568deb54ece1 Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 23 Jul 2026 07:17:34 +0000 Subject: [PATCH 13/33] test librarian ssh cmd From a95dc7ea500bf9135f7a8f9225e71fc49784cdc1 Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 23 Jul 2026 09:41:02 +0000 Subject: [PATCH 14/33] test push From d713feed750939082872ef023fd80dc1b834d04c Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 23 Jul 2026 10:16:28 +0000 Subject: [PATCH 15/33] push from librarian From 4f109d3760a0dd290eb44a5b6a81f70b4776cdd6 Mon Sep 17 00:00:00 2001 From: TwilioDX_local Date: Fri, 24 Jul 2026 10:02:14 +0530 Subject: [PATCH 16/33] prettify --- tests/unit/rest/test_client.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/unit/rest/test_client.py b/tests/unit/rest/test_client.py index 5b1f047255..93338dfefa 100644 --- a/tests/unit/rest/test_client.py +++ b/tests/unit/rest/test_client.py @@ -86,9 +86,7 @@ def setUp(self): mock_response.text = "" mock_response.headers = {} self.client.http_client.session = Mock() - self.client.http_client.session.prepare_request = Mock( - side_effect=lambda r: r - ) + self.client.http_client.session.prepare_request = Mock(side_effect=lambda r: r) self.client.http_client.session.merge_environment_settings = Mock( return_value={} ) From 25d4e597f720a992d38d0b62a120eae4ed499085 Mon Sep 17 00:00:00 2001 From: TwilioDX_local Date: Fri, 24 Jul 2026 10:06:34 +0530 Subject: [PATCH 17/33] prettify --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 825557d77b..09672cd205 100644 --- a/setup.py +++ b/setup.py @@ -23,8 +23,8 @@ install_requires=[ "requests >= 2.0.0", "PyJWT >= 2.0.0, < 3.0.0", - # "aiohttp>=3.8.4", - # "aiohttp-retry>=2.8.3", + "aiohttp>=3.8.4", + "aiohttp-retry>=2.8.3", ], packages=find_packages(exclude=["tests", "tests.*"]), include_package_data=True, From 05171806b0d7d53ba8bd82591ea59b412545e3c7 Mon Sep 17 00:00:00 2001 From: sburman Date: Fri, 24 Jul 2026 10:11:15 +0530 Subject: [PATCH 18/33] Update test-release.yml --- .github/workflows/test-release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index a20de4b476..465cdb8b06 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -64,6 +64,9 @@ jobs: with: python-version: "3.12" + - name: Install setuptools + run: pip install setuptools + - name: Validate version in setup.py run: | PKG_VERSION=$(python setup.py --version) From 30f1c347522eb70b92362359d33c99c73c0b5575 Mon Sep 17 00:00:00 2001 From: sburman Date: Fri, 24 Jul 2026 10:19:10 +0530 Subject: [PATCH 19/33] Update deploy.yml --- .github/deploy.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/deploy.yml b/.github/deploy.yml index 6e4d9101fe..704851f2ea 100644 --- a/.github/deploy.yml +++ b/.github/deploy.yml @@ -59,6 +59,9 @@ jobs: with: python-version: "3.12" + - name: Install setuptools + run: pip install setuptools + - name: Validate tag matches package version run: | TAG="${GITHUB_REF#refs/tags/}" From 56d4bb976a73618490ee2a9705a8c59315a655d3 Mon Sep 17 00:00:00 2001 From: sburman Date: Fri, 24 Jul 2026 10:23:13 +0530 Subject: [PATCH 20/33] move the workflow to correct folder --- .github/{ => workflows}/deploy.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{ => workflows}/deploy.yml (100%) diff --git a/.github/deploy.yml b/.github/workflows/deploy.yml similarity index 100% rename from .github/deploy.yml rename to .github/workflows/deploy.yml From 2396bcf495159b26a5fac253a77bb01041370253 Mon Sep 17 00:00:00 2001 From: sburman Date: Fri, 24 Jul 2026 10:25:56 +0530 Subject: [PATCH 21/33] add workflow templates --- .github/deploy.yml | 84 ---------------------------- .github/workflows/deploy.yml | 0 .github/workflows/test-release.yml | 90 ------------------------------ 3 files changed, 174 deletions(-) delete mode 100644 .github/deploy.yml create mode 100644 .github/workflows/deploy.yml diff --git a/.github/deploy.yml b/.github/deploy.yml deleted file mode 100644 index 29c7cc3636..0000000000 --- a/.github/deploy.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: Test and Deploy to PyPI - -on: - release: - types: [published] - workflow_dispatch: - -env: - ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }} - -jobs: - test: - name: Test - Python ${{ matrix.python-version }} - runs-on: ${{ github.repository_owner == 'twilio-internal' && 'ubuntu-x64' || 'ubuntu-latest' }} - timeout-minutes: 20 - permissions: - contents: read - id-token: write - strategy: - fail-fast: false - matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] - steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - with: - fetch-depth: 0 - - - name: Authenticate with Artifactory - uses: ./.github/actions/artifactory-oidc - - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - pip install virtualenv --upgrade - make install test-install - - - name: Run tests - run: make test-with-coverage - - deploy: - name: Publish to PyPI - needs: [test] - runs-on: ${{ github.repository_owner == 'twilio-internal' && 'ubuntu-x64' || 'ubuntu-latest' }} - environment: pypi - permissions: - contents: read - id-token: write - attestations: write - steps: - - uses: actions/checkout@v4 - - - name: Authenticate with Artifactory - uses: ./.github/actions/artifactory-oidc - - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Validate tag matches package version - run: | - TAG="${GITHUB_REF#refs/tags/}" - # Strip leading 'v' if present - VERSION="${TAG#v}" - PKG_VERSION=$(python setup.py --version) - if [ "$VERSION" != "$PKG_VERSION" ]; then - echo "::error::Tag $TAG does not match setup.py version $PKG_VERSION" - exit 1 - fi - - - name: Build package - run: | - pip install build - python -m build - - - name: Verify package - run: | - pip install twine - twine check dist/* - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@v1 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 29808ad2cb..e69de29bb2 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -1,90 +0,0 @@ -name: Test Release (Dry Run) - -on: - workflow_dispatch: - inputs: - python-version: - description: "Python version to test with (or 'all' for full matrix)" - required: false - default: "3.12" - -env: - ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }} - -jobs: - test: - name: Test - Python ${{ matrix.python-version }} - runs-on: ${{ github.repository_owner == 'twilio-internal' && 'ubuntu-x64' || 'ubuntu-latest' }} - timeout-minutes: 20 - permissions: - contents: read - id-token: write - strategy: - fail-fast: false - matrix: - python-version: ${{ github.event.inputs.python-version == 'all' && fromJson('["3.8","3.9","3.10","3.11","3.12","3.13"]') || fromJson(format('["{0}"]', github.event.inputs.python-version)) }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Authenticate with Artifactory - uses: ./.github/actions/artifactory-oidc - - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - pip install virtualenv --upgrade - make install test-install - - - name: Run tests - run: make test-with-coverage - - deploy-dry-run: - name: Deploy (Dry Run - No Publish) - needs: [test] - runs-on: ${{ github.repository_owner == 'twilio-internal' && 'ubuntu-x64' || 'ubuntu-latest' }} - environment: pypi - permissions: - contents: read - id-token: write - attestations: write - steps: - - uses: actions/checkout@v4 - - - name: Authenticate with Artifactory - uses: ./.github/actions/artifactory-oidc - - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Validate version in setup.py - run: | - PKG_VERSION=$(python setup.py --version) - echo "Package version: $PKG_VERSION" - echo "PKG_VERSION=$PKG_VERSION" >> "$GITHUB_ENV" - - - name: Build package - run: | - pip install build - python -m build - - - name: Verify package - run: | - pip install twine - twine check dist/* - - - name: List built artifacts - run: | - echo "Built artifacts:" - ls -lh dist/ - echo "" - echo "Package version: $PKG_VERSION" - echo "" - echo "--- DRY RUN COMPLETE ---" - echo "To publish for real, create a GitHub Release with a matching tag" - echo "and use the deploy.yml workflow." From e9c86901357cc05f26f1004b79f6cf70ba1e879b Mon Sep 17 00:00:00 2001 From: sburman Date: Fri, 24 Jul 2026 10:41:25 +0530 Subject: [PATCH 22/33] add workflow templates and RC tag --- .github/workflows/test-release.yml | 28 ++++++++++++++++++++-------- setup.py | 2 +- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 465cdb8b06..753632b0e6 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -1,4 +1,4 @@ -name: Test Release (Dry Run) +name: Test and Release on: workflow_dispatch: @@ -44,8 +44,8 @@ jobs: - name: Run tests run: make test-with-coverage - deploy-dry-run: - name: Deploy (Dry Run - NO Publish) + deploy: + name: Build and Publish to PyPI needs: [test] runs-on: ${{ 'ubuntu-x64' }} #runs-on: ${{ github.repository_owner == 'twilio' && 'ubuntu-latest' || 'ubuntu-x64' }} @@ -67,11 +67,19 @@ jobs: - name: Install setuptools run: pip install setuptools - - name: Validate version in setup.py + - name: Validate tag matches package version run: | PKG_VERSION=$(python setup.py --version) echo "Package version: $PKG_VERSION" echo "PKG_VERSION=$PKG_VERSION" >> "$GITHUB_ENV" + if [[ "${GITHUB_REF}" == refs/tags/* ]]; then + TAG="${GITHUB_REF#refs/tags/}" + VERSION="${TAG#v}" + if [ "$VERSION" != "$PKG_VERSION" ]; then + echo "::error::Tag $TAG does not match setup.py version $PKG_VERSION" + exit 1 + fi + fi - name: Build package run: | @@ -89,7 +97,11 @@ jobs: ls -lh dist/ echo "" echo "Package version: $PKG_VERSION" - echo "" - echo "--- DRY RUN COMPLETE ---" - echo "To publish for real, create a GitHub Release with a matching tag" - echo "and use the deploy.yml workflow." + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@f8f40c50f8a38deb44a60a2a2d2f2a9e5b2f7b16 # v1 + + - name: Summary + run: | + echo "--- RELEASE COMPLETE ---" + echo "Published version $PKG_VERSION to PyPI" diff --git a/setup.py b/setup.py index 09672cd205..2677958405 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setup( name="twilio", - version="9.10.9", + version="9.10.10a1", description="Twilio API client and TwiML generator", author="Twilio", help_center="https://www.twilio.com/help/contact", From 7ce4e760dd25cd6961dc02db0504c6300c2fb3e8 Mon Sep 17 00:00:00 2001 From: sburman Date: Fri, 24 Jul 2026 10:45:14 +0530 Subject: [PATCH 23/33] update sha --- .github/workflows/deploy.yml | 2 +- .github/workflows/test-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 704851f2ea..257caa764b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -84,4 +84,4 @@ jobs: twine check dist/* - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@f8f40c50f8a38deb44a60a2a2d2f2a9e5b2f7b16 # v1 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1 diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 753632b0e6..01a1bfcbc1 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -99,7 +99,7 @@ jobs: echo "Package version: $PKG_VERSION" - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@f8f40c50f8a38deb44a60a2a2d2f2a9e5b2f7b16 # v1 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1 - name: Summary run: | From 30aecdbbda2a113df9d825524a41f6c7e30fe565 Mon Sep 17 00:00:00 2001 From: shrutiburman <87537688+shrutiburman@users.noreply.github.com> Date: Fri, 24 Jul 2026 11:07:23 +0530 Subject: [PATCH 24/33] Create test11.yml --- .github/workflows/test11.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/workflows/test11.yml diff --git a/.github/workflows/test11.yml b/.github/workflows/test11.yml new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/.github/workflows/test11.yml @@ -0,0 +1 @@ + From 43188eac26357221931a69e889f4abb240159a3e Mon Sep 17 00:00:00 2001 From: shrutiburman <87537688+shrutiburman@users.noreply.github.com> Date: Fri, 24 Jul 2026 11:10:21 +0530 Subject: [PATCH 25/33] Delete .github/workflows/test11.yml --- .github/workflows/test11.yml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .github/workflows/test11.yml diff --git a/.github/workflows/test11.yml b/.github/workflows/test11.yml deleted file mode 100644 index 8b13789179..0000000000 --- a/.github/workflows/test11.yml +++ /dev/null @@ -1 +0,0 @@ - From c5e879f08d8b410715ad61b7f8e4a0ba83f877ca Mon Sep 17 00:00:00 2001 From: sburman Date: Fri, 24 Jul 2026 11:30:18 +0530 Subject: [PATCH 26/33] add workflow templates --- .github/workflows/deploy.yml | 2 +- .github/workflows/test-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 257caa764b..0f4f62b164 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -44,7 +44,7 @@ jobs: name: Publish to PyPI needs: [test] runs-on: ${{ github.repository_owner == 'twilio-internal' && 'ubuntu-x64' || 'ubuntu-latest' }} - environment: pypi + environment: production permissions: contents: read id-token: write diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 01a1bfcbc1..f338a6b638 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -49,7 +49,7 @@ jobs: needs: [test] runs-on: ${{ 'ubuntu-x64' }} #runs-on: ${{ github.repository_owner == 'twilio' && 'ubuntu-latest' || 'ubuntu-x64' }} - environment: pypi + environment: production permissions: contents: read id-token: write From 2243f609ece710bf539ec5d909716c8405229b1e Mon Sep 17 00:00:00 2001 From: sburman Date: Fri, 24 Jul 2026 11:36:07 +0530 Subject: [PATCH 27/33] workflow dispatcher --- .github/workflows/test-release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index e69de29bb2..b6687c5e29 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -0,0 +1,3 @@ +name: Test and Release +on: + workflow_dispatch: From c56c9fd84c2281f83213d0eaba0a28d337d34051 Mon Sep 17 00:00:00 2001 From: sburman Date: Fri, 24 Jul 2026 18:26:55 +0530 Subject: [PATCH 28/33] updates --- .github/artifactory-oidc/action.yml | 70 ----------------------------- .github/workflows/deploy.yml | 59 +++++++++++++++++------- .github/workflows/test-release.yml | 67 ++++++++++++++------------- setup.py | 2 +- 4 files changed, 82 insertions(+), 116 deletions(-) delete mode 100644 .github/artifactory-oidc/action.yml diff --git a/.github/artifactory-oidc/action.yml b/.github/artifactory-oidc/action.yml deleted file mode 100644 index 98a7ed450b..0000000000 --- a/.github/artifactory-oidc/action.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: "Artifactory OIDC Auth" -description: "Exchange GitHub OIDC token for Artifactory access and configure pip indexes" - -inputs: - artifactory-url: - description: "Base Artifactory platform URL. Falls back to ARTIFACTORY_URL env var if not provided." - required: false - default: "" - oidc-provider-name: - description: "OIDC provider name configured in Artifactory" - required: false - default: "github-actions" - pypi-repo: - description: "Artifactory virtual PyPI repository name" - required: false - default: "virtual-pypi-thirdparty" - -runs: - using: "composite" - steps: - - name: Exchange GitHub OIDC token for Artifactory token - shell: bash - env: - INPUT_ARTIFACTORY_URL: ${{ inputs.artifactory-url }} - OIDC_PROVIDER_NAME: ${{ inputs.oidc-provider-name }} - PYPI_REPO: ${{ inputs.pypi-repo }} - run: | - set -euo pipefail - ARTIFACTORY_URL="${INPUT_ARTIFACTORY_URL:-${ARTIFACTORY_URL:-}}" - if [ -z "${ARTIFACTORY_URL}" ]; then - echo "::error::ARTIFACTORY_URL is not set (pass as input or set as env var)"; exit 1 - fi - - OIDC_JWT=$(curl -sS \ - "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=${ARTIFACTORY_URL}" \ - -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" | jq -r '.value') - - if [ -z "$OIDC_JWT" ] || [ "$OIDC_JWT" = "null" ]; then - echo "::error::Failed to obtain GitHub OIDC token"; exit 1 - fi - - decode_seg() { local s="${1}"; local m=$(( ${#s} % 4 )); [ $m -ne 0 ] && s="${s}$(printf '=%.0s' $(seq 1 $((4-m))))"; echo "$s" | tr '_-' '/+' | base64 -d 2>/dev/null; } - PAYLOAD=$(decode_seg "$(echo "$OIDC_JWT" | cut -d. -f2)") - echo "OIDC token claims:" - echo " sub = $(echo "$PAYLOAD" | jq -r '.sub')" - echo " aud = $(echo "$PAYLOAD" | jq -r '.aud')" - echo " iss = $(echo "$PAYLOAD" | jq -r '.iss')" - - RESP=$(curl -sS "${ARTIFACTORY_URL}/access/api/v1/oidc/token" \ - -H 'Content-Type: application/json' \ - -d "{\"grant_type\":\"urn:ietf:params:oauth:grant-type:token-exchange\", - \"subject_token_type\":\"urn:ietf:params:oauth:token-type:id_token\", - \"subject_token\":\"${OIDC_JWT}\", - \"provider_name\":\"${OIDC_PROVIDER_NAME}\"}") - - ART_TOKEN=$(echo "$RESP" | jq -r '.access_token // empty') - - if [ -z "$ART_TOKEN" ]; then - echo "::error::OIDC token exchange failed. Raw response (token field redacted):" - echo "$RESP" | jq 'if .access_token then .access_token="" else . end' 2>/dev/null || echo "$RESP" - exit 1 - fi - echo "::add-mask::$ART_TOKEN" - - HOST=$(echo "${ARTIFACTORY_URL}" | sed -E 's#^https?://##') - INDEX_URL="https://:${ART_TOKEN}@${HOST}/artifactory/api/pypi/${PYPI_REPO}/simple/" - - echo "::add-mask::${INDEX_URL}" - echo "PIP_INDEX_URL=${INDEX_URL}" >> "$GITHUB_ENV" - echo "PIP_TRUSTED_HOST=${HOST}" >> "$GITHUB_ENV" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0f4f62b164..ec306c9ce1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,17 +1,27 @@ -name: Test and Deploy to PyPI +name: Twilio Public release-pypi on: - release: - types: [published] - workflow_dispatch: + push: + tags: + - 'v*' env: ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }} jobs: + lockfile-hygiene: + runs-on: ubuntu-x64 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: twilio/sdk-actions/uv-lockfile-hygiene@23b656b843d2a3461cf38e4fd466c07a822d5fc0 # v1 + with: + clean-room: 'false' + test: name: Test - Python ${{ matrix.python-version }} - runs-on: ${{ github.repository_owner == 'twilio-internal' && 'ubuntu-x64' || 'ubuntu-latest' }} + needs: [lockfile-hygiene] + runs-on: ubuntu-x64 + if: github.repository_owner == 'twilio' timeout-minutes: 20 permissions: contents: read @@ -21,12 +31,14 @@ jobs: matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 - - name: Authenticate with Artifactory - uses: ./.github/actions/artifactory-oidc + - name: Artifactory OIDC Auth + uses: twilio/sdk-actions/artifactory-oidc@23b656b843d2a3461cf38e4fd466c07a822d5fc0 # v1 + with: + ecosystem: python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: @@ -43,17 +55,25 @@ jobs: deploy: name: Publish to PyPI needs: [test] - runs-on: ${{ github.repository_owner == 'twilio-internal' && 'ubuntu-x64' || 'ubuntu-latest' }} + runs-on: ubuntu-x64 + if: success() && github.ref_type == 'tag' && github.repository_owner == 'twilio' environment: production permissions: - contents: read + contents: write # required for creating GitHub Release id-token: write attestations: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Authenticate with Artifactory - uses: ./.github/actions/artifactory-oidc + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release create "${{ github.ref_name }}" --generate-notes + + - name: Artifactory OIDC Auth + uses: twilio/sdk-actions/artifactory-oidc@23b656b843d2a3461cf38e4fd466c07a822d5fc0 # v1 + with: + ecosystem: python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: @@ -62,12 +82,17 @@ jobs: - name: Install setuptools run: pip install setuptools - - name: Validate tag matches package version + - name: Validate tag format and version match run: | TAG="${GITHUB_REF#refs/tags/}" - # Strip leading 'v' if present + if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+ ]]; then + echo "::error::Release tag must be in the form v1.2.3 (got '$TAG')" + exit 1 + fi VERSION="${TAG#v}" PKG_VERSION=$(python setup.py --version) + echo "Package version: $PKG_VERSION" + echo "PKG_VERSION=$PKG_VERSION" >> "$GITHUB_ENV" if [ "$VERSION" != "$PKG_VERSION" ]; then echo "::error::Tag $TAG does not match setup.py version $PKG_VERSION" exit 1 @@ -85,3 +110,7 @@ jobs: - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1 + + - name: Summary + run: | + echo "Published version $PKG_VERSION to PyPI" diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 2f4e4f7660..6d2375b87c 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -1,4 +1,5 @@ -name: Test and Release +name: Pre-release Check (Dry Run) + on: workflow_dispatch: inputs: @@ -6,15 +7,26 @@ on: description: "Python version to test with (or 'all' for full matrix)" required: false default: "3.12" + schedule: + - cron: '30 3 * * 1' # Monday 9AM IST env: ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }} jobs: + lockfile-hygiene: + runs-on: ubuntu-x64 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: twilio/sdk-actions/uv-lockfile-hygiene@23b656b843d2a3461cf38e4fd466c07a822d5fc0 # v1 + with: + clean-room: 'false' + test: name: Test - Python ${{ matrix.python-version }} - runs-on: ${{ 'ubuntu-x64' }} - # github.repository_owner == 'twilio' && 'ubuntu-latest' || + needs: [lockfile-hygiene] + runs-on: ubuntu-x64 + if: github.repository_owner == 'twilio' timeout-minutes: 20 permissions: contents: read @@ -22,14 +34,16 @@ jobs: strategy: fail-fast: false matrix: - python-version: ${{ github.event.inputs.python-version == 'all' && fromJson('["3.8","3.9","3.10","3.11","3.12","3.13"]') || fromJson(format('["{0}"]', github.event.inputs.python-version)) }} + python-version: ${{ github.event.inputs.python-version == 'all' && fromJson('["3.8","3.9","3.10","3.11","3.12","3.13"]') || fromJson(format('["{0}"]', github.event.inputs.python-version || '3.12')) }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 - - name: Authenticate with Artifactory - uses: ./.github/artifactory-oidc + - name: Artifactory OIDC Auth + uses: twilio/sdk-actions/artifactory-oidc@23b656b843d2a3461cf38e4fd466c07a822d5fc0 # v1 + with: + ecosystem: python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: @@ -43,21 +57,21 @@ jobs: - name: Run tests run: make test-with-coverage - deploy: - name: Build and Publish to PyPI + deploy-dry-run: + name: Pre-release validation - No Publish needs: [test] - runs-on: ${{ 'ubuntu-x64' }} - #runs-on: ${{ github.repository_owner == 'twilio' && 'ubuntu-latest' || 'ubuntu-x64' }} - environment: production + runs-on: ubuntu-x64 + if: github.repository_owner == 'twilio' permissions: contents: read id-token: write - attestations: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Authenticate with Artifactory - uses: ./.github/artifactory-oidc + - name: Artifactory OIDC Auth + uses: twilio/sdk-actions/artifactory-oidc@23b656b843d2a3461cf38e4fd466c07a822d5fc0 # v1 + with: + ecosystem: python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: @@ -66,18 +80,14 @@ jobs: - name: Install setuptools run: pip install setuptools - - name: Validate tag matches package version + - name: Validate version format run: | PKG_VERSION=$(python setup.py --version) echo "Package version: $PKG_VERSION" echo "PKG_VERSION=$PKG_VERSION" >> "$GITHUB_ENV" - if [[ "${GITHUB_REF}" == refs/tags/* ]]; then - TAG="${GITHUB_REF#refs/tags/}" - VERSION="${TAG#v}" - if [ "$VERSION" != "$PKG_VERSION" ]; then - echo "::error::Tag $TAG does not match setup.py version $PKG_VERSION" - exit 1 - fi + if [[ ! "$PKG_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then + echo "::error::setup.py version must follow semver (got '$PKG_VERSION')" + exit 1 fi - name: Build package @@ -97,11 +107,8 @@ jobs: echo "" echo "Package version: $PKG_VERSION" - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1 - - - name: Summary + - name: Dry run summary run: | - echo "--- RELEASE COMPLETE ---" - echo "Published version $PKG_VERSION to PyPI" + echo "--- DRY RUN COMPLETE ---" + echo "All pre-release steps passed for version $PKG_VERSION" diff --git a/setup.py b/setup.py index 2677958405..09672cd205 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setup( name="twilio", - version="9.10.10a1", + version="9.10.9", description="Twilio API client and TwiML generator", author="Twilio", help_center="https://www.twilio.com/help/contact", From 0e23f19bfbd24ca743096c0f01f566b0635d02e8 Mon Sep 17 00:00:00 2001 From: sburman Date: Mon, 27 Jul 2026 16:55:38 +0530 Subject: [PATCH 29/33] Update pre-release-check.yml --- .github/workflows/{test-release.yml => pre-release-check.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{test-release.yml => pre-release-check.yml} (100%) diff --git a/.github/workflows/test-release.yml b/.github/workflows/pre-release-check.yml similarity index 100% rename from .github/workflows/test-release.yml rename to .github/workflows/pre-release-check.yml From bcec6f132da8eabd2a5d5cf83e2233366e15ca3f Mon Sep 17 00:00:00 2001 From: sburman Date: Tue, 28 Jul 2026 11:21:17 +0530 Subject: [PATCH 30/33] Create release-plan.md --- release-plan.md | 95 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 release-plan.md diff --git a/release-plan.md b/release-plan.md new file mode 100644 index 0000000000..8c6509356f --- /dev/null +++ b/release-plan.md @@ -0,0 +1,95 @@ +# Twilio Python SDK — Release Plan + +## pre-release-check.yml — Pre-release Validation + +**Triggers:** Manual dispatch | Cron (Monday 9AM IST) + +### Jobs + +1. **lockfile-hygiene** + - Checkout + - [`uv-lockfile-hygiene`](https://github.com/twilio/sdk-actions/blob/main/uv-lockfile-hygiene/action.yml) action (scan only, no clean-room install) + - Fails if internal Artifactory hosts found in `requirements*.txt` + +2. **test** (Python 3.12 default, or full matrix 3.8–3.13) — _needs: lockfile-hygiene_ + - Checkout + - [Artifactory OIDC Auth](https://github.com/twilio/sdk-actions/tree/main/artifactory-oidc) (`ecosystem: python`) + - Setup Python + - `pip install virtualenv` + `make install test-install` + - `make test-with-coverage` + +3. **deploy-dry-run** (Pre-release validation) — _needs: test_ + - Checkout + - Artifactory OIDC Auth + - Setup Python 3.12 + - Validate version format is semver (`X.Y.Z`) + - Build sdist + wheel (`python -m build`) + - Verify with `twine check dist/*` + - List artifacts + print summary + - **Does NOT publish** + +4. **notify-on-failure** (cron runs only) + - Slack notification with job results + +--- + +## deploy.yml — Publish to PyPI + +**Trigger:** Tag push matching `v*` + +### How to release + +1. Bump `version` in `setup.py`, merge to `main` +2. Tag and push: `git tag v9.0.1 && git push --tags` +3. Workflow fires automatically +4. Approve the `production` environment gate when prompted +5. Verify: `pip install twilio==9.0.1` + check attestations on pypi.org + +### Jobs + +1. **lockfile-hygiene** + - Checkout + - [`uv-lockfile-hygiene`](https://github.com/twilio/sdk-actions/blob/main/uv-lockfile-hygiene/action.yml) scan (no clean-room install) + +2. **test** (Python 3.8–3.13 full matrix) — _needs: lockfile-hygiene_ + - Checkout + - [Artifactory OIDC Auth](https://github.com/twilio/sdk-actions/tree/main/artifactory-oidc) (`ecosystem: python`) + - Setup Python + - `pip install virtualenv` + `make install test-install` + - `make test-with-coverage` + +3. **deploy** (Publish to PyPI) — _needs: test, requires `production` env approval_ + - Checkout + - Create GitHub Release (auto-generated notes) + - Artifactory OIDC Auth + - Setup Python 3.12 + - Validate tag format (`vX.Y.Z`) + matches `setup.py` version + - Build sdist + wheel (`python -m build`) + - Verify with `twine check dist/*` + - Publish to PyPI (OIDC trusted publishing, PEP 740 attestations) + +--- + +## End-to-end Release Day Flow + +| Step | Action | +| --- | --- | +| Weekly | Monday cron runs `pre-release-check.yml` — confirms infra is healthy | +| 1 | [ **_Librarian_** ] PR: bump `setup.py` version, merge to `main` | +| 2 | [ **_Librarian_** ] `git tag vX.Y.Z && git push --tags` | +| 3 | `deploy.yml` fires automatically on tag creation, tests run | +| 4 | [ **_Manual_** ] Approve `production` environment gate | +| 5 | GitHub Release created, package published to PyPI | +| 6 | [ **_Manual_** ] Verify: `pip install twilio==X.Y.Z` + check attestations on pypi.org | + +--- + +## Platform Team Dependencies + +| Dependency | Owner | Breaks if... | +| --- | --- | --- | +| Artifactory OIDC provider (`github-actions`) | SSC / Platform | Repo renamed, org changed, trust not configured | +| `vars.ARTIFACTORY_URL` | Repo admin | Variable not set or URL changes | +| `production` GitHub environment | Repo admin | Environment doesn't exist or approvals misconfigured | +| `ubuntu-x64` runner group | Enterprise admin | Repo not added to runner group, or runner pool down | +| PyPI trusted publisher | PyPI org admin | Not registered, or workflow filename / environment mismatch | From 642d7f3ed9b4e060c1998e5e57c1cf8b90d8a430 Mon Sep 17 00:00:00 2001 From: sburman Date: Tue, 28 Jul 2026 11:26:14 +0530 Subject: [PATCH 31/33] Delete test-release.yml --- .github/workflows/test-release.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .github/workflows/test-release.yml diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml deleted file mode 100644 index e69de29bb2..0000000000 From 920a59ea95b3d88ab5ef2854d02d73801fe5bbc4 Mon Sep 17 00:00:00 2001 From: sburman Date: Tue, 28 Jul 2026 12:31:54 +0530 Subject: [PATCH 32/33] Create test-release.yml --- .github/workflows/test-release.yml | 129 +++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 .github/workflows/test-release.yml diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml new file mode 100644 index 0000000000..3d443281cf --- /dev/null +++ b/.github/workflows/test-release.yml @@ -0,0 +1,129 @@ +name: Pre-release Check (Dry Run) + +on: + workflow_dispatch: + inputs: + python-version: + description: "Python version to test with (or 'all' for full matrix)" + required: false + default: "3.12" + schedule: + - cron: '30 3 * * 1' # Monday 9AM IST + +env: + ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }} + +jobs: +# lockfile-hygiene: +# runs-on: ubuntu-x64 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: twilio/sdk-actions/uv-lockfile-hygiene@23b656b843d2a3461cf38e4fd466c07a822d5fc0 # v1 +# with: +# clean-room: 'false' + + test: + name: Test - Python ${{ matrix.python-version }} + runs-on: ubuntu-x64 + if: github.repository_owner == 'twilio' + timeout-minutes: 20 + permissions: + contents: read + id-token: write + strategy: + fail-fast: false + matrix: + python-version: ${{ github.event.inputs.python-version == 'all' && fromJson('["3.8","3.9","3.10","3.11","3.12","3.13"]') || fromJson(format('["{0}"]', github.event.inputs.python-version || '3.12')) }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + + - name: Artifactory OIDC Auth + uses: twilio/sdk-actions/artifactory-oidc@23b656b843d2a3461cf38e4fd466c07a822d5fc0 # v1 + with: + ecosystem: python + + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + pip install virtualenv --upgrade + make install test-install + make prettier + + - name: Run tests + run: make test-with-coverage + + - name: Run cluster tests + if: ${{ !github.event.pull_request.head.repo.fork }} + env: + TWILIO_ACCOUNT_SID: ${{ secrets.TWILIO_ACCOUNT_SID }} + TWILIO_API_KEY: ${{ secrets.TWILIO_CLUSTER_TEST_API_KEY }} + TWILIO_API_SECRET: ${{ secrets.TWILIO_CLUSTER_TEST_API_KEY_SECRET }} + TWILIO_FROM_NUMBER: ${{ secrets.TWILIO_FROM_NUMBER }} + TWILIO_TO_NUMBER: ${{ secrets.TWILIO_TO_NUMBER }} + TWILIO_AUTH_TOKEN: ${{ secrets.TWILIO_AUTH_TOKEN }} + ASSISTANT_ID: ${{ secrets.ASSISTANT_ID }} + run: make cluster-test + + - name: Verify docs generation + run: make docs + +# deploy-dry-run: +# name: Pre-release validation - No Publish +# needs: [test] +# runs-on: ubuntu-x64 +# if: github.repository_owner == 'twilio' +# permissions: +# contents: read +# id-token: write +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# +# - name: Artifactory OIDC Auth +# uses: twilio/sdk-actions/artifactory-oidc@23b656b843d2a3461cf38e4fd466c07a822d5fc0 # v1 +# with: +# ecosystem: python +# +# - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 +# with: +# python-version: "3.12" +# +# - name: Install setuptools +# run: pip install setuptools +# +# - name: Validate version format +# run: | +# PKG_VERSION=$(python setup.py --version) +# echo "Package version: $PKG_VERSION" +# echo "PKG_VERSION=$PKG_VERSION" >> "$GITHUB_ENV" +# if [[ ! "$PKG_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then +# echo "::error::setup.py version must follow semver (got '$PKG_VERSION')" +# exit 1 +# fi +# +# - name: Build package +# run: | +# pip install build +# python -m build +# +# - name: Verify package +# run: | +# pip install twine +# twine check dist/* +# +# - name: List built artifacts +# run: | +# echo "Built artifacts:" +# ls -lh dist/ +# echo "" +# echo "Package version: $PKG_VERSION" +# +# - name: Dry run summary +# run: | +# echo "--- DRY RUN COMPLETE ---" +# echo "All pre-release steps passed for version $PKG_VERSION" +# From ccfcfa3fa52e220daa0bf971ab81c1e658f11e12 Mon Sep 17 00:00:00 2001 From: sburman Date: Tue, 28 Jul 2026 13:02:08 +0530 Subject: [PATCH 33/33] updates --- .../{pre-release-check.yml => ci.yml} | 33 ++++- .github/workflows/deploy.yml | 1 + .github/workflows/test-release.yml | 129 ------------------ release-plan.md | 25 ++-- 4 files changed, 41 insertions(+), 147 deletions(-) rename .github/workflows/{pre-release-check.yml => ci.yml} (70%) delete mode 100644 .github/workflows/test-release.yml diff --git a/.github/workflows/pre-release-check.yml b/.github/workflows/ci.yml similarity index 70% rename from .github/workflows/pre-release-check.yml rename to .github/workflows/ci.yml index 6d2375b87c..e00bdbb4c5 100644 --- a/.github/workflows/pre-release-check.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,16 @@ -name: Pre-release Check (Dry Run) +name: CI on: + pull_request: + branches: [main] + push: + branches: [main] workflow_dispatch: inputs: python-version: description: "Python version to test with (or 'all' for full matrix)" required: false - default: "3.12" + default: "all" schedule: - cron: '30 3 * * 1' # Monday 9AM IST @@ -27,14 +31,15 @@ jobs: needs: [lockfile-hygiene] runs-on: ubuntu-x64 if: github.repository_owner == 'twilio' - timeout-minutes: 20 + # make docs takes ~15mins + timeout-minutes: 30 permissions: contents: read id-token: write strategy: fail-fast: false matrix: - python-version: ${{ github.event.inputs.python-version == 'all' && fromJson('["3.8","3.9","3.10","3.11","3.12","3.13"]') || fromJson(format('["{0}"]', github.event.inputs.python-version || '3.12')) }} + python-version: ${{ (github.event_name == 'schedule' || github.event.inputs.python-version == 'all') && fromJson('["3.8","3.9","3.10","3.11","3.12","3.13"]') || fromJson(format('["{0}"]', github.event.inputs.python-version || '3.12')) }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: @@ -53,15 +58,31 @@ jobs: run: | pip install virtualenv --upgrade make install test-install + make prettier - name: Run tests run: make test-with-coverage + - name: Run cluster tests + if: ${{ !github.event.pull_request.head.repo.fork }} + env: + TWILIO_ACCOUNT_SID: ${{ secrets.TWILIO_ACCOUNT_SID }} + TWILIO_API_KEY: ${{ secrets.TWILIO_CLUSTER_TEST_API_KEY }} + TWILIO_API_SECRET: ${{ secrets.TWILIO_CLUSTER_TEST_API_KEY_SECRET }} + TWILIO_FROM_NUMBER: ${{ secrets.TWILIO_FROM_NUMBER }} + TWILIO_TO_NUMBER: ${{ secrets.TWILIO_TO_NUMBER }} + TWILIO_AUTH_TOKEN: ${{ secrets.TWILIO_AUTH_TOKEN }} + ASSISTANT_ID: ${{ secrets.ASSISTANT_ID }} + run: make cluster-test + + - name: Verify docs generation + run: make docs + deploy-dry-run: - name: Pre-release validation - No Publish + name: Release Readiness Check - Build artifact needs: [test] runs-on: ubuntu-x64 - if: github.repository_owner == 'twilio' + if: github.repository_owner == 'twilio' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') permissions: contents: read id-token: write diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ec306c9ce1..f2f188c132 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -48,6 +48,7 @@ jobs: run: | pip install virtualenv --upgrade make install test-install + make prettier - name: Run tests run: make test-with-coverage diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml deleted file mode 100644 index 3d443281cf..0000000000 --- a/.github/workflows/test-release.yml +++ /dev/null @@ -1,129 +0,0 @@ -name: Pre-release Check (Dry Run) - -on: - workflow_dispatch: - inputs: - python-version: - description: "Python version to test with (or 'all' for full matrix)" - required: false - default: "3.12" - schedule: - - cron: '30 3 * * 1' # Monday 9AM IST - -env: - ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }} - -jobs: -# lockfile-hygiene: -# runs-on: ubuntu-x64 -# steps: -# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - uses: twilio/sdk-actions/uv-lockfile-hygiene@23b656b843d2a3461cf38e4fd466c07a822d5fc0 # v1 -# with: -# clean-room: 'false' - - test: - name: Test - Python ${{ matrix.python-version }} - runs-on: ubuntu-x64 - if: github.repository_owner == 'twilio' - timeout-minutes: 20 - permissions: - contents: read - id-token: write - strategy: - fail-fast: false - matrix: - python-version: ${{ github.event.inputs.python-version == 'all' && fromJson('["3.8","3.9","3.10","3.11","3.12","3.13"]') || fromJson(format('["{0}"]', github.event.inputs.python-version || '3.12')) }} - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - - name: Artifactory OIDC Auth - uses: twilio/sdk-actions/artifactory-oidc@23b656b843d2a3461cf38e4fd466c07a822d5fc0 # v1 - with: - ecosystem: python - - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - pip install virtualenv --upgrade - make install test-install - make prettier - - - name: Run tests - run: make test-with-coverage - - - name: Run cluster tests - if: ${{ !github.event.pull_request.head.repo.fork }} - env: - TWILIO_ACCOUNT_SID: ${{ secrets.TWILIO_ACCOUNT_SID }} - TWILIO_API_KEY: ${{ secrets.TWILIO_CLUSTER_TEST_API_KEY }} - TWILIO_API_SECRET: ${{ secrets.TWILIO_CLUSTER_TEST_API_KEY_SECRET }} - TWILIO_FROM_NUMBER: ${{ secrets.TWILIO_FROM_NUMBER }} - TWILIO_TO_NUMBER: ${{ secrets.TWILIO_TO_NUMBER }} - TWILIO_AUTH_TOKEN: ${{ secrets.TWILIO_AUTH_TOKEN }} - ASSISTANT_ID: ${{ secrets.ASSISTANT_ID }} - run: make cluster-test - - - name: Verify docs generation - run: make docs - -# deploy-dry-run: -# name: Pre-release validation - No Publish -# needs: [test] -# runs-on: ubuntu-x64 -# if: github.repository_owner == 'twilio' -# permissions: -# contents: read -# id-token: write -# steps: -# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# -# - name: Artifactory OIDC Auth -# uses: twilio/sdk-actions/artifactory-oidc@23b656b843d2a3461cf38e4fd466c07a822d5fc0 # v1 -# with: -# ecosystem: python -# -# - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 -# with: -# python-version: "3.12" -# -# - name: Install setuptools -# run: pip install setuptools -# -# - name: Validate version format -# run: | -# PKG_VERSION=$(python setup.py --version) -# echo "Package version: $PKG_VERSION" -# echo "PKG_VERSION=$PKG_VERSION" >> "$GITHUB_ENV" -# if [[ ! "$PKG_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then -# echo "::error::setup.py version must follow semver (got '$PKG_VERSION')" -# exit 1 -# fi -# -# - name: Build package -# run: | -# pip install build -# python -m build -# -# - name: Verify package -# run: | -# pip install twine -# twine check dist/* -# -# - name: List built artifacts -# run: | -# echo "Built artifacts:" -# ls -lh dist/ -# echo "" -# echo "Package version: $PKG_VERSION" -# -# - name: Dry run summary -# run: | -# echo "--- DRY RUN COMPLETE ---" -# echo "All pre-release steps passed for version $PKG_VERSION" -# diff --git a/release-plan.md b/release-plan.md index 8c6509356f..570079c913 100644 --- a/release-plan.md +++ b/release-plan.md @@ -1,24 +1,27 @@ # Twilio Python SDK — Release Plan -## pre-release-check.yml — Pre-release Validation +## ci.yml (CI) -**Triggers:** Manual dispatch | Cron (Monday 9AM IST) +**Triggers:** PRs to main | Push to main | Manual dispatch | Cron (Monday 9AM IST) ### Jobs -1. **lockfile-hygiene** +1. **lockfile-hygiene** _(all triggers)_ - Checkout - [`uv-lockfile-hygiene`](https://github.com/twilio/sdk-actions/blob/main/uv-lockfile-hygiene/action.yml) action (scan only, no clean-room install) - Fails if internal Artifactory hosts found in `requirements*.txt` -2. **test** (Python 3.12 default, or full matrix 3.8–3.13) — _needs: lockfile-hygiene_ +2. **test** (Python matrix) — _needs: lockfile-hygiene_ _(all triggers)_ - Checkout - [Artifactory OIDC Auth](https://github.com/twilio/sdk-actions/tree/main/artifactory-oidc) (`ecosystem: python`) - Setup Python - - `pip install virtualenv` + `make install test-install` + - `pip install virtualenv` + `make install test-install` + `make prettier` - `make test-with-coverage` + - Cluster tests (`make cluster-test` with 7 secrets) + - Verify docs generation (`make docs`) + - Matrix: `3.12` on PRs/pushes, `[3.8–3.13]` on cron/manual with `all` -3. **deploy-dry-run** (Pre-release validation) — _needs: test_ +3. **deploy-dry-run** (Release Readiness Check - Build artifact) — _needs: test_ _(cron + manual dispatch only)_ - Checkout - Artifactory OIDC Auth - Setup Python 3.12 @@ -28,9 +31,6 @@ - List artifacts + print summary - **Does NOT publish** -4. **notify-on-failure** (cron runs only) - - Slack notification with job results - --- ## deploy.yml — Publish to PyPI @@ -55,7 +55,7 @@ - Checkout - [Artifactory OIDC Auth](https://github.com/twilio/sdk-actions/tree/main/artifactory-oidc) (`ecosystem: python`) - Setup Python - - `pip install virtualenv` + `make install test-install` + - `pip install virtualenv` + `make install test-install` + `make prettier` - `make test-with-coverage` 3. **deploy** (Publish to PyPI) — _needs: test, requires `production` env approval_ @@ -74,10 +74,11 @@ | Step | Action | | --- | --- | -| Weekly | Monday cron runs `pre-release-check.yml` — confirms infra is healthy | +| Weekly | Monday cron runs CI workflow — confirms infra is healthy (full matrix + dry run) | +| On every PR | CI workflow runs lockfile-hygiene + test (3.12) + cluster tests + docs | | 1 | [ **_Librarian_** ] PR: bump `setup.py` version, merge to `main` | | 2 | [ **_Librarian_** ] `git tag vX.Y.Z && git push --tags` | -| 3 | `deploy.yml` fires automatically on tag creation, tests run | +| 3 | `deploy.yml` fires automatically on tag creation, tests run (3.8–3.13) | | 4 | [ **_Manual_** ] Approve `production` environment gate | | 5 | GitHub Release created, package published to PyPI | | 6 | [ **_Manual_** ] Verify: `pip install twilio==X.Y.Z` + check attestations on pypi.org |