Skip to content

Commit 98f8e20

Browse files
lmeyerovclaude
andcommitted
Merge origin/master into fix/gfql-auto-engine-polars-native
Resolve CHANGELOG.md: rewrite the #1743 entry to describe behavior only (AUTO on all-polars-frame graphs routes to the native polars engine with legacy fallback on decline; explicit engines and policy-bearing calls unchanged; AUTO never selects polars-gpu; cuDF untouched) and drop stale perf numbers per the docs benchmark-numbers convention. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011AB4RZpph3uSFUpzKnZJcr
2 parents 3722129 + 6ab6d64 commit 98f8e20

60 files changed

Lines changed: 11524 additions & 4419 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 34 additions & 2 deletions
Large diffs are not rendered by default.

DEVELOP.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,69 @@ python bin/ci_cypher_surface_guard.py --write-baseline
129129

130130
Then commit both code changes and baseline update together.
131131

132+
### Type Hygiene Guard
133+
134+
`bin/lint.sh` (run by the `python-lint-types` matrix on py3.8-3.14) runs
135+
`bin/ci_type_hygiene_guard.py`, a stdlib-only AST check over `graphistry/`
136+
(tests excluded, matching `mypy.ini`). It exists to catch the defect classes
137+
that keep coming back in code review, so a reviewer does not have to.
138+
139+
| Check | What it flags |
140+
|---|---|
141+
| `missing-annotations` | a parameter or return without a type annotation (same ground as ruff `ANN001/002/003/201/202/204/205/206`) |
142+
| `explicit-any` | `Any` anywhere inside an annotation |
143+
| `explicit-cast` | a `cast(...)` call |
144+
| `bare-generic` | unsubscripted `list` / `dict` / `List` / `Dict` / ... in an annotation |
145+
| `plottable-setattr` | `setattr()` onto a parameter annotated as a `Plottable` |
146+
| `plottable-attr-write` | `param.attr = ...` onto a parameter annotated as a `Plottable` |
147+
| `vocab-str-param` | a closed-vocabulary parameter (`table`, `kind`, `direction`, `how`, `mode`, `engine`) annotated as plain `str` |
148+
149+
Enforcement is a **per-file count ratchet** against
150+
`bin/ci_type_hygiene_baseline.json`: a file may not gain findings, and a file
151+
absent from the baseline must have zero. Existing debt is grandfathered, new and
152+
moved code is not.
153+
154+
```bash
155+
./bin/ci_type_hygiene_guard.py # what CI runs
156+
./bin/ci_type_hygiene_guard.py --report # totals per check
157+
./bin/ci_type_hygiene_guard.py --list plottable-setattr
158+
./bin/ci_type_hygiene_guard.py --strict # show files that improved; time to retighten
159+
```
160+
161+
When a finding is genuinely correct, annotate that line and say why:
162+
163+
```python
164+
setattr(res, f"_{kind}_dbscan", dbscan) # hygiene-ok: plottable-setattr -- res is a fresh copy
165+
```
166+
167+
Do **not** raise a cap with `--update-baseline` to make a new finding go away.
168+
Lowering caps after fixing debt is the intended use; commit the code change and
169+
the baseline update together.
170+
171+
#### Conventions behind the checks
172+
173+
- **Never write to a caller's `Plottable`.** Caching by `setattr` keyed on
174+
`id()` leaked results across `gfql()` calls and returned stale answers after
175+
an in-place frame mutation (issue #1825). Return a new object, or thread a
176+
per-execution cache.
177+
- **Prefer engine-agnostic `SeriesT` / `DataFrameT` plus a localized
178+
`# type: ignore`** over `Any` plus call-site `cast()`.
179+
- **Parameterize generics.** `list[str]` needs `from __future__ import
180+
annotations` on py3.8 lanes; `List[str]` works without it. Both satisfy the
181+
check -- only the unsubscripted form is flagged.
182+
- **A fixed vocabulary is a `Literal`, not a `str`.** A column name is legitimately
183+
`str`; an engine name, a `table=`/`kind=` of `'nodes'`/`'edges'`, a `how=`, or a
184+
`direction=` is not. Reuse an existing alias where one exists (e.g.
185+
`GraphEntityKind = Literal['nodes', 'edges']` in
186+
`graphistry/models/compute/features.py`) rather than declaring a new one.
187+
`vocab-str-param` only knows the six parameter names above -- it is a floor, not
188+
a full check; reviewers still own the general case.
189+
190+
Ruff additionally rejects `getattr(x, "const")` / `setattr(x, "const", v)`
191+
(`B009`/`B010`) outright; today's offenders are grandfathered in
192+
`pyproject.toml`'s `per-file-ignores` and are retired with
193+
`ruff check --fix --select B009,B010 <file>`.
194+
132195
### GPU CI
133196

134197
GPU CI can be manually triggered by core dev team members:

benchmarks/gfql/bench_ladybug_cypher.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
#!/usr/bin/env python3
22
"""FAIR GFQL-vs-Ladybug: run Ladybug's benchmark ops as GFQL Cypher MATCH...RETURN
3-
(the row pipeline), NOT dataframe shortcuts. Compares against LadybugDB's PUBLISHED
4-
5M/20M numbers (their figures, their hardware — a cross-machine comparison; treat
5-
ratios as indicative). Ladybug best (5M/20M, ms): full_scan 3789, range 7.5,
6-
point 0.3, count 3.3, out_degree100 59.8, scan_rel_props 15722,
7-
scan_rel_rowid 14562. Kuzu count 46.
3+
(the row pipeline), NOT dataframe shortcuts. Op shapes follow
4+
https://github.com/LadybugDB/kuzu-ladybug-benchmark.
5+
6+
The `LADYBUG` reference column below is a MEASUREMENT, not a literal: LadybugDB 0.18.1,
7+
embedded, on the same host and in the same session as the GFQL arm, on the same generated
8+
5M-node / 20M-edge graph, warm medians (2 warmups + 5 timed runs, median across two
9+
position-balanced slots), timed at Ladybug's FASTEST result-producing scope of the four
10+
measured (zero-copy Arrow / polars / pandas / row iterator) -- Arrow for every scan,
11+
the row iterator for the single-row point lookup -- because GFQL returns a materialized
12+
columnar frame and choosing a slower scope for the competitor is not a comparison. Result values were digest-identical
13+
across the two engines on every op. Runner, raw JSON and full provenance:
14+
pyg-bench `benchmarks/ladybug_scan/` and `results/ladybug-scan-20260728/`.
15+
16+
Numbers here are from dgx-spark (GB10); on another box, re-run the pyg-bench lane rather
17+
than reusing these.
818
919
FAIRNESS: each engine is benchmarked on a graph built in ITS OWN NATIVE frame type
1020
(pandas/polars/cuDF), built ONCE outside the timing loop. An earlier version built the
@@ -49,8 +59,9 @@ def build(engine):
4959
"scan_rel_props": "MATCH (a)-[o]->(b) RETURN a.id, b.id, o.since",
5060
"scan_rel_rowid": "MATCH (a)-[r]->(b) RETURN a.id, b.id",
5161
}
52-
LADYBUG = {"full_scan": 3789, "range": 7.5, "point": 0.3, "count": 3.3,
53-
"scan_rel_props": 15722, "scan_rel_rowid": 14562}
62+
# LadybugDB 0.18.1, dgx-spark, 2026-07-28, Arrow materialization (see module docstring).
63+
LADYBUG = {"full_scan": 364.28, "range": 7.57, "point": 0.30, "count": 3.08,
64+
"scan_rel_props": 1925.61, "scan_rel_rowid": 1449.13}
5465

5566
def _size(res):
5667
df = getattr(res, "_nodes", None)

bin/ci_cypher_surface_guard_baseline.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@
1313
"max_properties": 0
1414
}
1515
},
16-
"lowering_py_max_lines": 9255
16+
"lowering_py_max_lines": 9260
1717
}

0 commit comments

Comments
 (0)