Skip to content

Commit a91b314

Browse files
authored
Merge pull request #82 from MDAnalysis/update-python-and-gh
update Python and workflows - NEP: removed 3.9, 3.10, added 3.13, 3.14 - update workflows - actions versions updated - improved automatic deployment - fix update package metadata #83
2 parents 2972714 + 0d8c4d6 commit a91b314

File tree

11 files changed

+360
-89
lines changed

11 files changed

+360
-89
lines changed
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: 0 additions & 57 deletions
This file was deleted.

.github/workflows/deploy.yml

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
name: PyPi Package Deployment
2+
3+
on:
4+
push:
5+
tags:
6+
- "*"
7+
release:
8+
types:
9+
- published
10+
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+
19+
jobs:
20+
build:
21+
name: Build package
22+
runs-on: ubuntu-latest
23+
outputs:
24+
version: ${{ steps.extract-version.outputs.version }}
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v6
28+
with:
29+
fetch-depth: 0
30+
fetch-tags: true
31+
32+
- name: Set up Python
33+
uses: actions/setup-python@v6
34+
with:
35+
python-version: "3.14"
36+
37+
- name: Install build dependencies
38+
run: |
39+
python -m pip install --upgrade pip
40+
pip install build twine
41+
42+
- name: Build package (binary wheel and source distribution package)
43+
run: python -m build
44+
45+
- name: Check package
46+
run: twine check dist/*
47+
48+
- name: Extract package version
49+
id: extract-version
50+
run: |
51+
WHEEL_FILE=$(ls dist/*.whl)
52+
VERSION=$(basename "$WHEEL_FILE" | sed -n 's/mdanalysisdata-\([^-]*\)-.*/\1/p')
53+
if [ -z "$VERSION" ]; then
54+
python -m pip install --upgrade pip
55+
pip install "$WHEEL_FILE" --quiet
56+
VERSION=$(python -c "import MDAnalysisData; print(MDAnalysisData.__version__)")
57+
pip uninstall -y MDAnalysisData --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+
runs-on: ubuntu-latest
72+
needs: build
73+
steps:
74+
- name: Set up Python
75+
uses: actions/setup-python@v6
76+
with:
77+
python-version: "3.14"
78+
79+
- name: Download dist files
80+
uses: actions/download-artifact@v8
81+
with:
82+
name: dist-files
83+
path: dist/
84+
85+
- name: Install package with test dependencies and tests
86+
run: |
87+
python -m pip install --upgrade pip
88+
WHEEL_FILE=$(ls dist/*.whl)
89+
pip install "${WHEEL_FILE}[test]"
90+
91+
- name: Test import
92+
run: |
93+
python -c "import MDAnalysisData; print(f'Package {MDAnalysisData.__version__} imported successfully')"
94+
95+
- name: Run tests (without data file downloads)
96+
run: |
97+
pytest --verbose -m "not online" --pyargs MDAnalysisData
98+
99+
deploy-testpypi:
100+
name: Deploy to TestPyPI
101+
runs-on: ubuntu-latest
102+
needs: [build, test-pytest]
103+
if: |
104+
github.repository == 'MDAnalysis/MDAnalysisData' &&
105+
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
106+
environment:
107+
name: testpypi
108+
url: https://test.pypi.org/p/MDAnalysisData
109+
permissions:
110+
id-token: write # IMPORTANT: mandatory for trusted publishing
111+
steps:
112+
- name: Download dist files
113+
uses: actions/download-artifact@v8
114+
with:
115+
name: dist-files
116+
path: dist/
117+
118+
- name: Publish to TestPyPI
119+
uses: pypa/gh-action-pypi-publish@v1.13.0
120+
with:
121+
repository-url: https://test.pypi.org/legacy/
122+
verbose: true # useful debugging info (eg wrong classifiers)
123+
skip-existing: true # allows repeated testing with same tag
124+
125+
deploy-pypi:
126+
name: Deploy to PyPI
127+
runs-on: ubuntu-latest
128+
needs: [build, test-pytest]
129+
if: |
130+
github.repository == 'MDAnalysis/MDAnalysisData' &&
131+
(github.event_name == 'release' && github.event.action == 'published')
132+
environment:
133+
name: pypi
134+
url: https://pypi.org/p/MDAnalysisData
135+
permissions:
136+
id-token: write # IMPORTANT: mandatory for trusted publishing
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@v1.13.0
146+
147+
test-deployed-testpypi:
148+
name: Test deployed package (TestPyPI)
149+
runs-on: ${{ matrix.os }}
150+
strategy:
151+
fail-fast: false
152+
matrix:
153+
os: [ubuntu-latest, macos-latest]
154+
needs: [build, deploy-testpypi]
155+
if: |
156+
github.repository == 'MDAnalysis/MDAnalysisData' &&
157+
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
158+
steps:
159+
- name: Set up Python
160+
uses: actions/setup-python@v6
161+
with:
162+
python-version: "3.14"
163+
164+
- name: Checkout repository for actions
165+
uses: actions/checkout@v6
166+
with:
167+
path: source # put code under different path and do not cd to avoid interference with pytest invocation (missing authors.py)
168+
169+
- name: Wait for version to be available on TestPyPI
170+
uses: ./source/.github/actions/wait-for-pypi-version
171+
with:
172+
repository: testpypi
173+
package: MDAnalysisData
174+
version: ${{ needs.build.outputs.version }}
175+
176+
- name: Install from TestPyPI
177+
run: |
178+
python -m pip install --upgrade pip
179+
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ "MDAnalysisData[test]==${{ needs.build.outputs.version }}"
180+
181+
- name: Test import
182+
run: |
183+
python -c "import MDAnalysisData; print(f'Package {MDAnalysisData.__version__} imported successfully from TestPyPi')"
184+
185+
- name: Run tests
186+
run: |
187+
pytest --verbose -m "not online" --pyargs MDAnalysisData
188+
189+
test-deployed-pypi:
190+
name: Test deployed package (PyPI)
191+
runs-on: ${{ matrix.os }}
192+
strategy:
193+
fail-fast: false
194+
matrix:
195+
os: [ubuntu-latest, macos-latest]
196+
needs: [build, deploy-pypi]
197+
if: |
198+
github.repository == 'MDAnalysis/MDAnalysisData' &&
199+
(github.event_name == 'release' && github.event.action == 'published')
200+
steps:
201+
- name: Set up Python
202+
uses: actions/setup-python@v6
203+
with:
204+
python-version: "3.14"
205+
206+
- name: Checkout repository for actions
207+
uses: actions/checkout@v6
208+
with:
209+
path: source # put code under different path and do not cd to avoid interference with pytest invocation (missing authors.py)
210+
211+
- name: Wait for version to be available on PyPI
212+
uses: ./source/.github/actions/wait-for-pypi-version
213+
with:
214+
repository: pypi
215+
package: MDAnalysisData
216+
version: ${{ needs.build.outputs.version }}
217+
218+
- name: Install from PyPI
219+
run: |
220+
python -m pip install --upgrade pip
221+
pip install "MDAnalysisData[test]==${{ needs.build.outputs.version }}"
222+
223+
- name: Test import
224+
run: |
225+
python -c "import MDAnalysisData; print(f'Package {MDAnalysisData.__version__} imported successfully from PyPi')"
226+
227+
- name: Run tests
228+
run: |
229+
pytest --verbose -m "not online" --pyargs MDAnalysisData

0 commit comments

Comments
 (0)