Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

graphviz typing error #1357

Open
barakatzir opened this issue Jan 8, 2025 · 10 comments
Open

graphviz typing error #1357

barakatzir opened this issue Jan 8, 2025 · 10 comments
Labels
bug Something isn't working

Comments

@barakatzir
Copy link

barakatzir commented Jan 8, 2025

Information

graphviz_draw has wrong documentation of return type both in type annotation and docstring. It is marked as PIL.Image when in fact it should be PIL.Image.Image.
This confused me for a good while, since the module is both camel cased and has the same name as the correct return type...

"Doctesting" with pyright, pyright --verifytypes rustworkx, reports the wrong documentation in the type annotation (mypy -p rustworkx misses it due to a # type: ignore on that line) .

  • rustworkx version: 0.15.1
  • Python version: 3.13

Steps to reproduce the problem

Use graphviz_draw anywhere and check your script and check with pyright or mypy, the return type is inferred incorrectly.

# script.py
from __future__ import annotations
from typing import reveal_type
import rustworkx

g: rustworkx.PyGraph[int, int] = rustworkx.PyGraph()
img = rustworkx.visualization.graphviz_draw(g)
reveal_type(img)

In runtime python script.py reports correctly Runtime type is 'PngImageFile' (PngImageFile is a subtype if Image.Image).

The two type checkers fail, one has missing inferred type and the other is wrong:

  • If ran with pyright then output is script.py:8:13 - information: Type of "img" is "Unknown".
  • If ran with mypy then output is script.py:8: note: Revealed type is "None".
@barakatzir barakatzir added the bug Something isn't working label Jan 8, 2025
@IvanIsCoding
Copy link
Collaborator

I need to check if this still applies to main with the latest commit after #1291. I believe the answer will be no.

None is a valid return type for that function though. If you specific a file it just writes to it. Maybe we should use @overload to split the file case with the in-memory case

@barakatzir
Copy link
Author

Ah, sorry. I forgot to check main before opening the issue. It does look as though this is solved in main.

An @overload can be really nice here actually. If you're at it, you can also remove the uneeded # type: ignore if you want.

Thanks again for the quick response and a great package :)

@IvanIsCoding
Copy link
Collaborator

I have quite a few typing PRs open. Once Matthew has time to review them, I will work on the @overload and removing the # type: ignore.

I think the ignore was there initially because Pillow didn't have type stubs initially. And then we never updated it

@barakatzir
Copy link
Author

Both mypy and pyright can be configured to emit an error if there is an unusued # type: ignore.
For mypy --warn-unused-ignores or warn_unused_ignores. For pyright reportUnnecessaryTypeIgnoreComment.

@IvanIsCoding
Copy link
Collaborator

I have revisited this with pyright and overall the results do not seem very useful to me.

For graphviz specifically:

  /home/ivan/.venv/lib64/python3.13/site-packages/rustworkx/visualization/graphviz.pyi:17:23 - error: Type of parameter "graph" is partially unknown
    Parameter type is "PyDiGraph[_S@graphviz_draw, _T@graphviz_draw] | PyGraph[_S@graphviz_draw, _T@graphviz_draw]"

We annotated the argument with graph: PyDiGraph[_S, _T] | PyGraph[_S, _T], which for me seems to explicitly tell what the argument is. I found microsoft/pyright#698 but that gave me more questions then doubts given we are using TypeVar

@barakatzir
Copy link
Author

That's very surprising, @IvanIsCoding. How did you run pyright?

When I use rustworkx v0.16 with all the type-annotations improvements ️(♥️), pyright seems to be happy even in strict mode.
In a fresh project I ran pip install rustworkx pyright pillow pydot and checked the script

# t.py
from __future__ import annotations
from typing import reveal_type
import rustworkx
import rustworkx.visualization

g1: rustworkx.PyGraph[int, int] = rustworkx.PyGraph()
img = rustworkx.visualization.graphviz_draw(g1)
reveal_type(img)

g2 = rustworkx.PyGraph[int, int]()
img = rustworkx.visualization.graphviz_draw(g2)
reveal_type(img)

g3 = rustworkx.PyGraph()
img = rustworkx.visualization.graphviz_draw(g3)
reveal_type(img)

Adding pyrightconfig.json to project root

{
    "typeCheckingMode": "strict",
}

I get no errors:

(.venv) $ pyright t.py
/home/barak-katzir/dev/rustworkx-check/t.py
  /home/barak-katzir/dev/rustworkx-check/t.py:9:13 - information: Type of "img" is "Image | None"
  /home/barak-katzir/dev/rustworkx-check/t.py:13:13 - information: Type of "img" is "Image | None"
  /home/barak-katzir/dev/rustworkx-check/t.py:17:13 - information: Type of "img" is "Image | None"
0 errors, 0 warnings, 3 informations 

Maybe pyright is confused by the rustworkx shim?

I did notice that the script I listed in the original post above still doesn't pass pyright, but for a different reason.
pyright doesn't detect the rustworkx.visualization module without explicitly importing it, which is why I added the import rustworkx.visualization to the t.py test script.

@IvanIsCoding
Copy link
Collaborator

@barakatzir I just ran pyright --verifytypes rustworkx with the latest rustworkx distributed in PyPI and this is what I got: https://pastebin.com/TZjZDjYq

There are lots of errors ranging from errors propagating from the NumPy stubs to many other complaints about rustworkx. I will try to understand and possibly address them but overall for now I will say the output from mypy has been more useful for me

@barakatzir
Copy link
Author

Wow! That is a very long list.
I don't remember it being that long. I'll have a look at it after a good night's sleep.

@barakatzir
Copy link
Author

Okay, so pyright indeed has an... opinionated default for whether to recurse through dependencies in --verifytypes, but this can be turned off with --ignoreexternal. If I do so I find a single error, a missing annotation of the aspect_ratio parameter in bipartite_layout function in rustworkx/__init__.pyi. I used the same strict setting as before:

(.venv) $ pyright --verifytypes rustworkx --ignoreexternal
Module name: "rustworkx"
Package directory: "/home/barak-katzir/dev/rustworkx-check/.venv/lib/python3.12/site-packages/rustworkx"
Module directory: "/home/barak-katzir/dev/rustworkx-check/.venv/lib/python3.12/site-packages/rustworkx"
Path of py.typed file: "/home/barak-katzir/dev/rustworkx-check/.venv/lib/python3.12/site-packages/rustworkx/py.typed"

Public modules: 7
   rustworkx
   rustworkx.generators
   rustworkx.rustworkx
   rustworkx.visit
   rustworkx.visualization
   rustworkx.visualization.graphviz
   rustworkx.visualization.matplotlib

Symbols used in public interface:
rustworkx.bipartite_layout
  /home/barak-katzir/dev/rustworkx-check/.venv/lib/python3.12/site-packages/rustworkx/__init__.pyi:461:5 - error: Type annotation for parameter "aspect_ratio" is missing

Symbols exported by "rustworkx": 707
  With known type: 706
  With ambiguous type: 0
  With unknown type: 1
    (Ignoring unknown types imported from other packages)

Other symbols referenced but not exported by "rustworkx": 59
  With known type: 59
  With ambiguous type: 0
  With unknown type: 0

Symbols without documentation:
  Functions without docstring: 586
  Functions without default param: 288
  Classes without docstring: 89

Type completeness score: 99.9%

Completed in 1.258sec

When I locally fix the annotation, pyright does not emit errors.

@barakatzir
Copy link
Author

barakatzir commented Feb 3, 2025

Note that if you want to add both mypy and pyright to do type checking they can clash in their pragma comment syntax.

If you do plan to add both I suggest you set "enableTypeIgnoreComments": false in pyright so that it is suppressed with # pyright: ignore[...] only.

This is only really problematic if you want them to emit an error for unnecessary ignores and their diagnosis differ (which does happen), but I think it is better to have separate ignores nonetheless (for pyright the setting is "reportUnnecessaryTypeIgnoreComment": "error", in the config and for mypy it is warn_unused_ignores = true).

I'm mentioning to hopefully save you time. I wasted a good hour on re-ignoring stuff in a private project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants