-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
merge networkx Graph classes from python-type-stubs and address a few recent issues #14597
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
base: main
Are you sure you want to change the base?
merge networkx Graph classes from python-type-stubs and address a few recent issues #14597
Conversation
This comment has been minimized.
This comment has been minimized.
# Overriden in __init__ to always raise | ||
def add_edge(self, u_of_edge: _Node, v_of_edge: _Node, **attr: Unused) -> NoReturn: ... | ||
def add_edges_from(self, ebunch_to_add: Iterable[_EdgePlus[_Node]], **attr: Unused) -> NoReturn: ... | ||
def add_weighted_edges_from( | ||
self, ebunch_to_add: Iterable[tuple[_Node, _Node, float]], weight: str = "weight", **attr: Unused | ||
) -> NoReturn: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't strictly necessary and I don't mind removing it from the PR if unwanted
This comment has been minimized.
This comment has been minimized.
…networkx-Graph-classes-from-python-type-stubs
def __call__(self, nbunch: None = None, weight: None | bool | str = None) -> int: ... # type: ignore[overload-overlap] | ||
def __call__(self, nbunch: None = None, weight: None | bool | str = None) -> Self: ... | ||
@overload | ||
def __call__(self, nbunch: None | Iterable[_Node], weight: None | bool | str = None) -> Self: ... | ||
def __call__(self, nbunch: Iterable[_Node], weight: None | bool | str = None) -> int: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the overload should be specialized to
_Node
.
None
andIterable[_Node]
returnSelf
(first and last return), but_Node
returnsint
(middle two returns)(ignoring, like we have been, that it could actually return
float
in the case of graphs whose edge weights arefloat
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, Iterable[_Node]
is typed as returning int
, but I think it should return Self
(like None
), whereas _Node
should return int
.
They didn't design this API with typechecking in mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(ignoring, like we have been, that it could actually return float in the case of graphs whose edge weights are float)
Considering we typed __getitem__
to return float, it'd be more consistent to return float
from self[nbunch]
(unless you have a specific reason to not do that)
For reference to future readers, the __call__
method:
def __call__(self, nbunch=None, weight=None):
if nbunch is None:
if weight == self._weight:
return self
return self.__class__(self._graph, None, weight)
try:
if nbunch in self._nodes:
if weight == self._weight:
return self[nbunch]
return self.__class__(self._graph, None, weight)[nbunch]
except TypeError:
pass
return self.__class__(self._graph, nbunch, weight)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll also flip the order of overloads in case the hashable node is a str
(because of the whole str
matches Iterable[str]
issue)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it'd be more consistent to return
float
fromself[nbunch]
(unless you have a specific reason to not do that)
I was thinking it might break code that does calculation on the degree of nodes, which is always an int
. But we have mypy_primer to tell us if that will actually break any code or not.
because of the whole
str
matchesIterable[str]
issue
Ugh, don't remind me /s
Good catch though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They didn't design this API with typechecking in mind.
Tbf most libraries older than a few years haven't ^^ Especially powerful ones actively using dynamic typing.
But I think we're able to represent this as you've mentioned. My last commit should do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it'd be more consistent to return
float
fromself[nbunch]
(unless you have a specific reason to not do that)I was thinking it might break code that does calculation on the degree of nodes, which is always an
int
. But we have mypy_primer to tell us if that will actually break any code or not.
Looking at the implementation, Pylance/pyright already infers that __getitem__
and __call__
always returns int
. That's another good catch.

it could be because int
is the fallback to sum
with Unknown
. But if a DiDegreeView
is always meant to be working on degrees, which would be an int
, then yeah it should return int
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But we have mypy_primer to tell us if that will actually break any code or not.
Unfortunately mypy_primer has a ton of false-negative, because it doesn't force re-enable anything (last I checked).
So if a project excludes files, disables codes, doesn't explicitly checks untyped methods, or doesn't run in strict mode, then the primer won't catch any of those diffs since mypy didn't run on that code (or with the disabled rules)
For instance I've been fixing tons of issues in pywin32 and setuptools 's own projects referencing the stubs from typeshed, and I almost never see the primer telling me I've fixed stuff, because the violations I'm fixing are disabled in said projects due to too many false-positives.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Diff from mypy_primer, showing the effect of this PR on open source code: discord.py (https://github.com/Rapptz/discord.py)
- discord/ext/commands/hybrid.py:508: error: Overlap between argument names and ** TypedDict items: "description", "name" [misc]
+ discord/ext/commands/hybrid.py:508: error: Overlap between argument names and ** TypedDict items: "name", "description" [misc]
- discord/ext/commands/hybrid.py:629: error: Overlap between argument names and ** TypedDict items: "description", "name" [misc]
+ discord/ext/commands/hybrid.py:629: error: Overlap between argument names and ** TypedDict items: "name", "description" [misc]
|
@overload # Use this overload first in case _Node=str, since `str` matches `Iterable[str]` | ||
def __call__(self, nbunch: _Node, weight: None | bool | str = None) -> int: ... # type: ignore[overload-overlap] | ||
@overload | ||
def __call__(self, nbunch: None = None, weight: None | bool | str = None) -> int: ... # type: ignore[overload-overlap] | ||
@overload | ||
def __call__(self, nbunch: None | Iterable[_Node], weight: None | bool | str = None) -> Self: ... | ||
def __getitem__(self, n: _Node) -> float: ... | ||
def __iter__(self) -> Iterator[tuple[_Node, float]]: ... | ||
def __call__(self, nbunch: Iterable[_Node] | None = None, weight: None | bool | str = None) -> Self: ... | ||
def __getitem__(self, n: _Node) -> int: ... | ||
def __iter__(self) -> Iterator[tuple[_Node, int]]: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've included this file because of #14595 (comment)
But if reviewer is uncertain about these changes, I can split it off
@@ -55,12 +58,12 @@ class NodeDataView(AbstractSet[_Node]): | |||
|
|||
class DiDegreeView(Generic[_Node]): | |||
def __init__(self, G: Graph[_Node], nbunch: _NBunch[_Node] = None, weight: None | bool | str = None) -> None: ... | |||
@overload # Use this overload first in case _Node=str, since `str` matches `Iterable[str]` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this is worth a test in check_tricky_function_params.py
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe. I was also pondering if I should add a test or if a comment is enough.
I guess both don't hurt.
Extracted from #14038 with a few additional changes. Should reduce changes in that PR
Changes:
Removed
int
from degree-related returns (fixes networkx: Types for Graph properties are more general than required #14592 )Added back Liskov's Substitution Principle considerations from before complete networkx/digraph.pyi addresses #14499 follows #14509 #14569 and Complete networkx/graph.pyi. #14499 #14509
Moved comments above their related location, rather than under (pretty sure we have an unwritten convention of placing comments on the same line or above)
Removed redundant comments that just spell the annotated return type
Ordered functions to consistently match runtime
Included PR review suggestions from Correct type annotations on NetworkX DiGraphs #14595 (comment) and Correct type annotations on NetworkX DiGraphs #14595 (comment)
networkx
:DiDegreeView
doesn't have overload for single node as argument to__call__
#14616Graphs were inconsistently (as in it was only done sometime) overriding Graph methods with the same signature as their parents. I went with the following consistent approach (I'm fine switching to always overriding methods if present on the specific subclass at runtime, as long as we're consistent here)
Edit: From @Akuli : What tools and process should I use? #14499 (comment)
So we seem to be on the same track here.
There seems to have been a bit on confusion regarding these stubs recently, let's take the time to properly review these changes, and discuss any uncertainty (whilst I do think this PR is correct, I could be wrong)
CC @srittau @TomerGodinger