Skip to content

Commit 1a6bc6d

Browse files
authored
Update PyRosettaCluster extra unit tests and extra documentation for PyRosetta quarterly builds (#7)
This PR adds: 1. new GitHub Actions jobs involving the PyRosetta quarterly builds 2. extra documentation on installing PyRosetta quarterly builds 3. template environment configuration files to get started 4. a PyRosettaCluster logo
1 parent 4c0aea6 commit 1a6bc6d

File tree

17 files changed

+383
-35
lines changed

17 files changed

+383
-35
lines changed

.github/workflows/pyrosettacluster.yml

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ jobs:
5656
5757
5858
# ============================================================
59-
# UV JOB
59+
# UV JOB (QUARTERLY RELEASES)
6060
# ============================================================
6161
uv:
62-
name: Uv
62+
name: Uv (quarterly)
6363
runs-on: ubuntu-latest
6464
strategy:
6565
fail-fast: false
@@ -90,6 +90,41 @@ jobs:
9090
python -u -m unittest actions.pyrosettacluster.test_env_reproducibility.TestEnvironmentReproducibility.test_recreate_environment_uv -v
9191
9292
93+
# ============================================================
94+
# UV JOB (WEEKLY RELEASES)
95+
# ============================================================
96+
uv_pyrosetta_installer:
97+
name: Uv (pyrosetta-installer)
98+
runs-on: ubuntu-latest
99+
strategy:
100+
fail-fast: false
101+
matrix:
102+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
103+
steps:
104+
- uses: actions/checkout@v4
105+
106+
- name: Set up Python
107+
uses: actions/setup-python@v4
108+
with:
109+
python-version: ${{ matrix.python-version }}
110+
111+
- name: Install uv
112+
run: |
113+
curl -LsSf https://astral.sh/uv/install.sh | sh
114+
echo "$HOME/.local/bin" >> $GITHUB_PATH
115+
uv --version
116+
117+
- name: Add repo root to PYTHONPATH
118+
run: echo "PYTHONPATH=$PYTHONPATH:${{ github.workspace }}" >> $GITHUB_ENV
119+
120+
- name: Run tests (uv)
121+
shell: bash
122+
run: |
123+
which python
124+
python --version
125+
python -u -m unittest actions.pyrosettacluster.test_env_reproducibility.TestEnvironmentReproducibility.test_recreate_environment_uv_pyrosetta_installer -v
126+
127+
93128
# ============================================================
94129
# CONDA JOB
95130
# ============================================================

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,18 @@
44
> Downloads of PyRosetta software are provided to academic and non-commercial users under the [PyRosetta Software Non-Commercial License Agreement](https://github.com/RosettaCommons/rosetta/blob/main/LICENSE.PyRosetta.md).
55
> While scripts in this GitHub repository are made publicly available under the [MIT License](https://github.com/RosettaCommons/pyrosetta-extras/blob/main/LICENSE), the scripts themselves may require download and installation of PyRosetta software to run properly, and therefore use of the scripts for commercial purposes (including fee-for-service work by academic users) may require purchase of a separate license. Please see [here](https://els2.comotion.uw.edu/product/pyrosetta) or email license@uw.edu for more information.
66
7-
## ✏️ Contributions:
7+
---
8+
9+
## 🗂️ Features
10+
<div align="left">
11+
<a href="pyrosettacluster/">
12+
<img src="pyrosettacluster/images/title.png" width="67%" />
13+
</a>
14+
</div>
15+
16+
- Visit the [PyRosettaCluster extra documentation](pyrosettacluster) for more info.
17+
18+
---
19+
20+
## ✏️ Contributions
821
Please use Rosetta's [Fork-and-PR model](https://github.com/RosettaCommons/rosetta?tab=contributing-ov-file#starting-rosetta-developement) for development. See https://www.rosettacommons.org/docs/latest/internal_documentation/GithubWorkflow for more information about the recommended workflow.

actions/pyrosettacluster/setup_envs.py

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
from pathlib import Path
1616

1717
from actions.pyrosettacluster.utils import (
18+
PYROSETTA_FIND_LINKS_PATH,
1819
ROSETTACOMMONS_CONDA_CHANNEL,
20+
USE_WEST_MIRROR,
1921
detect_platform,
2022
)
2123

@@ -63,8 +65,13 @@ def setup_pixi_environment(env_dir, timeout):
6365
print(f"Pixi environment setup complete in directory: '{env_path}'.")
6466

6567

66-
def setup_uv_environment(env_dir, timeout):
68+
def setup_uv_environment_pyrosetta_installer(env_dir, timeout):
6769
"""
70+
*Deprecated* This uv project setup function uses the 'pyrosetta-installer' package,
71+
which does not support long-term reproducibility of PyRosetta package installations.
72+
Please see the `setup_uv_environment` function, which instead implements the quarterly
73+
PyRosetta package installations that are maintained for long-term reproducibility.
74+
6875
Create a fresh uv environment using the 'pyrosetta-installer' package.
6976
7077
Note: this requires that `uv` is an executable installed and on `${PATH}`. This function:
@@ -80,7 +87,7 @@ def setup_uv_environment(env_dir, timeout):
8087
print(f"Creating uv environment at '{env_path}'...")
8188
os.makedirs(env_dir, exist_ok=True)
8289
py_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
83-
template_toml_file = Path(__file__).resolve().parent / "uv" / "pyproject.toml"
90+
template_toml_file = Path(__file__).resolve().parent / "uv" / "pyproject_pyrosetta_installer.toml"
8491
with open(template_toml_file, "r") as f:
8592
toml_data = f.read().format(
8693
name=os.path.basename(env_dir),
@@ -91,8 +98,8 @@ def setup_uv_environment(env_dir, timeout):
9198
f.write(toml_data)
9299

93100
# Install pyrosetta-installer
94-
print("Adding 'pyrosetta-installer', 'pip', and `pyrosetta.distributed` depedencies to uv environment...")
95-
requirements_txt_file = Path(__file__).resolve().parent / "uv" / "requirements.txt"
101+
print("Adding 'pyrosetta-installer', 'pip', and `pyrosetta.distributed` dependencies to uv environment...")
102+
requirements_txt_file = Path(__file__).resolve().parent / "uv" / "requirements_pyrosetta_installer.txt"
96103
subprocess.run(
97104
[
98105
"uv",
@@ -108,9 +115,55 @@ def setup_uv_environment(env_dir, timeout):
108115
# Run PyRosetta installer with mirror fallback
109116
print("Running PyRosetta installer in uv environment...")
110117
install_pyrosetta_file = Path(__file__).resolve().parent.parent.parent / "pyrosettacluster" / "install_pyrosetta.py"
118+
mirror_order_str = "0 1" if USE_WEST_MIRROR else "1 0"
119+
subprocess.run(
120+
["uv", "run", "--project", str(env_path), "python", install_pyrosetta_file, "--mirror_order", *mirror_order_str.split()],
121+
check=True,
122+
timeout=timeout,
123+
)
124+
125+
print(f"Uv environment setup complete in directory: '{env_path}'.")
126+
127+
128+
def setup_uv_environment(env_dir, timeout):
129+
"""
130+
Create a fresh uv environment using the PyRosetta quarterly builds for long-term reproducibility.
131+
132+
Note: this requires that `uv` is an executable installed and on `${PATH}`. This function:
133+
- detects the current Python version
134+
- adds PyRosetta and `pyrosetta.distributed` dependencies via `uv add ...`
135+
"""
136+
env_path = Path(env_dir)
137+
if env_path.exists():
138+
raise FileExistsError(f"The specified uv environment path already exists: '{env_path}'.")
139+
140+
# Create uv environment using the current Python
141+
print(f"Creating uv environment at '{env_path}'...")
142+
os.makedirs(env_dir, exist_ok=True)
143+
py_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
144+
template_toml_file = Path(__file__).resolve().parent / "uv" / "pyproject.toml"
145+
with open(template_toml_file, "r") as f:
146+
toml_data = f.read().format(
147+
name=os.path.basename(env_dir),
148+
py_version=py_version,
149+
pyrosetta_find_links_path=PYROSETTA_FIND_LINKS_PATH,
150+
)
151+
toml_file = env_path / "pyproject.toml"
152+
with open(toml_file, "w") as f:
153+
f.write(toml_data)
154+
155+
# Install pyrosetta-installer
156+
print("Adding PyRosetta and `pyrosetta.distributed` dependencies to uv environment...")
157+
requirements_txt_file = Path(__file__).resolve().parent / "uv" / "requirements.txt"
111158
subprocess.run(
112-
["uv", "run", "--project", str(env_path), "python", install_pyrosetta_file],
159+
[
160+
"uv",
161+
"add",
162+
"--project", str(env_path),
163+
"--requirements", str(requirements_txt_file),
164+
],
113165
check=True,
166+
cwd=str(env_path),
114167
timeout=timeout,
115168
)
116169

@@ -172,11 +225,23 @@ def setup_conda_environment(env_dir, timeout, env_manager="conda"):
172225
parser.add_argument('--env_manager', type=str)
173226
parser.add_argument('--env_dir', type=str)
174227
parser.add_argument('--timeout', type=float)
228+
parser.add_argument('--use_pyrosetta_installer', dest='use_pyrosetta_installer', action='store_true')
229+
parser.set_defaults(use_pyrosetta_installer=False)
175230
args = parser.parse_args()
231+
# Validate
232+
if args.use_pyrosetta_installer and args.env_manager != "uv":
233+
raise ValueError(
234+
"If passing the '--use_pyrosetta_installer' flag, the '--env_manager' flag "
235+
f"must be set to 'uv'. Received: '{args.env_manager}'"
236+
)
237+
# Run setup
176238
if args.env_manager == "pixi":
177239
setup_pixi_environment(args.env_dir, args.timeout)
178240
elif args.env_manager == "uv":
179-
setup_uv_environment(args.env_dir, args.timeout)
241+
if args.use_pyrosetta_installer:
242+
setup_uv_environment_pyrosetta_installer(args.env_dir, args.timeout)
243+
else:
244+
setup_uv_environment(args.env_dir, args.timeout)
180245
elif args.env_manager in ("conda", "mamba"):
181246
setup_conda_environment(args.env_dir, args.timeout, env_manager=args.env_manager)
182247
else:

actions/pyrosettacluster/test_env_reproducibility.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
from pathlib import Path
1919

2020

21+
from actions.pyrosettacluster.utils import USE_WEST_MIRROR
22+
23+
2124
class TestEnvironmentReproducibility(unittest.TestCase):
2225
@classmethod
2326
def setUpClass(cls):
@@ -75,11 +78,15 @@ def run_subprocess(cmd, module_dir=None, cwd=None):
7578
def recreate_environment_test(
7679
self,
7780
environment_manager="conda",
81+
use_pyrosetta_installer=False,
7882
timeout=(3 * 60 * 60), # 3 hours per subprocess
7983
verbose=False,
8084
):
8185
"""Test for PyRosettaCluster decoy reproducibility in a recreated virtual environment."""
8286
self.assertIn(environment_manager, ("conda", "mamba", "uv", "pixi"))
87+
self.assertIsInstance(use_pyrosetta_installer, bool)
88+
if use_pyrosetta_installer:
89+
self.assertEqual(environment_manager, "uv")
8390

8491
test_script = os.path.join(os.path.dirname(__file__), "recreate_environment_test_runs.py")
8592

@@ -94,6 +101,8 @@ def recreate_environment_test(
94101
f"--env_dir '{original_env_dir}' "
95102
f"--timeout '{timeout}'"
96103
)
104+
if use_pyrosetta_installer:
105+
cmd += " --use_pyrosetta_installer"
97106
returncode = TestEnvironmentReproducibility.run_subprocess(
98107
cmd,
99108
module_dir=os.path.dirname(setup_env_script),
@@ -190,11 +199,13 @@ def recreate_environment_test(
190199
f"Reproduced '{environment_manager}' environment directory was not created: '{reproduce_env_dir}'",
191200
)
192201
recreate_env_script = Path(__file__).resolve().parent.parent.parent / "pyrosettacluster" / "recreate_env.py"
202+
mirror_order_str = "0 1" if USE_WEST_MIRROR else "1 0"
193203
cmd = (
194204
f"{sys.executable} -u {recreate_env_script} "
195205
f"--env_dir '{reproduce_env_dir}' "
196206
f"--env_manager '{environment_manager}' "
197-
f"--timeout '{timeout}'"
207+
f"--timeout '{timeout}' "
208+
f"--mirror_order {mirror_order_str}"
198209
)
199210
returncode = TestEnvironmentReproducibility.run_subprocess(
200211
cmd,
@@ -329,6 +340,10 @@ def test_recreate_environment_mamba(self):
329340
def test_recreate_environment_uv(self):
330341
return self.recreate_environment_test(environment_manager="uv")
331342

343+
@unittest.skipIf(shutil.which("uv") is None, "The executable 'uv' is not available.")
344+
def test_recreate_environment_uv_pyrosetta_installer(self):
345+
return self.recreate_environment_test(environment_manager="uv", use_pyrosetta_installer=True)
346+
332347
@unittest.skipIf(shutil.which("pixi") is None, "The executable 'pixi' is not available.")
333348
def test_recreate_environment_pixi(self):
334349
return self.recreate_environment_test(environment_manager="pixi")

actions/pyrosettacluster/utils.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
import platform
77

88

9-
ROSETTACOMMONS_CONDA_CHANNEL = "https://conda.rosettacommons.org" # "https://conda.graylab.jhu.edu"
9+
USE_WEST_MIRROR: bool = True
10+
if USE_WEST_MIRROR:
11+
ROSETTACOMMONS_CONDA_CHANNEL: str = "https://conda.rosettacommons.org"
12+
PYROSETTA_FIND_LINKS_PATH: str = "https://west.rosettacommons.org/pyrosetta/quarterly/release.cxx11thread.serialization"
13+
else:
14+
ROSETTACOMMONS_CONDA_CHANNEL: str = "https://conda.graylab.jhu.edu"
15+
PYROSETTA_FIND_LINKS_PATH: str = "https://graylab.jhu.edu/download/PyRosetta4/archive/release-quarterly/release.cxx11thread.serialization"
1016

1117

1218
def detect_platform():

actions/pyrosettacluster/uv/pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ name = "{name}"
33
version = "0.1.0"
44
requires-python = "=={py_version}"
55
dependencies = []
6+
7+
[tool.uv]
8+
find-links = ['{pyrosetta_find_links_path}']
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[project]
2+
name = "{name}"
3+
version = "0.1.0"
4+
requires-python = "=={py_version}"
5+
dependencies = []

actions/pyrosettacluster/uv/requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ gitpython>=3.1.1
1010
jupyter>=1.0.0
1111
numpy>=1.17.3
1212
pandas>=0.25.2
13-
pip>=25.3
1413
py3Dmol>=0.8.0
15-
pyrosetta-installer>=0.1.2
14+
pyrosetta>=2026.3
1615
python-xz>=0.4.0
1716
scipy>=1.4.1
1817
traitlets>=4.3.3
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
attrs>=19.3.0
2+
billiard>=3.6.3.0
3+
blosc>=1.8.3
4+
cloudpickle>=1.5.0
5+
cryptography>=2.8
6+
dask>=2.16.0
7+
dask-jobqueue>=0.7.0
8+
distributed>=2.16.0
9+
gitpython>=3.1.1
10+
jupyter>=1.0.0
11+
numpy>=1.17.3
12+
pandas>=0.25.2
13+
pip>=25.3
14+
py3Dmol>=0.8.0
15+
pyrosetta-installer>=0.1.2
16+
python-xz>=0.4.0
17+
scipy>=1.4.1
18+
traitlets>=4.3.3

0 commit comments

Comments
 (0)