feat(hop): add hop ranges and labeling #8450
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | |