fix: support Python 3.14 optional dependencies - #3490
Conversation
# Conflicts: # pyproject.toml
There was a problem hiding this comment.
🟡 Changes recommended
The optional-backend test can fail without its dependency, and documentation updates remain pending.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Updates Python 3.13/3.14 dependency handling, native Leiden support, dependency floors, and CI coverage.
Changes:
- Adds version-specific Leiden and tokenizer dependencies.
- Preserves isolated nodes in native Leiden partitions.
- Updates dependency locks, PostgreSQL extras, tests, and CI.
File summaries
| File | Summary | Review comments |
|---|---|---|
uv.lock |
Locks updated dependency resolutions. | — |
tests/test_serve.py |
Tests strict-warning imports. | — |
tests/test_cluster.py |
Tests native Leiden completeness. | Moderate (1 vote): Skip when the optional native backend is unavailable. |
tests/test_build_merge_hyperedges_and_prune.py |
Fixes a warning-producing docstring. | — |
tests/test_backend_extras.py |
Verifies PostgreSQL in [all]. |
— |
pyproject.toml |
Updates extras and dependency floors. | Nit (3 votes): Update documentation describing the Leiden extra as Python < 3.13 only. |
graphify/serve.py |
Suppresses tokenizer syntax warnings. | — |
graphify/cluster.py |
Restores omitted isolated nodes. | — |
.github/workflows/ci.yml |
Adds Python 3.13 and 3.14 CI jobs. | Nit (1 vote): Update README CI-version documentation. |
Review details
Suppressed comments (2)
.github/workflows/ci.yml:53
- The matrix now covers Python 3.13 and 3.14, but README.md:880 and README.md:907 still instruct contributors to use only Python 3.10/3.12 and say CI runs only those versions. Please update the local CI-parity and matrix documentation so it matches this workflow.
python-version: ["3.10", "3.12", "3.13", "3.14"]
tests/test_cluster.py:146
- This test only skips on Python <3.13, but
graspologic-nativeremains optional and is installed here only through theleiden/allextras (pyproject.toml:66-69, 108). On a Python 3.13+ development environment without those extras, this import raisesModuleNotFoundErrorand makes the suite fail before testing anything. Usepytest.importorskip("graspologic_native")so the test runs when the optional backend is present and otherwise follows the neighboring optional-backend test's skip behavior.
import graspologic_native # noqa: F401
- Files reviewed: 8/9 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| leiden = [ | ||
| "graspologic; python_version < '3.13'", | ||
| "graspologic-native>=1.3.1,<2; python_version >= '3.13'", | ||
| ] |
There was a problem hiding this comment.
Fixed in c49db38. README now documents the version-specific Leiden backends, the Hebrew translation matches, and the local CI-parity documentation lists Python 3.10, 3.12, 3.13, and 3.14.
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.
Formal verification. 2 change(s) tested, no difference found (not proven).
Graphify review — findings
Adds Python 3.13 and 3.14 to the CI matrix and wires up the version-gated optional dependencies to make those interpreters work: graspologic-native for Leiden clustering on 3.13+ and jieba-py for Chinese segmentation on 3.14+. Fixes _native_leiden to return a complete partition by assigning each unclustered/isolated node its own community, so the 3.13+ native-only path runs Leiden instead of silently falling through to Louvain. Suppresses SyntaxWarning while importing jieba so graphify.serve stays importable under -W error::SyntaxWarning, and adds psycopg to the all extra.
Worth a look
- Test requires optional graspologic-native dependency —
tests/test_cluster.py:143· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Test requires optional jieba dependency —
tests/test_serve.py:1131· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 906 functions depend on the 427 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_rebuild_code()— 115 callers, 51 callees - new:
_query_graph_text()— 25 callers, 10 callees - new:
dispatch_command()— 2 callers, 124 callees - new:
_make_graph()— 33 callers, 6 callees - new:
cluster()— 61 callers, 3 callees - new:
run_pipeline()— 8 callers, 13 callees - new:
_score_query()— 15 callers, 6 callees - new:
make_inputs()— 14 callers, 5 callees - …and 20 more — each is listed as a finding
Verification — 906 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 725 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
266 of 266 test file(s) selected (100%) via static blast radius.
Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.
tests/test_affected_cli.py— full-run-safetytests/test_affected_member_seed.py— full-run-safetytests/test_agents_platform.py— full-run-safetytests/test_analyze.py— impact, full-run-safetytests/test_anthropic_custom_endpoint.py— full-run-safetytests/test_antigravity_install.py— full-run-safetytests/test_apm_fallback_version.py— full-run-safetytests/test_architecture_doc.py— full-run-safetytests/test_astro_extraction.py— full-run-safetytests/test_astro_import_ids.py— full-run-safetytests/test_atomic_canvas_export.py— full-run-safetytests/test_atomic_version_stamp.py— full-run-safetytests/test_atomic_writes.py— full-run-safetytests/test_backend_extras.py— impact, changed-test, full-run-safetytests/test_benchmark.py— impact, full-run-safetytests/test_benchmark_raw_graph.py— impact, full-run-safetytests/test_build.py— impact, full-run-safetytests/test_build_merge_hyperedges_and_prune.py— impact, changed-test, full-run-safetytests/test_build_merge_shrink_guard.py— full-run-safetytests/test_builtin_global_type_refs.py— full-run-safetytests/test_cache.py— full-run-safetytests/test_callflow_html.py— full-run-safetytests/test_cargo_introspect.py— full-run-safetytests/test_carried_hyperedge_remap.py— full-run-safetytests/test_case_sensitive_resolution.py— full-run-safetytests/test_charmap_encoding.py— full-run-safetytests/test_chunking.py— full-run-safetytests/test_cjs_module_extension.py— full-run-safetytests/test_claude_cli_backend.py— full-run-safetytests/test_claude_md.py— full-run-safetytests/test_cli_broken_pipe.py— full-run-safetytests/test_cli_export.py— impact, full-run-safetytests/test_cli_help.py— full-run-safetytests/test_cluster.py— impact, changed-test, full-run-safetytests/test_codebuddy.py— full-run-safetytests/test_community_hub_labels.py— impact, full-run-safetytests/test_community_labels_skill.py— full-run-safetytests/test_confidence.py— impact, full-run-safetytests/test_corrupt_graph_json.py— full-run-safetytests/test_cpp_nested_and_cli.py— full-run-safetytests/test_cpp_objc_cross_file_calls.py— full-run-safetytests/test_cpp_preprocess.py— full-run-safetytests/test_cross_extension_reexport_self_cycle.py— full-run-safetytests/test_cross_language_call_resolution.py— full-run-safetytests/test_cross_repo_member_calls.py— full-run-safetytests/test_cross_repo_shared_types.py— full-run-safetytests/test_csharp_call_site_generic_args.py— full-run-safetytests/test_csharp_enum_members.py— full-run-safetytests/test_csharp_field_generic_args.py— full-run-safetytests/test_csharp_generic_callsites.py— full-run-safety- … and 216 more
non-code file(s) changed (
.github/workflows/ci.yml,pyproject.toml,uv.lock) → running the full suite for safety (a code graph can't see config/fixture/data deps)
changed code file(s) with no mapped test (
.github/workflows/ci.yml,pyproject.toml) — a coverage gap or a missing link — running the full suite rather than only the selected tests
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify build.
The verifier did not have enough to check build, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 9 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify build\_merge.
The verifier did not have enough to check build\_merge, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `graph_path` is annotated `str | Path | None` — outside the synthesizable primitive/collection set
No difference found (not proven): No behavior difference found in \_native\_leiden (not a proof).
The verifier ran both versions of \_native\_leiden on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
Could not verify: Could not verify extract\_rust.
The verifier did not have enough to check extract\_rust, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
No difference found (not proven): No behavior difference found in \_pinned\_python (not a proof).
The verifier ran both versions of \_pinned\_python on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
Could not verify: Could not verify install.
The verifier did not have enough to check install, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
Could not verify: Could not verify generate\_community\_labels.
The verifier did not have enough to check generate\_community\_labels, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous
· 1 grounded finding(s) anchored inline below; 27 more finding(s) on lines outside this diff (see the check run).
| return partition | ||
|
|
||
|
|
||
| def _partition(G: nx.Graph, resolution: float = 1.0) -> dict[str, int]: |
There was a problem hiding this comment.
_partition()
high coupling complexity (Ca·Ce = 12).
Grounded coupling-delta finding (deterministic), not an LLM guess.
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Formal verification. 1 change(s) tested, no difference found (not proven).
Graphify review — findings
Adds Python 3.13 and 3.14 to the CI test matrix and provisions the corresponding native dependencies: graspologic-native for Leiden clustering on 3.13+ and jieba-py for Chinese segmentation on 3.14+. Fixes _native_leiden to return a complete partition by assigning each node the native binding omitted (e.g. isolated nodes) its own community, so the 3.13+ native-only path no longer silently falls through to Louvain. Suppresses SyntaxWarning while importing jieba in serve so the module stays importable under -W error::SyntaxWarning.
Worth a look
- Import-time warning suppression leaks across threads —
graphify/serve.py:21· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 908 functions depend on the 429 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_rebuild_code()— 115 callers, 51 callees - new:
_query_graph_text()— 25 callers, 10 callees - new:
dispatch_command()— 2 callers, 124 callees - new:
_make_graph()— 33 callers, 6 callees - new:
cluster()— 61 callers, 3 callees - new:
run_pipeline()— 8 callers, 13 callees - new:
_score_query()— 15 callers, 6 callees - new:
make_inputs()— 14 callers, 5 callees - …and 20 more — each is listed as a finding
Verification — 908 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 727 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
268 of 268 test file(s) selected (100%) via static blast radius.
Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.
tests/test_affected_cli.py— full-run-safetytests/test_affected_member_seed.py— full-run-safetytests/test_agents_platform.py— full-run-safetytests/test_analyze.py— impact, full-run-safetytests/test_anthropic_custom_endpoint.py— full-run-safetytests/test_antigravity_install.py— full-run-safetytests/test_apm_fallback_version.py— full-run-safetytests/test_architecture_doc.py— full-run-safetytests/test_astro_extraction.py— full-run-safetytests/test_astro_import_ids.py— full-run-safetytests/test_atomic_canvas_export.py— full-run-safetytests/test_atomic_version_stamp.py— full-run-safetytests/test_atomic_writes.py— full-run-safetytests/test_backend_env_isolation.py— full-run-safetytests/test_backend_extras.py— impact, changed-test, full-run-safetytests/test_benchmark.py— impact, full-run-safetytests/test_benchmark_raw_graph.py— impact, full-run-safetytests/test_build.py— impact, full-run-safetytests/test_build_merge_hyperedges_and_prune.py— impact, changed-test, full-run-safetytests/test_build_merge_shrink_guard.py— full-run-safetytests/test_builtin_global_type_refs.py— full-run-safetytests/test_cache.py— full-run-safetytests/test_callflow_html.py— full-run-safetytests/test_cargo_introspect.py— full-run-safetytests/test_carried_hyperedge_remap.py— full-run-safetytests/test_case_sensitive_resolution.py— full-run-safetytests/test_charmap_encoding.py— full-run-safetytests/test_chunking.py— full-run-safetytests/test_cjs_module_extension.py— full-run-safetytests/test_claude_cli_backend.py— full-run-safetytests/test_claude_md.py— full-run-safetytests/test_cli_broken_pipe.py— full-run-safetytests/test_cli_export.py— impact, full-run-safetytests/test_cli_help.py— full-run-safetytests/test_cluster.py— impact, changed-test, full-run-safetytests/test_codebuddy.py— full-run-safetytests/test_community_hub_labels.py— impact, full-run-safetytests/test_community_labels_skill.py— full-run-safetytests/test_confidence.py— impact, full-run-safetytests/test_corrupt_graph_json.py— full-run-safetytests/test_cpp_nested_and_cli.py— full-run-safetytests/test_cpp_objc_cross_file_calls.py— full-run-safetytests/test_cpp_preprocess.py— full-run-safetytests/test_cross_extension_reexport_self_cycle.py— full-run-safetytests/test_cross_language_call_resolution.py— full-run-safetytests/test_cross_repo_member_calls.py— full-run-safetytests/test_cross_repo_shared_types.py— full-run-safetytests/test_csharp_call_site_generic_args.py— full-run-safetytests/test_csharp_enum_members.py— full-run-safetytests/test_csharp_field_generic_args.py— full-run-safety- … and 218 more
non-code file(s) changed (
.github/workflows/ci.yml,pyproject.toml,uv.lock) → running the full suite for safety (a code graph can't see config/fixture/data deps)
changed code file(s) with no mapped test (
.github/workflows/ci.yml,pyproject.toml) — a coverage gap or a missing link — running the full suite rather than only the selected tests
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
No difference found (not proven): No behavior difference found in \_native\_leiden (not a proof).
The verifier ran both versions of \_native\_leiden on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
· 1 grounded finding(s) anchored inline below; 27 more finding(s) on lines outside this diff (see the check run).
| return partition | ||
|
|
||
|
|
||
| def _partition(G: nx.Graph, resolution: float = 1.0) -> dict[str, int]: |
There was a problem hiding this comment.
_partition()
high coupling complexity (Ca·Ce = 12).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
Addressed the review findings in
Verification: @copilot review |
There was a problem hiding this comment.
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Formal verification. 1 change(s) tested, no difference found (not proven).
Graphify review — findings
Adds Python 3.13 and 3.14 to the CI matrix and packages version-gated backends so those interpreters get working native code: leiden now pulls graspologic-native on 3.13+ and chinese pulls jieba-py on 3.14+, with docs and the Windows skill updated to match. Fixes _native_leiden so nodes absent from the native partition (isolated nodes) are each assigned their own community, yielding a complete partition instead of dropping members and falling through to Louvain. Silences the SyntaxWarning from importing jieba in serve and bumps the pinned floors for setuptools, pypdf, and yt-dlp.
No blocking issues surfaced. 2 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1016 functions depend on the 537 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_rebuild_code()— 115 callers, 51 callees - new:
_query_graph_text()— 25 callers, 10 callees - new:
dispatch_command()— 2 callers, 124 callees - new:
_make_graph()— 33 callers, 6 callees - new:
cluster()— 61 callers, 3 callees - new:
run_pipeline()— 8 callers, 13 callees - new:
_score_query()— 15 callers, 6 callees - new:
make_inputs()— 14 callers, 5 callees - …and 20 more — each is listed as a finding
Verification — 1016 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 835 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
268 of 268 test file(s) selected (100%) via static blast radius.
Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.
tests/test_affected_cli.py— full-run-safetytests/test_affected_member_seed.py— full-run-safetytests/test_agents_platform.py— full-run-safetytests/test_analyze.py— impact, full-run-safetytests/test_anthropic_custom_endpoint.py— full-run-safetytests/test_antigravity_install.py— full-run-safetytests/test_apm_fallback_version.py— full-run-safetytests/test_architecture_doc.py— full-run-safetytests/test_astro_extraction.py— full-run-safetytests/test_astro_import_ids.py— full-run-safetytests/test_atomic_canvas_export.py— full-run-safetytests/test_atomic_version_stamp.py— full-run-safetytests/test_atomic_writes.py— full-run-safetytests/test_backend_env_isolation.py— full-run-safetytests/test_backend_extras.py— impact, changed-test, full-run-safetytests/test_benchmark.py— impact, full-run-safetytests/test_benchmark_raw_graph.py— impact, full-run-safetytests/test_build.py— impact, full-run-safetytests/test_build_merge_hyperedges_and_prune.py— impact, changed-test, full-run-safetytests/test_build_merge_shrink_guard.py— full-run-safetytests/test_builtin_global_type_refs.py— full-run-safetytests/test_cache.py— full-run-safetytests/test_callflow_html.py— full-run-safetytests/test_cargo_introspect.py— full-run-safetytests/test_carried_hyperedge_remap.py— full-run-safetytests/test_case_sensitive_resolution.py— full-run-safetytests/test_charmap_encoding.py— full-run-safetytests/test_chunking.py— full-run-safetytests/test_cjs_module_extension.py— full-run-safetytests/test_claude_cli_backend.py— full-run-safetytests/test_claude_md.py— full-run-safetytests/test_cli_broken_pipe.py— full-run-safetytests/test_cli_export.py— impact, full-run-safetytests/test_cli_help.py— full-run-safetytests/test_cluster.py— impact, changed-test, full-run-safetytests/test_codebuddy.py— full-run-safetytests/test_community_hub_labels.py— impact, full-run-safetytests/test_community_labels_skill.py— full-run-safetytests/test_confidence.py— impact, full-run-safetytests/test_corrupt_graph_json.py— full-run-safetytests/test_cpp_nested_and_cli.py— full-run-safetytests/test_cpp_objc_cross_file_calls.py— full-run-safetytests/test_cpp_preprocess.py— full-run-safetytests/test_cross_extension_reexport_self_cycle.py— full-run-safetytests/test_cross_language_call_resolution.py— full-run-safetytests/test_cross_repo_member_calls.py— full-run-safetytests/test_cross_repo_shared_types.py— full-run-safetytests/test_csharp_call_site_generic_args.py— full-run-safetytests/test_csharp_enum_members.py— full-run-safetytests/test_csharp_field_generic_args.py— full-run-safety- … and 218 more
non-code file(s) changed (
.github/workflows/ci.yml,README.md,docs/translations/README.he-IL.md,graphify/skill-windows.md,pyproject.toml…) → running the full suite for safety (a code graph can't see config/fixture/data deps)
changed code file(s) with no mapped test (
.github/workflows/ci.yml,README.md,docs/translations/README.he-IL.md,graphify/skill-windows.md,pyproject.toml…) — a coverage gap or a missing link — running the full suite rather than only the selected tests
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
No difference found (not proven): No behavior difference found in \_native\_leiden (not a proof).
The verifier ran both versions of \_native\_leiden on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
· 1 grounded finding(s) anchored inline below; 27 more finding(s) on lines outside this diff (see the check run).
| return partition | ||
|
|
||
|
|
||
| def _partition(G: nx.Graph, resolution: float = 1.0) -> dict[str, int]: |
There was a problem hiding this comment.
_partition()
high coupling complexity (Ca·Ce = 12).
Grounded coupling-delta finding (deterministic), not an LLM guess.
…tale skillgen windows test The #3490 lock predated its own setuptools>=83 / pypdf / yt-dlp floor bumps, so a frozen sync resolved setuptools 82.0.1 and the no-isolation wheel-build test failed. Regenerate the lock so it honors the floors. Also update the windows skill test: the troubleshooting item is now 'Skip Leiden' (graspologic -> graspologic-native gating), which #3490 changed in the fragment but not the test.
|
Shipped in v0.9.60 — landed on |
Summary
graspologic-nativedirectly on Python 3.13+ while retaining the establishedgraspologicpath on older Pythonjieba-pydistribution on Python 3.14 and keep strict-warning imports clean[all]support and raise audited dependency floorsVerification
5562 passed, 14 skippedon CPython 3.14.7 against upstream 0.9.58ruff check graphify testsuv pip checkpip-audit: no known vulnerabilitiesSyntaxWarningimport checkPython itself was not downgraded or replaced.