Skip to content

Commit b67986b

Browse files
authored
Merge pull request #27 from Becksteinlab/automate-deployment
updated workflow for deployment
2 parents c06e9a0 + d3be0c5 commit b67986b

9 files changed

Lines changed: 451 additions & 59 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: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
name: Build and upload to PyPI
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 (wheel and sdist)
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/multibind-\([^-]*\)-.*/\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 multibind; print(multibind.__version__)")
57+
pip uninstall -y multibind --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: Checkout repository for test files
75+
uses: actions/checkout@v6
76+
with:
77+
fetch-depth: 0
78+
fetch-tags: true
79+
80+
- name: Set up Python
81+
uses: actions/setup-python@v6
82+
with:
83+
python-version: "3.14"
84+
85+
- name: Download dist files
86+
uses: actions/download-artifact@v7
87+
with:
88+
name: dist-files
89+
path: dist/
90+
91+
- name: Install wheel with test extras
92+
run: |
93+
python -m pip install --upgrade pip
94+
WHEEL_FILE=$(ls dist/*.whl)
95+
pip install "${WHEEL_FILE}[test]"
96+
97+
- name: Test import
98+
run: |
99+
python -c "import multibind; print(f'Package {multibind.__version__} imported successfully')"
100+
101+
- name: Run tests
102+
run: |
103+
cd tests && pytest --verbose
104+
105+
deploy-testpypi:
106+
name: Deploy to TestPyPI
107+
runs-on: ubuntu-latest
108+
needs: [build, test-pytest]
109+
if: |
110+
github.repository == 'BecksteinLab/multibind' &&
111+
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
112+
environment:
113+
name: testpypi
114+
url: https://test.pypi.org/p/multibind
115+
permissions:
116+
id-token: write # IMPORTANT: mandatory for trusted publishing
117+
steps:
118+
- name: Download dist files
119+
uses: actions/download-artifact@v7
120+
with:
121+
name: dist-files
122+
path: dist/
123+
124+
- name: Publish to TestPyPI
125+
uses: pypa/gh-action-pypi-publish@release/v1
126+
with:
127+
repository-url: https://test.pypi.org/legacy/
128+
129+
deploy-pypi:
130+
name: Deploy to PyPI
131+
runs-on: ubuntu-latest
132+
needs: [build, test-pytest]
133+
if: |
134+
github.repository == 'BecksteinLab/multibind' &&
135+
(github.event_name == 'release' && github.event.action == 'published')
136+
environment:
137+
name: pypi
138+
url: https://pypi.org/p/multibind
139+
permissions:
140+
id-token: write # IMPORTANT: mandatory for trusted publishing
141+
steps:
142+
- name: Download dist files
143+
uses: actions/download-artifact@v7
144+
with:
145+
name: dist-files
146+
path: dist/
147+
148+
- name: Publish to PyPI
149+
uses: pypa/gh-action-pypi-publish@release/v1
150+
151+
test-deployed-testpypi:
152+
name: Test deployed package (TestPyPI)
153+
runs-on: ${{ matrix.os }}
154+
strategy:
155+
fail-fast: false
156+
matrix:
157+
os: [ubuntu-latest]
158+
needs: [build, deploy-testpypi]
159+
if: |
160+
github.repository == 'BecksteinLab/multibind' &&
161+
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
162+
steps:
163+
- name: Set up Python
164+
uses: actions/setup-python@v6
165+
with:
166+
python-version: "3.14"
167+
168+
- name: Checkout repository for actions
169+
uses: actions/checkout@v6
170+
171+
- name: Wait for version to be available on TestPyPI
172+
uses: ./.github/actions/wait-for-pypi-version
173+
with:
174+
repository: testpypi
175+
package: multibind
176+
version: ${{ needs.build.outputs.version }}
177+
178+
- name: Install from TestPyPI
179+
run: |
180+
python -m pip install --upgrade pip
181+
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ "multibind[test]==${{ needs.build.outputs.version }}"
182+
183+
- name: Test import
184+
run: |
185+
python -c "import multibind; print(f'Package {multibind.__version__} imported successfully from TestPyPI')"
186+
187+
- name: Run tests
188+
run: |
189+
git clone --depth 1 https://github.com/${{ github.repository }}.git _src
190+
cd _src/tests/
191+
pytest -v
192+
193+
test-deployed-pypi:
194+
name: Test deployed package (PyPI)
195+
runs-on: ${{ matrix.os }}
196+
strategy:
197+
fail-fast: false
198+
matrix:
199+
os: [ubuntu-latest]
200+
needs: [build, deploy-pypi]
201+
if: |
202+
github.repository == 'BecksteinLab/multibind' &&
203+
(github.event_name == 'release' && github.event.action == 'published')
204+
steps:
205+
- name: Set up Python
206+
uses: actions/setup-python@v6
207+
with:
208+
python-version: "3.14"
209+
210+
- name: Checkout repository for actions
211+
uses: actions/checkout@v6
212+
213+
- name: Wait for version to be available on PyPI
214+
uses: ./.github/actions/wait-for-pypi-version
215+
with:
216+
repository: pypi
217+
package: multibind
218+
version: ${{ needs.build.outputs.version }}
219+
220+
- name: Install from PyPI
221+
run: |
222+
python -m pip install --upgrade pip
223+
pip install "multibind[test]==${{ needs.build.outputs.version }}"
224+
225+
- name: Test import
226+
run: |
227+
python -c "import multibind; print(f'Package {multibind.__version__} imported successfully from PyPI')"
228+
229+
- name: Run tests
230+
run: |
231+
git clone --depth 1 https://github.com/${{ github.repository }}.git _src
232+
cd _src/tests/
233+
pytest -v

.github/workflows/tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ jobs:
1818
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
1919

2020
steps:
21-
- uses: actions/checkout@v4
21+
- uses: actions/checkout@v6
2222
- name: Set up Python ${{ matrix.python-version }}
23-
uses: actions/setup-python@v5
23+
uses: actions/setup-python@v6
2424
with:
2525
python-version: ${{ matrix.python-version }}
2626

@@ -31,7 +31,7 @@ jobs:
3131
3232
- name: Install multibind with poetry
3333
run: |
34-
poetry install
34+
poetry install --extras dev
3535
3636
- name: Test with pytest
3737
run: |

CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
orbeckst
33

44
Support Python 3.10 - 3.14.
5+
Package version is derived from Git tags via versioningit (setuptools build backend).
56

67

78
2022-02-22 0.2.0

0 commit comments

Comments
 (0)