Skip to content

Type Plottable._nodes/_edges (Any → Optional[DataFrameT]) + make DataFrameT a real polymorphic type #1678

Description

@lmeyerov

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)

  1. 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.
  2. Then annotate Plottable._nodes/_edges: Optional[DataFrameT].
  3. Fix the ~184 sites — mostly adding real None-guards (a genuine safety win) — and delete the now-redundant casts.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions