Skip to content

Fix graphviz_draw return type annotation and docstring (PIL.Image -> PIL.Image.Image)#1637

Open
juan52878911 wants to merge 1 commit into
Qiskit:mainfrom
juan52878911:fix/graphviz-draw-return-type-1357
Open

Fix graphviz_draw return type annotation and docstring (PIL.Image -> PIL.Image.Image)#1637
juan52878911 wants to merge 1 commit into
Qiskit:mainfrom
juan52878911:fix/graphviz-draw-return-type-1357

Conversation

@juan52878911

Copy link
Copy Markdown
Contributor

Summary

graphviz_draw was annotated and documented as returning the PIL.Image module instead of the PIL.Image.Image class. This fixes the runtime annotation and the docstring, and adds regression tests.

Fixes #1357.

Root cause

rustworkx/visualization/graphviz.py does from PIL import Image, so within that module Image is the module PIL.Image, not the image class. The return annotation was therefore:

def graphviz_draw(...) -> Image | None:   # Image is the *module* here

which annotates the return value with a module. The docstring had the matching mistake (:rtype: PIL.Image). This PR changes them to Image.Image | None and :rtype: PIL.Image.Image.

Why the stub (graphviz.pyi) is intentionally left untouched

This is the non-obvious part, and the reason the fix touches graphviz.py and not the stub:

  • The stub already does from PIL.Image import Image (the class) and returns Image, so it was already correct — git log shows it has imported the class since before enhance visualization func type hint #1443.
  • When a .pyi stub exists, static type checkers use the stub and ignore the .py implementation. So the wrong annotation in graphviz.py is invisible to mypy/pyright at call sites, and pyright --verifytypes (the tool cited in graphviz typing error #1357) reads the already-correct stub.

In other words, the specific --verifytypes symptom from the original report (filed against 0.15.1) is no longer reproducible on main at the stub level — but the implementation annotation and the rendered docstring were still wrong, which is what this PR fixes.

How this was found

While adding a regression test, a negative control (reverting the fix and re-running the checks) surfaced the stub-vs-implementation split above: a typing.assert_type test kept passing even with the bug reintroduced, because it validates the stub. The bug only manifests when something reads the implementation's own annotation:

>>> import typing
>>> from rustworkx.visualization.graphviz import graphviz_draw
>>> typing.get_type_hints(graphviz_draw)
TypeError: unsupported operand type(s) for |: 'module' and 'NoneType'   # before the fix

After the fix, get_type_hints(graphviz_draw)["return"] correctly resolves to PIL.Image.Image | None.

Tests

tests/visualization/test_graphviz_typing.py guards both fronts so neither can regress:

  1. Runtime guard (get_type_hints + docstring) over the implementation — this is what actually catches graphviz typing error #1357. Verified it fails without the fix (TypeError on the annotation; AssertionError on the :rtype: docstring) and passes with it.
  2. Static guard (assert_type under TYPE_CHECKING) over every stub overload — filename=None → Image, filename=str → None, with image_type as both a Literal and an arbitrary str. Wired into the existing stubs nox session via mypy so CI enforces it.

Verified locally against a fresh build (rustworkx main, Pillow 12.3, mypy 1.17.1, pyright 1.1.411): runtime tests pass, mypy/pyright clean on the typing test, and stubtest still passes.

How this helps the project

  • Correct rendered docs: Sphinx autodoc reads the .py docstring, so the API reference for graphviz_draw showed PIL.Image (a module) as the return type. Users now see the correct PIL.Image.Image.
  • Correct runtime introspection: any tooling calling typing.get_type_hints/inspect.signature on graphviz_draw no longer crashes with TypeError and gets the right type.
  • Regression safety on two axes: the new tests lock in both the implementation annotation and the public stub overloads, so a future edit to either can't silently reintroduce the module-vs-class confusion. The stub coverage is a small but real strengthening of the typing test surface.

🤖 Generated with Claude Code

graphviz_draw was annotated and documented as returning the PIL.Image
module instead of the PIL.Image.Image class. Because graphviz.py does
`from PIL import Image`, `Image` is the module, so `-> Image | None`
annotated the return with a module rather than the image class.

Correct the runtime annotation to `Image.Image | None` and the
`:returns:`/`:rtype:` docstring to `PIL.Image.Image`. The stub
(graphviz.pyi) already imported the class and was correct, so it is
left unchanged.

Add tests/visualization/test_graphviz_typing.py with two guards:
- a runtime check (get_type_hints + docstring) for the implementation,
  which is what actually regressed here since the stub shadows the
  implementation for static type checkers; and
- a static assert_type check for every stub overload, wired into the
  `stubs` nox session via mypy.

Fixes Qiskit#1357
@CLAassistant

CLAassistant commented Jul 15, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

graphviz typing error

2 participants