-
Notifications
You must be signed in to change notification settings - Fork 0
212 lines (183 loc) · 6.69 KB
/
Copy pathpublish.yml
File metadata and controls
212 lines (183 loc) · 6.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# =============================================================================
# Publish to PyPI
# Trigger: Manual workflow dispatch (workflow_dispatch)
# Features:
# 1. Run full test suite
# 2. Build distribution package
# 3. Publish to PyPI (using API Token)
# 4. Verify installation
# 5. Create GitHub Release
#
# Usage Instructions:
# 1. Prerequisites:
# - Generate API Token on PyPI account: https://pypi.org/manage/account/tokens/
# - In GitHub repository Settings > Secrets and variables > Actions, add:
# - Name: PYPI_API_TOKEN
# - Secret: <your-pypi-api-token>
# - Ensure PYPI_API_TOKEN secret is configured in the environment
#
# 2. Trigger Publishing:
# - Go to GitHub Actions page and select this workflow
# - Click "Run workflow"
# - Enter version tag (e.g., v1.0.0, v0.1.1, v2.0.0rc1)
# - Click "Run workflow"
#
# 3. The workflow will automatically:
# - Run tests on all Python versions
# - Build sdist and wheel distributions
# - Publish to PyPI
# - Create GitHub Release
# =============================================================================
name: Publish to PyPI
on:
workflow_dispatch:
inputs:
version_tag:
description: 'Version tag to publish (e.g., v1.0.0, v0.1.1, v2.0.0rc1)'
required: true
type: string
jobs:
# ===========================================================================
# First run complete test suite
# ===========================================================================
test:
name: Test before publish
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Run tests
run: pytest --tb=short
# ===========================================================================
# Publish to PyPI
# ===========================================================================
publish:
name: Publish to PyPI
runs-on: ubuntu-latest
needs: test
environment: pypi
permissions:
contents: write # For creating release
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0 # setuptools-scm requires complete git history
- name: Check git tag exists (fail if not)
shell: bash
run: |
set -euo pipefail
TAG="${{ github.event.inputs.version_tag }}"
git fetch --tags --force
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
echo "Tag already exists: ${TAG}"
else
echo "::error::Tag does not exist: ${TAG}"
exit 1
fi
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.8"
- name: Install build tools
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Build package
run: python -m build
- name: Check package
run: twine check dist/*
- name: Publish to PyPI (using API Token)
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
TWINE_NON_INTERACTIVE: 1
run: twine upload dist/* --skip-existing
- name: Extract version from tag
id: get_version
run: |
TAG="${{ github.event.inputs.version_tag }}"
VERSION="${TAG#v}"
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "Extracted version from tag: $VERSION"
- name: Install from PyPI (retry until available) and validate
env:
EXPECTED_VERSION: ${{ steps.get_version.outputs.VERSION }}
run: |
set -euo pipefail
python -m venv test-env
source test-env/bin/activate
python -m pip install --upgrade pip
echo "Waiting for deep-solutions==${EXPECTED_VERSION} on PyPI..."
max_attempts=12 # 12 * 15s = 180s (3 minutes)
attempt=1
until pip install "deep-solutions==${EXPECTED_VERSION}"
do
if [ "$attempt" -ge "$max_attempts" ]; then
echo "::error::Package deep-solutions==${EXPECTED_VERSION} not available after $((max_attempts*15))s"
exit 1
fi
echo "Attempt ${attempt}/${max_attempts} failed; retrying in 15s..."
attempt=$((attempt+1))
sleep 15
done
echo "✅ Package installed successfully"
# Validate version and functionality
python - <<PYEOF
import sys, os, deep_solutions
from deep_solutions import hello_world
expected = os.environ["EXPECTED_VERSION"]
actual = deep_solutions.__version__
print(f"Expected version: {expected}")
print(f"Actual version: {actual}")
if actual != expected:
print(f"❌ Version mismatch! Expected '{expected}', got '{actual}'")
sys.exit(1)
print("✅ Version validated successfully!")
result = hello_world()
expected_output = "Hello from deep-solutions!"
print(f"Function output: {result}")
if result != expected_output:
print(f"❌ Output mismatch! Expected '{expected_output}', got '{result}'")
sys.exit(1)
print("✅ hello_world() validated successfully!")
print("\n✅ All validations passed!")
PYEOF
deactivate
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.inputs.version_tag }}
name: Release ${{ github.event.inputs.version_tag }}
body: |
## deep-solutions ${{ github.event.inputs.version_tag }}
### Installation
```bash
pip install deep-solutions==${{ steps.get_version.outputs.VERSION }}
```
### Changelog
See [CHANGELOG.md](https://github.com/FrostyHec/deep-solutions/blob/main/CHANGELOG.md) for details.
files: |
dist/*
draft: false
prerelease: false
- name: Upload build artifacts
uses: actions/upload-artifact@v6
with:
name: dist-pypi
path: dist/