Skip to content

feat(hop): add hop ranges and labeling #8450

feat(hop): add hop ranges and labeling

feat(hop): add hop ranges and labeling #8450

Workflow file for this run

name: CI Tests
on:
#NOTE: All jobs gated by auth job
#Regular dev
push:
pull_request:
#Enable UI-driven branch testing
workflow_dispatch:
#Test main bidaily @ 1a
schedule:
- cron: '0 1 1-31/2 * *'
jobs:
changes:
# Determine which files changed to run only relevant jobs
runs-on: ubuntu-latest
outputs:
python: ${{ steps.filter.outputs.python }}
docs: ${{ steps.filter.outputs.docs }}
infra: ${{ steps.filter.outputs.infra }}
docs_only_latest: ${{ steps.docs_only_latest.outputs.docs_only_latest }}
steps:
- uses: actions/checkout@v3
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
# Infrastructure changes that affect all tests
infra:
- '.github/workflows/ci.yml'
- 'docker/**'
- 'bin/**'
- 'setup.py'
- 'setup.cfg'
- 'MANIFEST.in'
# Python code changes
python:
- '**.py'
- 'graphistry/**'
- 'setup.py'
- 'setup.cfg'
- 'pytest.ini'
- 'mypy.ini'
- 'bin/lint.sh'
- 'bin/typecheck.sh'
# Documentation changes
docs:
- 'docs/**'
- '**.md'
- '**.rst'
- 'demos/**'
- 'notebooks/**'
- name: Detect docs-only change on tip
id: docs_only_latest
run: |
# Compare the latest commit to its immediate predecessor (push)
# or to the PR base (pull_request) to see if ONLY docs files changed.
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
# Compare the tip commit to its immediate parent to see if the latest commit is docs-only.
base_ref=$(git rev-parse HEAD^ 2>/dev/null || true)
base_ref=${base_ref:-${{ github.event.pull_request.base.sha }}}
else
base_ref="${{ github.event.before }}"
base_ref=${base_ref:-HEAD^}
fi
changed_files=$(git diff --name-only "${base_ref}" HEAD || true)
docs_only=true
for f in $changed_files; do
if [[ "$f" == "README.md" || "$f" == "CHANGELOG.md" || "$f" == docs/* || "$f" == demos/* || "$f" == notebooks/* || "$f" == *.md || "$f" == *.rst ]]; then
continue
else
docs_only=false
break
fi
done
echo "docs_only_latest=${docs_only}" >> "$GITHUB_OUTPUT"
python-lint-types:
needs: changes
# Run if Python files changed OR infrastructure changed OR manual/scheduled run
if: ${{ (needs.changes.outputs.python == 'true' || needs.changes.outputs.infra == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule') && !(needs.changes.outputs.docs_only_latest == 'true' && (github.event_name == 'push' || github.event_name == 'pull_request')) }}
runs-on: ubuntu-latest
timeout-minutes: 5
strategy:
matrix:
python-version: [3.8, 3.9, '3.10', 3.11, 3.12, '3.13', '3.14'] # Run lint/types on all versions
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
lfs: true
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m venv pygraphistry
source pygraphistry/bin/activate
python -m pip install --upgrade pip
python -m pip install -e .[test]
- name: Lint
run: |
source pygraphistry/bin/activate
./bin/lint.sh
- name: Type check
run: |
source pygraphistry/bin/activate
./bin/typecheck.sh
test-minimal-python:
needs: [changes, python-lint-types]
# Run if Python files changed OR infrastructure changed OR manual/scheduled run
if: ${{ (needs.changes.outputs.python == 'true' || needs.changes.outputs.infra == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule') && !(needs.changes.outputs.docs_only_latest == 'true' && (github.event_name == 'push' || github.event_name == 'pull_request')) }}
runs-on: ubuntu-latest
timeout-minutes: 6
strategy:
matrix:
python-version: [3.8, 3.9, '3.10', 3.11, 3.12, '3.13', '3.14']
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
lfs: true
- name: Checkout LFS objects
run: git lfs pull
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install test dependencies
run: |
python -m venv pygraphistry
source pygraphistry/bin/activate
python -m pip install --upgrade pip
python -m pip install -e .[test]
- name: Test pip install (Docker)
env:
PYTHON_VERSION: ${{ matrix.python-version }}
run: |
./docker/test-pip-install.sh
- name: Minimal tests
run: |
source pygraphistry/bin/activate
./bin/test-minimal.sh
test-core-python:
needs: [ test-minimal-python ]
# Inherit condition from test-minimal-python
if: ${{ success() }}
runs-on: ubuntu-latest
timeout-minutes: 5
strategy:
matrix:
python-version: [3.8, 3.9, '3.10', 3.11, 3.12, '3.13', '3.14']
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
lfs: true
- name: Checkout LFS objects
run: git lfs pull
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install test dependencies
run: |
python -m venv pygraphistry
source pygraphistry/bin/activate
python -m pip install --upgrade pip
python -m pip install -e .[test,build,bolt,igraph,networkx,gremlin,nodexl,jupyter]
- name: Core tests
run: |
source pygraphistry/bin/activate
./bin/test.sh
test-graphviz:
needs: [ test-minimal-python ]
# Inherit condition from test-minimal-python
if: ${{ success() }}
runs-on: ubuntu-latest
timeout-minutes: 10 # Accommodate retry logic: 3 attempts × 2min + 1.5min backoff/cleanup
strategy:
matrix:
python-version: [3.8, 3.9, '3.10', 3.11, 3.12, '3.13', '3.14']
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
lfs: true
- name: Checkout LFS objects
run: git lfs pull
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install system dependencies
run: |
# Install graphviz system packages (typically fast: 10-30s)
sudo apt-get install -y graphviz graphviz-dev
- name: Install Python dependencies with retry
run: |
python -m venv pygraphistry
source pygraphistry/bin/activate
# Retry pip install up to 3 times with exponential backoff
# This handles network issues and transient PyPI problems
for attempt in 1 2 3; do
echo "==== Attempt $attempt of 3 ===="
if python -m pip install -e .[test,pygraphviz] \
--timeout 120 \
--retries 3; then
echo "✅ Installation successful on attempt $attempt"
break
fi
if [ $attempt -lt 3 ]; then
wait_time=$((attempt * 30))
echo "⚠️ Installation failed, retrying in ${wait_time}s..."
# Clear pip cache after failure to avoid bad intermediate state
echo "🧹 Clearing pip cache to ensure fresh state..."
python -m pip cache purge || true
sleep $wait_time
else
echo "❌ Installation failed after 3 attempts"
exit 1
fi
done
- name: Graphviz tests
run: |
source pygraphistry/bin/activate
./bin/test-graphviz.sh
test-core-umap:
needs: [ test-minimal-python ]
# Inherit condition from test-minimal-python
if: ${{ success() }}
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
# RAPIDS (umap-learn/numba) lacks Python 3.14 wheels as of 2025-11, so keep <=3.13 here
python-version: [3.9, '3.10', 3.11, 3.12, '3.13']
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
lfs: true
- name: Checkout LFS objects
run: git lfs pull
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Set HF cache env
run: |
echo "HF_HOME=${RUNNER_TEMP}/hf-cache" >> "$GITHUB_ENV"
echo "HF_HUB_CACHE=${RUNNER_TEMP}/hf-cache" >> "$GITHUB_ENV"
echo "PIP_EXTRA_INDEX_URL=https://download.pytorch.org/whl/cpu" >> "$GITHUB_ENV"
echo "PIP_PREFER_BINARY=1" >> "$GITHUB_ENV"
- name: Prepare HF cache directory
run: |
mkdir -p "${HF_HOME}"
- name: Restore HF cache
id: hf-cache
uses: actions/cache@v4
with:
path: ${{ env.HF_HOME }}
key: hf-cache-${{ runner.os }}-v1
restore-keys: |
hf-cache-${{ runner.os }}-
- name: Install test dependencies
run: |
python -m venv pygraphistry
source pygraphistry/bin/activate
python -m pip install --upgrade pip
python -m pip install --upgrade torch==2.8.0+cpu -f https://download.pytorch.org/whl/cpu
python -m pip install -e .[test,testai,umap-learn]
- name: Warm HF cache (sentence-transformers)
id: warm-hf
if: steps['hf-cache'].outputs['cache-hit'] != 'true'
run: |
mkdir -p "${HF_HOME}"
source pygraphistry/bin/activate
# Ensure sentence_transformers is available for warm step
python -m pip install --upgrade sentence-transformers
python - <<'PY'
import os
from pathlib import Path
from sentence_transformers import SentenceTransformer
models = [
"sentence-transformers/average_word_embeddings_komninos",
"sentence-transformers/paraphrase-MiniLM-L6-v2",
"sentence-transformers/paraphrase-albert-small-v2",
]
cache_dir = os.environ["HF_HOME"]
status_file = Path(cache_dir) / ".hf_cache_warmed"
status_file.parent.mkdir(parents=True, exist_ok=True)
success = True
for model in models:
try:
SentenceTransformer(model, cache_folder=cache_dir)
except Exception as exc: # pragma: no cover - logging only
success = False
print(f"⚠️ HF warm failed for {model}: {exc}")
status_file.write_text("ok\n" if success else "partial\n")
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh:
fh.write(f"success={str(success).lower()}\n")
print(f"✅ HF cache warm {'succeeded' if success else 'partial/fail'} at {cache_dir}")
PY
- name: Enable HF offline for tests
run: |
cache_hit="${{ steps['hf-cache'].outputs['cache-hit'] }}"
warm_success="${{ steps['warm-hf'].outputs.success }}"
if [ "${cache_hit}" = "true" ] || [ "${warm_success}" = "true" ]; then
echo "HF_HUB_OFFLINE=1" >> "$GITHUB_ENV"
echo "Using HF cache at ${HF_HOME} (cache-hit: ${cache_hit}, warm_success: ${warm_success})"
else
echo "HF_HUB_OFFLINE=0" >> "$GITHUB_ENV"
echo "HF cache not available; HF-dependent tests may skip or go online."
fi
- name: Core feature tests (weak featurize)
run: |
source pygraphistry/bin/activate
./bin/test-features.sh
- name: Core umap tests (weak featurize)
run: |
source pygraphistry/bin/activate
./bin/test-umap-learn-core.sh
test-full-ai:
needs: [ test-minimal-python ]
# Inherit condition from test-minimal-python
if: ${{ success() }}
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
# RAPIDS stack not available on Python 3.14 yet
python-version: [3.9, '3.10', 3.11, 3.12, '3.13']
#include:
# - python-version: 3.12
# continue-on-error: true
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
lfs: true
- name: Checkout LFS objects
run: git lfs pull
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Set HF cache env
run: |
echo "HF_HOME=${RUNNER_TEMP}/hf-cache" >> "$GITHUB_ENV"
echo "HF_HUB_CACHE=${RUNNER_TEMP}/hf-cache" >> "$GITHUB_ENV"
echo "PIP_EXTRA_INDEX_URL=https://download.pytorch.org/whl/cpu" >> "$GITHUB_ENV"
echo "PIP_PREFER_BINARY=1" >> "$GITHUB_ENV"
- name: Prepare HF cache directory
run: |
mkdir -p "${HF_HOME}"
- name: Restore HF cache
id: hf-cache
uses: actions/cache@v4
with:
path: ${{ env.HF_HOME }}
key: hf-cache-${{ runner.os }}-v1
restore-keys: |
hf-cache-${{ runner.os }}-
- name: Install test dependencies
run: |
python -m venv pygraphistry
source pygraphistry/bin/activate
python -m pip install --upgrade pip
python -m pip install --upgrade torch==2.8.0+cpu -f https://download.pytorch.org/whl/cpu
python -m pip install -e .[test,testai,ai]
echo "skrub: `pip show skrub | grep Version`"
echo "pandas: `pip show pandas | grep Version`"
echo "numpy: `pip show numpy | grep Version`"
echo "scikit-learn: `pip show scikit-learn | grep Version`"
echo "scipy: `pip show scipy | grep Version`"
echo "umap-learn: `pip show umap-learn | grep Version`"
- name: Warm HF cache (sentence-transformers)
id: warm-hf
if: steps['hf-cache'].outputs['cache-hit'] != 'true'
run: |
mkdir -p "${HF_HOME}"
source pygraphistry/bin/activate
# Ensure sentence_transformers is available for warm step
python -m pip install --upgrade sentence-transformers
python - <<'PY'
import os
from pathlib import Path
from sentence_transformers import SentenceTransformer
models = [
"sentence-transformers/average_word_embeddings_komninos",
"sentence-transformers/paraphrase-MiniLM-L6-v2",
"sentence-transformers/paraphrase-albert-small-v2",
]
cache_dir = os.environ["HF_HOME"]
status_file = Path(cache_dir) / ".hf_cache_warmed"
status_file.parent.mkdir(parents=True, exist_ok=True)
success = True
for model in models:
try:
SentenceTransformer(model, cache_folder=cache_dir)
except Exception as exc: # pragma: no cover - logging only
success = False
print(f"⚠️ HF warm failed for {model}: {exc}")
status_file.write_text("ok\n" if success else "partial\n")
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh:
fh.write(f"success={str(success).lower()}\n")
print(f"✅ HF cache warm {'succeeded' if success else 'partial/fail'} at {cache_dir}")
PY
- name: Enable HF offline for tests
run: |
cache_hit="${{ steps['hf-cache'].outputs['cache-hit'] }}"
warm_success="${{ steps['warm-hf'].outputs.success }}"
if [ "${cache_hit}" = "true" ] || [ "${warm_success}" = "true" ]; then
echo "HF_HUB_OFFLINE=1" >> "$GITHUB_ENV"
echo "Using HF cache at ${HF_HOME} (cache-hit: ${cache_hit}, warm_success: ${warm_success})"
else
echo "HF_HUB_OFFLINE=0" >> "$GITHUB_ENV"
echo "HF cache not available; HF-dependent tests may skip or go online."
fi
- name: Full dbscan tests (rich featurize)
run: |
source pygraphistry/bin/activate
./bin/test-dbscan.sh
- name: Full feature tests (rich featurize)
run: |
source pygraphistry/bin/activate
./bin/test-features.sh
- name: Full search tests (rich featurize)
run: |
source pygraphistry/bin/activate
./bin/test-text.sh
- name: Full umap tests (rich featurize)
run: |
source pygraphistry/bin/activate
./bin/test-umap-learn-core.sh
test-dgl-cpu:
needs: [changes, test-minimal-python]
runs-on: ubuntu-latest
timeout-minutes: 15
env:
PIP_EXTRA_INDEX_URL: https://download.pytorch.org/whl/cpu
PIP_PREFER_BINARY: "1"
DGLBACKEND: pytorch
steps:
- uses: actions/checkout@v3
with:
lfs: true
- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install DGL stack (CPU, legacy)
run: |
python -m venv pygraphistry
source pygraphistry/bin/activate
python -m pip install --upgrade pip
cat <<'EOF' > /tmp/dgl-constraints.txt
torch==2.0.1
torchdata==0.6.1
dgl==2.1.0
sentence-transformers==5.1.2
faiss-cpu
EOF
python -m pip install --constraint /tmp/dgl-constraints.txt torch==2.0.1 torchdata==0.6.1 dgl==2.1.0
python -m pip install --constraint /tmp/dgl-constraints.txt -e .[test,testai,ai,dgl-cpu]
- name: Run DGL tests
env:
HF_HUB_OFFLINE: 1
HF_HOME: /tmp/hf-cache
HF_HUB_CACHE: /tmp/hf-cache
run: |
source pygraphistry/bin/activate
mkdir -p "${HF_HOME}"
pytest graphistry/tests/test_embed_utils.py graphistry/tests/test_dgl_utils.py -vv --disable-warnings -x
./bin/test-embed.sh
./bin/test-dgl.sh
test-neo4j:
needs: [ test-minimal-python ]
# Inherit condition from test-minimal-python
if: ${{ success() }}
runs-on: ubuntu-latest
timeout-minutes: 3
env:
COMPOSE_DOCKER_CLI_BUILD: 1
DOCKER_BUILDKIT: 1
steps:
- uses: actions/checkout@v3
with:
lfs: true
- name: Checkout LFS objects
run: git lfs pull
- name: Neo4j connector tests
run: |
cd docker && WITH_SUDO=" " ./test-cpu-local-neo4j-only.sh
test-build:
needs: [ test-minimal-python ]
# Inherit condition from test-minimal-python
if: ${{ success() }}
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- uses: actions/checkout@v3
with:
lfs: true
- name: Checkout LFS objects
run: git lfs pull
- name: Set up Python 3.8
uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -e .[build]
- name: Test building
run: |
./bin/build.sh
- name: Validate py.typed in wheel
run: |
unzip -l dist/graphistry*.whl | grep -q "graphistry/py.typed" || (echo "ERROR: py.typed marker missing from wheel - users won't get type information" && exit 1)
echo "✅ py.typed marker confirmed in wheel distribution"
test-docs:
needs: [changes, python-lint-types]
# Run if docs changed OR Python changed OR infrastructure changed OR manual/scheduled run
if: ${{ needs.changes.outputs.docs == 'true' || needs.changes.outputs.python == 'true' || needs.changes.outputs.infra == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' }}
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- name: Test building docs
env:
VALIDATE_NOTEBOOK_EXECUTION: 1
run: |
cd docs && ./ci.sh
test-readme:
needs: [changes]
# Run if docs changed OR infrastructure changed OR manual/scheduled run
if: ${{ needs.changes.outputs.docs == 'true' || needs.changes.outputs.infra == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' }}
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Test building docs
continue-on-error: true
run: |
docker run --rm -v "$(pwd)/README.md:/workdir/README.md:ro" -v "$(pwd)/.markdownlint.yaml:/workdir/.markdownlint.yaml:ro" ghcr.io/igorshubovych/markdownlint-cli:v0.37.0 README.md