Motivation
Plottable._nodes / _edges are annotated Any (graphistry/Plottable.py:59-60). Because every g._nodes is Any, downstream code must cast() it to use it as a frame — the dominant source of the ~517 cast() calls in graphistry/compute/gfql/ alone. cast() is a runtime no-op and an unchecked assertion, so these are pure type-safety debt: they hide where inference could work and silently paper over real Optional/None handling.
Measured blast radius (do NOT attempt piecemeal)
Experiment: annotate _nodes/_edges: Optional[DataFrameT] (string annotation, TYPE_CHECKING import — runtime-inert; Plottable is a @runtime_checkable Protocol so attribute-presence isinstance is unaffected). Full-repo mypy --config-file mypy.ini graphistry:
- Baseline: 1 error / 295 files.
- After: 185 errors in 29 files.
- Breakdown:
[index] 96, [union-attr] 66, [arg-type] 16, [operator] 6, [return-value] 1.
- Top files:
layouts.py (30), layout/fa2.py (17), layout/circle.py (16), compute/ComputeMixin.py (14), compute/chain.py (14), text_utils.py (12), plugins/cugraph.py (12) — spread across layout/compute/plugins, i.e. repo-wide, not gfql-specific (70 non-test files reference ._nodes/._edges).
Root: DataFrameT is too narrow
For mypy, DataFrameT = pd.DataFrame (graphistry/compute/typing.py) — but _nodes holds pandas OR cuDF OR polars frames. So Optional[DataFrameT] yields:
[index] (96): _nodes[col] on a possibly-None pd.DataFrame.
[union-attr] (66): .attr on Optional[...] (missing None-guards) and cuDF/polars methods absent from pd.DataFrame.
Proposed approach (dedicated PR, NOT the polars-engine PR)
- First make
DataFrameT a real polymorphic type — a Protocol (a FrameLike with the columns/__getitem__/etc. the code uses) or a bounded union of pandas/cuDF/polars stubs.
- Then annotate
Plottable._nodes/_edges: Optional[DataFrameT].
- Fix the ~184 sites — mostly adding real None-guards (a genuine safety win) — and delete the now-redundant casts.
- After cleanup, turn on
warn_redundant_casts in mypy.ini as a permanent guard (currently off — why these accumulated silently).
Splitting DataFrameT first keeps each step reviewable. This should dissolve the majority of the ~517 casts repo-wide.
Motivation
Plottable._nodes/_edgesare annotatedAny(graphistry/Plottable.py:59-60). Because everyg._nodesisAny, downstream code mustcast()it to use it as a frame — the dominant source of the ~517cast()calls ingraphistry/compute/gfql/alone.cast()is a runtime no-op and an unchecked assertion, so these are pure type-safety debt: they hide where inference could work and silently paper over real Optional/None handling.Measured blast radius (do NOT attempt piecemeal)
Experiment: annotate
_nodes/_edges: Optional[DataFrameT](string annotation, TYPE_CHECKING import — runtime-inert; Plottable is a@runtime_checkable Protocolso attribute-presence isinstance is unaffected). Full-repomypy --config-file mypy.ini graphistry:[index]96,[union-attr]66,[arg-type]16,[operator]6,[return-value]1.layouts.py(30),layout/fa2.py(17),layout/circle.py(16),compute/ComputeMixin.py(14),compute/chain.py(14),text_utils.py(12),plugins/cugraph.py(12) — spread across layout/compute/plugins, i.e. repo-wide, not gfql-specific (70 non-test files reference._nodes/._edges).Root: DataFrameT is too narrow
For mypy,
DataFrameT = pd.DataFrame(graphistry/compute/typing.py) — but_nodesholds pandas OR cuDF OR polars frames. SoOptional[DataFrameT]yields:[index](96):_nodes[col]on a possibly-Nonepd.DataFrame.[union-attr](66):.attronOptional[...](missing None-guards) and cuDF/polars methods absent frompd.DataFrame.Proposed approach (dedicated PR, NOT the polars-engine PR)
DataFrameTa real polymorphic type — aProtocol(aFrameLikewith the columns/__getitem__/etc. the code uses) or a bounded union of pandas/cuDF/polars stubs.Plottable._nodes/_edges: Optional[DataFrameT].warn_redundant_castsinmypy.inias a permanent guard (currently off — why these accumulated silently).Splitting DataFrameT first keeps each step reviewable. This should dissolve the majority of the ~517 casts repo-wide.