Skip to content

Commit 94ac705

Browse files
authored
Test notebooks (#31)
* Cleanup unnecessary env when bringing up mlflow. * USE_JUPYTER setting and startup script. * Refactor MLflow startup scripts to conditionally disable Jupyter support * Refactor GitHub Actions workflow for Jupyter testing and add notebook execution script * Update MLflow startup script to support detached mode and improve SSH handling * f * Fix Docker command in Jupyter test setup and update MLflow service start command * Only build mlflow when no jupyter. * f
1 parent 35da83d commit 94ac705

File tree

5 files changed

+105
-12
lines changed

5 files changed

+105
-12
lines changed

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ JUPYTER_TOKEN=changeme
1717
GIT_PYTHON_REFRESH=quiet
1818

1919
# container uri for mlflow -- adjust this if you have a remote tracking server
20-
MLFLOW_TRACKING_URI=http://mlflow:8080
20+
# MLFLOW_TRACKING_URI=
2121

2222
MLFLOW_ARTIFACT_DESTINATION=./mlruns
2323
# To use cloud storage for artifacts, uncomment below and provide the necessary locations for credentials.

.github/workflows/tests.yml

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
pull_request:
88

99
jobs:
10-
integration-test:
10+
cli-test:
1111
runs-on: ubuntu-latest
1212

1313
steps:
@@ -19,16 +19,9 @@ jobs:
1919
with:
2020
python-version: "3.12"
2121

22-
- name: Start MLflow server
23-
run: |
24-
MLFLOW_TRACKING_URI=http://localhost:8080 docker compose up -d mlflow
25-
26-
- name: Wait for MLflow server to be ready
22+
- name: Start MLflow server (no jupyter)
2723
run: |
28-
until curl -f http://localhost:8080/health; do
29-
echo "Waiting for MLflow server..."
30-
sleep 5
31-
done
24+
./start_services.sh no-jupyter -d
3225
3326
- name: Install poetry
3427
run: pipx install "poetry == 1.8.5"
@@ -41,3 +34,31 @@ jobs:
4134

4235
- name: Stop MLflow server
4336
run: docker compose down
37+
38+
jupyter-test:
39+
runs-on: ubuntu-latest
40+
41+
steps:
42+
- name: Checkout code
43+
uses: actions/checkout@v3
44+
45+
- name: Set up Python
46+
uses: actions/setup-python@v4
47+
with:
48+
python-version: "3.12"
49+
50+
- name: Start MLflow server
51+
run: |
52+
./start_services.sh -d
53+
54+
- name: Copy test script to Jupyter container
55+
run: |
56+
docker cp tests/notebooks/run.py modelplane-jupyter-1:/app/test_notebooks.py
57+
58+
- name: Execute all notebooks
59+
run: |
60+
docker exec modelplane-jupyter-1 poetry run python /app/test_notebooks.py
61+
62+
63+
- name: Stop MLflow server
64+
run: docker compose down

docker-compose.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ services:
2222
POSTGRES_HOST: ${POSTGRES_HOST}
2323
POSTGRES_PORT: ${POSTGRES_PORT}
2424
MLFLOW_TRACKING_URI: ${MLFLOW_TRACKING_URI}
25-
# MLFLOW_ARTIFACT_TRACKING_URI: ${MLFLOW_ARTIFACT_TRACKING_URI}
2625
MLFLOW_ARTIFACT_DESTINATION: ${MLFLOW_ARTIFACT_DESTINATION}
2726
# if not provided via volume below, GS will not work as artifact store
2827
GOOGLE_APPLICATION_CREDENTIALS: /creds/gcp-key.json
@@ -40,6 +39,11 @@ services:
4039
--port 8080
4140
ports:
4241
- "8080:8080"
42+
healthcheck:
43+
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
44+
interval: 10s
45+
timeout: 5s
46+
retries: 5
4347
volumes:
4448
# Volume only needed for local storage of artifacts
4549
- ./mlruns:/mlruns

start_services.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/bin/bash
2+
3+
# load .env
4+
set -a
5+
source .env
6+
set +a
7+
8+
# Check if SSH agent is running
9+
if ssh-add -l >/dev/null 2>&1; then
10+
SSH_FLAG="--ssh default"
11+
else
12+
echo "SSH agent not running. Proceeding without SSH."
13+
SSH_FLAG=""
14+
fi
15+
16+
# Default values
17+
USE_JUPYTER=true
18+
DETACHED=""
19+
20+
# Parse arguments
21+
for arg in "$@"; do
22+
case $arg in
23+
no-jupyter)
24+
USE_JUPYTER=false
25+
;;
26+
-d)
27+
DETACHED="-d"
28+
;;
29+
esac
30+
done
31+
32+
# Start services based on the options
33+
if [ "$USE_JUPYTER" = "true" ]; then
34+
docker compose down && docker compose build $SSH_FLAG && MLFLOW_TRACKING_URI="http://mlflow:8080" docker compose up $DETACHED
35+
else
36+
docker compose down && docker compose build $SSH_FLAG mlflow && MLFLOW_TRACKING_URI="http://localhost:8080" docker compose up $DETACHED mlflow
37+
fi

tests/notebooks/run.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# This is tested via GitHub Actions (within the Jupyter container), but we
2+
# don't want pytest to run it directly, hence the strange name.
3+
4+
import os
5+
import nbformat
6+
from nbconvert.preprocessors import ExecutePreprocessor
7+
8+
9+
def test_notebooks(notebooks_dir):
10+
notebooks = [f for f in os.listdir(notebooks_dir) if f.endswith(".ipynb")]
11+
if not notebooks:
12+
print(f"No notebooks found in {notebooks_dir}")
13+
return
14+
15+
for notebook in notebooks:
16+
notebook_path = os.path.join(notebooks_dir, notebook)
17+
print(f"Testing notebook: {notebook_path}")
18+
with open(notebook_path) as f:
19+
nb = nbformat.read(f, as_version=4)
20+
ep = ExecutePreprocessor(timeout=600, kernel_name="python3")
21+
try:
22+
ep.preprocess(nb, {"metadata": {"path": notebooks_dir}})
23+
print(f"Notebook {notebook} executed successfully.")
24+
except Exception as e:
25+
print(f"Error executing notebook {notebook}: {e}")
26+
raise
27+
28+
29+
if __name__ == "__main__":
30+
notebooks_dir = "/app/flightpaths"
31+
test_notebooks(notebooks_dir)

0 commit comments

Comments
 (0)