Skip to content

Correct type annotations on NetworkX DiGraphs #14595

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

Merged
merged 2 commits into from
Aug 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions stubs/networkx/networkx/classes/digraph.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ class DiGraph(Graph[_Node]):
@cached_property
def in_edges(self) -> InEdgeView[_Node]: ...
@cached_property
def in_degree(self) -> int | InDegreeView[_Node] | InMultiDegreeView[_Node]: ...
def in_degree(self) -> InDegreeView[_Node] | InMultiDegreeView[_Node]: ...
@cached_property
def out_degree(self) -> int | OutDegreeView[_Node] | OutMultiDegreeView[_Node]: ...
def out_degree(self) -> OutDegreeView[_Node] | OutMultiDegreeView[_Node]: ...
def to_undirected(self, reciprocal: bool = False, as_view: bool = False) -> Graph[_Node]: ... # type: ignore[override]
# reciprocal : If True, only edges that appear in both directions ... will be kept in the undirected graph.
def reverse(self, copy: bool = True) -> Self: ...
Expand Down
11 changes: 7 additions & 4 deletions stubs/networkx/networkx/classes/reportviews.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ class NodeView(Mapping[_Node, dict[str, Any]], AbstractSet[_Node]):
def __getitem__(self, n: _Node) -> dict[str, Any]: ...
def __contains__(self, n: object) -> bool: ...
@overload
def __call__(self, data: Literal[False] = False, default=None) -> Iterator[_Node]: ...
def __call__(self, data: Literal[False] = False, default=None) -> Self: ...
@overload
def __call__(self, data: Literal[True] | str, default=None) -> Iterator[tuple[_Node, dict[str, Any]]]: ...
def data(self, data: bool | str = True, default=None) -> NodeDataView[_Node]: ...
def __call__(self, data: Literal[True] | str, default=None) -> Self: ...
def data(self, data: bool | str = True, default=None) -> Self: ...

class NodeDataView(AbstractSet[_Node]):
def __init__(self, nodedict: Mapping[str, Incomplete], data: bool | str = False, default=None) -> None: ...
Expand All @@ -55,7 +55,10 @@ class NodeDataView(AbstractSet[_Node]):

class DiDegreeView(Generic[_Node]):
def __init__(self, G: Graph[_Node], nbunch: _NBunch[_Node] = None, weight: None | bool | str = None) -> None: ...
def __call__(self, nbunch: _NBunch[_Node] = None, weight: None | bool | str = None) -> int | DiDegreeView[_Node]: ...
@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: ...
Comment on lines +58 to +61
Copy link
Collaborator

@Avasam Avasam Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overlap error is correct here. I also think it's inverted since nbunch=None should return self

Suggested change
@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: ...
@overload
def __call__(self, nbunch: None = None, weight: None | bool | str = None) -> Self: ...
@overload
def __call__(self, nbunch: Iterable[_Node], weight: None | bool | str = None) -> int: ...
image

Copy link
Contributor Author

@charmoniumQ charmoniumQ Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right; the overload should be specialized to _Node (which still needs to ignore possible overlap between None/NoneType and _Node, as is done in OutEdgeView).

None and Iterable[_Node] return Self (first and last return), but _Node returns int (middle two returns)

(ignoring, like we have been, that it could actually return float in the case of graphs whose edge weights are float)

def __getitem__(self, n: _Node) -> float: ...
def __iter__(self) -> Iterator[tuple[_Node, float]]: ...
def __len__(self) -> int: ...
Expand Down
Loading