|
| 1 | +--- |
| 2 | +name: Publish to PyPI |
| 3 | + |
| 4 | +# Trigger on push to main when version file changes OR manual trigger |
| 5 | +on: |
| 6 | + push: |
| 7 | + branches: |
| 8 | + - main |
| 9 | + paths: |
| 10 | + - 'src/honeyhive/__init__.py' |
| 11 | + workflow_dispatch: |
| 12 | + inputs: |
| 13 | + branch: |
| 14 | + description: 'Branch to publish from (e.g., complete-refactor for RC testing)' |
| 15 | + required: false |
| 16 | + default: 'main' |
| 17 | + |
| 18 | +permissions: |
| 19 | + contents: write # For creating GitHub releases |
| 20 | + id-token: write # For trusted publishing to PyPI |
| 21 | + |
| 22 | +env: |
| 23 | + PYTHON_VERSION: "3.11" |
| 24 | + |
| 25 | +jobs: |
| 26 | + publish: |
| 27 | + name: 📦 Build and Publish to PyPI |
| 28 | + runs-on: ubuntu-latest |
| 29 | + |
| 30 | + steps: |
| 31 | + - name: Checkout code |
| 32 | + uses: actions/checkout@v4 |
| 33 | + with: |
| 34 | + ref: ${{ github.event.inputs.branch || github.ref }} |
| 35 | + fetch-depth: 0 # Full history for proper release notes |
| 36 | + |
| 37 | + - name: Set up Python |
| 38 | + uses: actions/setup-python@v5 |
| 39 | + with: |
| 40 | + python-version: ${{ env.PYTHON_VERSION }} |
| 41 | + |
| 42 | + - name: Install build dependencies |
| 43 | + run: | |
| 44 | + python -m pip install --upgrade pip |
| 45 | + pip install build twine hatchling jq |
| 46 | +
|
| 47 | + # === VERSION VALIDATION === |
| 48 | + |
| 49 | + - name: Extract version from __init__.py |
| 50 | + id: get_version |
| 51 | + run: | |
| 52 | + # Extract version from source |
| 53 | + version=$(python -c "exec(open('src/honeyhive/__init__.py').read()); print(__version__)") |
| 54 | + echo "version=$version" >> $GITHUB_OUTPUT |
| 55 | + echo "📦 Detected version: $version" |
| 56 | +
|
| 57 | + # Validate version format (basic check) |
| 58 | + if ! echo "$version" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+(rc[0-9]+|alpha[0-9]+|beta[0-9]+)?$'; then |
| 59 | + echo "❌ Invalid version format: $version" |
| 60 | + echo "Expected format: X.Y.Z or X.Y.Zrc# or X.Y.Zalpha# or X.Y.Zbeta#" |
| 61 | + exit 1 |
| 62 | + fi |
| 63 | +
|
| 64 | + echo "✅ Version format is valid" |
| 65 | +
|
| 66 | + - name: Check if version already published to PyPI |
| 67 | + id: check_pypi |
| 68 | + run: | |
| 69 | + VERSION="${{ steps.get_version.outputs.version }}" |
| 70 | + echo "🔍 Checking if version $VERSION exists on PyPI..." |
| 71 | +
|
| 72 | + # Query PyPI API for package versions |
| 73 | + response=$(curl -s https://pypi.org/pypi/honeyhive/json || echo '{}') |
| 74 | +
|
| 75 | + # Check if version exists in releases |
| 76 | + if echo "$response" | python -c \ |
| 77 | + "import sys, json; \ |
| 78 | + data = json.load(sys.stdin); \ |
| 79 | + sys.exit(0 if '$VERSION' in data.get('releases', {}) else 1)"; then |
| 80 | + echo "exists=true" >> $GITHUB_OUTPUT |
| 81 | + echo "✅ Version $VERSION already published to PyPI" |
| 82 | + echo "SKIP_REASON=Version $VERSION already exists on PyPI" >> $GITHUB_OUTPUT |
| 83 | + else |
| 84 | + echo "exists=false" >> $GITHUB_OUTPUT |
| 85 | + echo "🆕 Version $VERSION is new - will publish to PyPI" |
| 86 | + fi |
| 87 | +
|
| 88 | + - name: Skip publishing (version already exists) |
| 89 | + if: steps.check_pypi.outputs.exists == 'true' |
| 90 | + run: | |
| 91 | + echo "## ✅ Version Already Published" >> $GITHUB_STEP_SUMMARY |
| 92 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 93 | + echo "**Version:** \`${{ steps.get_version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY |
| 94 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 95 | + echo "This version is already available on PyPI." >> $GITHUB_STEP_SUMMARY |
| 96 | + echo "No action needed." >> $GITHUB_STEP_SUMMARY |
| 97 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 98 | + VERSION="${{ steps.get_version.outputs.version }}" |
| 99 | + echo "🔗 [View on PyPI](https://pypi.org/project/honeyhive/$VERSION/)" \ |
| 100 | + >> $GITHUB_STEP_SUMMARY |
| 101 | +
|
| 102 | + echo "✅ Workflow complete - version already published" |
| 103 | + exit 0 |
| 104 | +
|
| 105 | + # === BUILD AND PUBLISH (only if version is new) === |
| 106 | + |
| 107 | + - name: Verify CHANGELOG updated |
| 108 | + if: steps.check_pypi.outputs.exists == 'false' |
| 109 | + run: | |
| 110 | + VERSION="${{ steps.get_version.outputs.version }}" |
| 111 | +
|
| 112 | + if ! grep -q "$VERSION" CHANGELOG.md; then |
| 113 | + echo "⚠️ Warning: Version $VERSION not found in CHANGELOG.md" |
| 114 | + echo "Please update CHANGELOG.md before releasing" |
| 115 | + echo "" |
| 116 | + echo "This is a warning only - publishing will continue" |
| 117 | + else |
| 118 | + echo "✅ CHANGELOG.md contains entry for version $VERSION" |
| 119 | + fi |
| 120 | +
|
| 121 | + - name: Build distribution packages |
| 122 | + if: steps.check_pypi.outputs.exists == 'false' |
| 123 | + run: | |
| 124 | + echo "📦 Building source distribution and wheel..." |
| 125 | + python -m build |
| 126 | +
|
| 127 | + echo "📋 Package contents:" |
| 128 | + ls -lh dist/ |
| 129 | +
|
| 130 | + - name: Verify package integrity |
| 131 | + if: steps.check_pypi.outputs.exists == 'false' |
| 132 | + run: | |
| 133 | + echo "🔍 Checking package integrity..." |
| 134 | + python -m twine check dist/* |
| 135 | + echo "✅ Package integrity verified" |
| 136 | +
|
| 137 | + - name: Test package installation |
| 138 | + if: steps.check_pypi.outputs.exists == 'false' |
| 139 | + run: | |
| 140 | + echo "🧪 Testing package installation..." |
| 141 | +
|
| 142 | + # Create clean test environment |
| 143 | + python -m venv test-install |
| 144 | + source test-install/bin/activate |
| 145 | +
|
| 146 | + # Install the wheel |
| 147 | + pip install dist/*.whl |
| 148 | +
|
| 149 | + # Test imports |
| 150 | + python -c " |
| 151 | + import honeyhive |
| 152 | + from honeyhive import HoneyHive, HoneyHiveTracer |
| 153 | + from honeyhive import trace, evaluate |
| 154 | +
|
| 155 | + print(f'✅ Package installation successful') |
| 156 | + print(f'HoneyHive version: {honeyhive.__version__}') |
| 157 | +
|
| 158 | + # Verify version matches |
| 159 | + expected_version = '${{ steps.get_version.outputs.version }}' |
| 160 | + if honeyhive.__version__ != expected_version: |
| 161 | + print(f'❌ Version mismatch: {honeyhive.__version__} != {expected_version}') |
| 162 | + exit(1) |
| 163 | +
|
| 164 | + print(f'✅ Version verified: {honeyhive.__version__}') |
| 165 | + " |
| 166 | +
|
| 167 | + deactivate |
| 168 | + echo "✅ Installation test passed" |
| 169 | +
|
| 170 | + - name: Publish to PyPI |
| 171 | + if: steps.check_pypi.outputs.exists == 'false' |
| 172 | + env: |
| 173 | + TWINE_USERNAME: __token__ |
| 174 | + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} |
| 175 | + run: | |
| 176 | + echo "🚀 Publishing version ${{ steps.get_version.outputs.version }} to PyPI..." |
| 177 | + python -m twine upload dist/* |
| 178 | + echo "✅ Published to PyPI successfully!" |
| 179 | +
|
| 180 | + - name: Verify publication |
| 181 | + if: steps.check_pypi.outputs.exists == 'false' |
| 182 | + run: | |
| 183 | + VERSION="${{ steps.get_version.outputs.version }}" |
| 184 | +
|
| 185 | + echo "⏳ Waiting 30 seconds for PyPI to update..." |
| 186 | + sleep 30 |
| 187 | +
|
| 188 | + echo "🔍 Verifying package is available on PyPI..." |
| 189 | + max_attempts=5 |
| 190 | + attempt=1 |
| 191 | +
|
| 192 | + while [ $attempt -le $max_attempts ]; do |
| 193 | + if pip index versions honeyhive | grep -q "$VERSION"; then |
| 194 | + echo "✅ Package verified on PyPI!" |
| 195 | + echo "🔗 https://pypi.org/project/honeyhive/$VERSION/" |
| 196 | + exit 0 |
| 197 | + fi |
| 198 | +
|
| 199 | + echo "Attempt $attempt/$max_attempts: Not yet available, waiting..." |
| 200 | + sleep 10 |
| 201 | + attempt=$((attempt + 1)) |
| 202 | + done |
| 203 | +
|
| 204 | + echo "⚠️ Could not verify package on PyPI within timeout" |
| 205 | + echo "This may be a PyPI indexing delay - check manually" |
| 206 | + echo "🔗 https://pypi.org/project/honeyhive/$VERSION/" |
| 207 | +
|
| 208 | + # === GITHUB RELEASE === |
| 209 | + |
| 210 | + - name: Create GitHub Release |
| 211 | + if: steps.check_pypi.outputs.exists == 'false' |
| 212 | + uses: actions/create-release@v1 |
| 213 | + env: |
| 214 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 215 | + with: |
| 216 | + tag_name: v${{ steps.get_version.outputs.version }} |
| 217 | + release_name: v${{ steps.get_version.outputs.version }} |
| 218 | + body: | |
| 219 | + # HoneyHive Python SDK v${{ steps.get_version.outputs.version }} |
| 220 | +
|
| 221 | + ## 📦 Installation |
| 222 | +
|
| 223 | + ```bash |
| 224 | + pip install honeyhive==${{ steps.get_version.outputs.version }} |
| 225 | + ``` |
| 226 | +
|
| 227 | + ## 🔗 Links |
| 228 | +
|
| 229 | + - [PyPI Package](https://pypi.org/project/honeyhive/${{ steps.get_version.outputs.version }}/) |
| 230 | + - [Documentation](https://honeyhiveai.github.io/python-sdk/) |
| 231 | + - [Changelog](https://github.com/honeyhiveai/python-sdk/blob/main/CHANGELOG.md) |
| 232 | +
|
| 233 | + ## 📝 Release Notes |
| 234 | +
|
| 235 | + See [CHANGELOG.md](https://github.com/honeyhiveai/python-sdk/blob/main/CHANGELOG.md) for detailed changes. |
| 236 | + draft: false |
| 237 | + prerelease: >- |
| 238 | + ${{ contains(steps.get_version.outputs.version, 'rc') || |
| 239 | + contains(steps.get_version.outputs.version, 'alpha') || |
| 240 | + contains(steps.get_version.outputs.version, 'beta') }} |
| 241 | +
|
| 242 | + # === SUMMARY === |
| 243 | + |
| 244 | + - name: Generate release summary |
| 245 | + if: steps.check_pypi.outputs.exists == 'false' |
| 246 | + run: | |
| 247 | + echo "# 🚀 Release Published Successfully" >> $GITHUB_STEP_SUMMARY |
| 248 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 249 | + echo "**Version:** \`${{ steps.get_version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY |
| 250 | + echo "**Published:** $(date -u)" >> $GITHUB_STEP_SUMMARY |
| 251 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 252 | + VERSION="${{ steps.get_version.outputs.version }}" |
| 253 | + echo "## 📦 Installation" >> $GITHUB_STEP_SUMMARY |
| 254 | + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY |
| 255 | + echo "pip install honeyhive==$VERSION" >> $GITHUB_STEP_SUMMARY |
| 256 | + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY |
| 257 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 258 | + echo "## 🔗 Links" >> $GITHUB_STEP_SUMMARY |
| 259 | + echo "- [PyPI Package](https://pypi.org/project/honeyhive/$VERSION/)" \ |
| 260 | + >> $GITHUB_STEP_SUMMARY |
| 261 | + echo "- [GitHub Release]\ |
| 262 | + (https://github.com/honeyhiveai/python-sdk/releases/tag/v$VERSION)" \ |
| 263 | + >> $GITHUB_STEP_SUMMARY |
| 264 | + echo "- [Documentation](https://honeyhiveai.github.io/python-sdk/)" >> $GITHUB_STEP_SUMMARY |
| 265 | + echo "" >> $GITHUB_STEP_SUMMARY |
| 266 | + echo "## ✅ Verification" >> $GITHUB_STEP_SUMMARY |
| 267 | + echo "- ✅ Package built successfully" >> $GITHUB_STEP_SUMMARY |
| 268 | + echo "- ✅ Package integrity verified" >> $GITHUB_STEP_SUMMARY |
| 269 | + echo "- ✅ Installation test passed" >> $GITHUB_STEP_SUMMARY |
| 270 | + echo "- ✅ Published to PyPI" >> $GITHUB_STEP_SUMMARY |
| 271 | + echo "- ✅ GitHub release created" >> $GITHUB_STEP_SUMMARY |
0 commit comments