Skip to content

Commit 977b316

Browse files
committed
updated deployment workflow
- ensure proper version is installed for testing - wait for release to appear on PyPi - source: https://github.com/Becksteinlab/multibind
1 parent 9d1e73c commit 977b316

2 files changed

Lines changed: 276 additions & 22 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: 'Wait for PyPI version'
2+
description: 'Wait for a specific package version to become available on PyPI or TestPyPI'
3+
inputs:
4+
repository:
5+
description: 'PyPI repository type: "pypi" or "testpypi"'
6+
required: true
7+
package:
8+
description: 'Package name'
9+
required: true
10+
version:
11+
description: 'Package version to wait for'
12+
required: true
13+
max_attempts:
14+
description: 'Maximum number of retry attempts'
15+
required: false
16+
default: '30'
17+
wait_seconds:
18+
description: 'Seconds to wait between attempts'
19+
required: false
20+
default: '10'
21+
22+
runs:
23+
using: composite
24+
steps:
25+
- name: Install requests
26+
shell: bash
27+
run: |
28+
python -m pip install --upgrade pip
29+
pip install requests
30+
31+
- name: Wait for version to be available
32+
shell: python
33+
env:
34+
REPOSITORY: ${{ inputs.repository }}
35+
PACKAGE: ${{ inputs.package }}
36+
VERSION: ${{ inputs.version }}
37+
MAX_ATTEMPTS: ${{ inputs.max_attempts }}
38+
WAIT_SECONDS: ${{ inputs.wait_seconds }}
39+
run: |
40+
import os
41+
import sys
42+
import time
43+
44+
import requests
45+
46+
repository = os.environ["REPOSITORY"].strip().lower()
47+
package = os.environ["PACKAGE"]
48+
version = os.environ["VERSION"]
49+
max_attempts = int(os.environ.get("MAX_ATTEMPTS", "30"))
50+
wait_seconds = int(os.environ.get("WAIT_SECONDS", "10"))
51+
52+
if repository == "testpypi":
53+
api_url = f"https://test.pypi.org/pypi/{package}/json"
54+
repo_name = "TestPyPI"
55+
elif repository == "pypi":
56+
api_url = f"https://pypi.org/pypi/{package}/json"
57+
repo_name = "PyPI"
58+
else:
59+
print(
60+
f"ERROR: repository must be 'pypi' or 'testpypi', got {repository!r}",
61+
file=sys.stderr,
62+
)
63+
sys.exit(1)
64+
65+
for attempt in range(max_attempts):
66+
try:
67+
r = requests.get(api_url, timeout=10)
68+
r.raise_for_status()
69+
data = r.json()
70+
versions = data.get("releases", {})
71+
keys = list(versions.keys())
72+
print("Available versions:", keys[-10:]) # Show last 10 versions
73+
if version in versions:
74+
print(f"✓ Version {version} is available on {repo_name}")
75+
print(f"Version {version} is now available on {repo_name}")
76+
sys.exit(0)
77+
print(f"✗ Version {version} is NOT available on {repo_name}")
78+
except Exception as e:
79+
print(f"Error checking version: {e}")
80+
81+
current = attempt + 1
82+
print(
83+
f"Attempt {current}/{max_attempts}: Version {version} not yet available "
84+
f"on {repo_name}, waiting {wait_seconds} seconds..."
85+
)
86+
time.sleep(wait_seconds)
87+
88+
print(
89+
f"ERROR: Version {version} did not become available on {repo_name} "
90+
f"after {max_attempts} attempts",
91+
file=sys.stderr,
92+
)
93+
sys.exit(1)

.github/workflows/deploy.yaml

Lines changed: 183 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,47 +8,208 @@ on:
88
types:
99
- published
1010

11+
concurrency:
12+
group: "${{ github.ref }}-${{ github.head_ref }}-${{ github.workflow }}"
13+
cancel-in-progress: false
14+
15+
defaults:
16+
run:
17+
shell: bash -l {0}
18+
1119
jobs:
12-
testpypi_push:
20+
build:
21+
name: Build package
22+
if: github.repository == 'MDAnalysis/PathSimAnalysis'
23+
runs-on: ubuntu-latest
24+
outputs:
25+
version: ${{ steps.extract-version.outputs.version }}
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@v7
29+
with:
30+
fetch-depth: 0
31+
fetch-tags: true
32+
33+
- name: Set up Python
34+
uses: actions/setup-python@v6
35+
with:
36+
python-version: "3.11"
37+
38+
- name: Install build dependencies
39+
run: |
40+
python -m pip install --upgrade pip
41+
pip install "setuptools>=61.2,<81" build twine
42+
43+
- name: Build package (wheel and sdist)
44+
run: python -m build
45+
46+
- name: Check package
47+
run: twine check dist/*
48+
49+
- name: Extract package version
50+
id: extract-version
51+
run: |
52+
WHEEL_FILE=$(ls dist/*.whl)
53+
VERSION=$(basename "$WHEEL_FILE" | sed -n 's/pathsimanalysis-\([^-]*\)-.*/\1/p')
54+
if [ -z "$VERSION" ]; then
55+
pip install "$WHEEL_FILE" --quiet
56+
VERSION=$(python -c "import pathsimanalysis; print(pathsimanalysis.__version__)")
57+
pip uninstall -y pathsimanalysis --quiet
58+
fi
59+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
60+
echo "Extracted version: $VERSION"
61+
62+
- name: Upload dist files
63+
uses: actions/upload-artifact@v7
64+
with:
65+
name: dist-files
66+
path: dist/
67+
retention-days: 1
68+
69+
test-pytest:
70+
name: Run tests
71+
if: github.repository == 'MDAnalysis/PathSimAnalysis'
72+
runs-on: ubuntu-latest
73+
needs: build
74+
steps:
75+
- name: Set up Python
76+
uses: actions/setup-python@v6
77+
with:
78+
python-version: "3.11"
79+
80+
- name: Download dist files
81+
uses: actions/download-artifact@v8
82+
with:
83+
name: dist-files
84+
path: dist/
85+
86+
- name: Install wheel with test extras
87+
run: |
88+
python -m pip install --upgrade pip
89+
WHEEL_FILE=$(ls dist/*.whl)
90+
pip install "${WHEEL_FILE}[test]"
91+
92+
- name: Test import
93+
run: |
94+
python -c "import pathsimanalysis; print(f'Package {pathsimanalysis.__version__} imported successfully')"
95+
96+
- name: Run tests
97+
run: pytest -v --pyargs pathsimanalysis.tests
98+
99+
deploy-testpypi:
100+
name: Deploy to TestPyPI
101+
if: |
102+
github.repository == 'MDAnalysis/PathSimAnalysis' &&
103+
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
104+
runs-on: ubuntu-latest
105+
needs: [build, test-pytest]
13106
environment:
14107
name: deploy
15108
url: https://test.pypi.org/p/pathsimanalysis
16109
permissions:
17110
id-token: write
18-
if: |
19-
github.repository == 'MDAnalysis/PathSimAnalysis' &&
20-
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
21-
name: Build, upload and test pure Python wheels to TestPyPi
22-
runs-on: ubuntu-latest
23-
24111
steps:
25-
- uses: actions/checkout@v7
112+
- name: Download dist files
113+
uses: actions/download-artifact@v8
114+
with:
115+
name: dist-files
116+
path: dist/
26117

27-
- name: testpypi_deploy
28-
uses: MDAnalysis/pypi-deployment@main
118+
- name: Publish to TestPyPI
119+
uses: pypa/gh-action-pypi-publish@release/v1
29120
with:
30-
test_submission: true
31-
tests: true
32-
test_deps: 'pytest MDAnalysisTests'
33-
package_name: 'pathsimanalysis'
121+
repository-url: https://test.pypi.org/legacy/
122+
verbose: true
123+
skip-existing: true
34124

35-
pypi_push:
125+
deploy-pypi:
126+
name: Deploy to PyPI
127+
if: |
128+
github.repository == 'MDAnalysis/PathSimAnalysis' &&
129+
(github.event_name == 'release' && github.event.action == 'published')
130+
runs-on: ubuntu-latest
131+
needs: [build, test-pytest]
36132
environment:
37133
name: deploy
38134
url: https://pypi.org/p/pathsimanalysis
39135
permissions:
40136
id-token: write
137+
steps:
138+
- name: Download dist files
139+
uses: actions/download-artifact@v8
140+
with:
141+
name: dist-files
142+
path: dist/
143+
144+
- name: Publish to PyPI
145+
uses: pypa/gh-action-pypi-publish@release/v1
146+
147+
test-deployed-testpypi:
148+
name: Test deployed package (TestPyPI)
41149
if: |
42150
github.repository == 'MDAnalysis/PathSimAnalysis' &&
43-
(github.event_name == 'release' && github.event.action == 'published')
44-
name: Build, upload and test pure Python wheels to PyPi
151+
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
45152
runs-on: ubuntu-latest
153+
needs: [build, deploy-testpypi]
154+
steps:
155+
- name: Checkout repository for actions
156+
uses: actions/checkout@v7
157+
158+
- name: Set up Python
159+
uses: actions/setup-python@v6
160+
with:
161+
python-version: "3.11"
162+
163+
- name: Wait for version to be available on TestPyPI
164+
uses: ./.github/actions/wait-for-pypi-version
165+
with:
166+
repository: testpypi
167+
package: pathsimanalysis
168+
version: ${{ needs.build.outputs.version }}
169+
170+
- name: Install from TestPyPI
171+
run: |
172+
python -m pip install --upgrade pip
173+
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ "pathsimanalysis[test]==${{ needs.build.outputs.version }}"
46174
175+
- name: Test import
176+
run: |
177+
python -c "import pathsimanalysis; print(f'Package {pathsimanalysis.__version__} imported successfully from TestPyPI')"
178+
179+
- name: Run tests
180+
run: pytest -v --pyargs pathsimanalysis.tests
181+
182+
test-deployed-pypi:
183+
name: Test deployed package (PyPI)
184+
if: |
185+
github.repository == 'MDAnalysis/PathSimAnalysis' &&
186+
(github.event_name == 'release' && github.event.action == 'published')
187+
runs-on: ubuntu-latest
188+
needs: [build, deploy-pypi]
47189
steps:
48-
- uses: actions/checkout@v7
190+
- name: Checkout repository for actions
191+
uses: actions/checkout@v7
192+
193+
- name: Set up Python
194+
uses: actions/setup-python@v6
195+
with:
196+
python-version: "3.11"
49197

50-
- name: pypi_deploy
51-
uses: MDAnalysis/pypi-deployment@main
198+
- name: Wait for version to be available on PyPI
199+
uses: ./.github/actions/wait-for-pypi-version
52200
with:
53-
package_name: 'pathsimanalysis'
54-
tests: false
201+
repository: pypi
202+
package: pathsimanalysis
203+
version: ${{ needs.build.outputs.version }}
204+
205+
- name: Install from PyPI
206+
run: |
207+
python -m pip install --upgrade pip
208+
pip install "pathsimanalysis[test]==${{ needs.build.outputs.version }}"
209+
210+
- name: Test import
211+
run: |
212+
python -c "import pathsimanalysis; print(f'Package {pathsimanalysis.__version__} imported successfully from PyPI')"
213+
214+
- name: Run tests
215+
run: pytest -v --pyargs pathsimanalysis.tests

0 commit comments

Comments
 (0)