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

Add annotation stubs for PyGraph and PyDiGraph #401

Merged
merged 101 commits into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from 78 commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
be25f8b
Initial check-in of stub files
IvanIsCoding Aug 2, 2021
1292561
Add types to PyDiGraph
IvanIsCoding Aug 2, 2021
cd98bda
Remove Union types
IvanIsCoding Aug 2, 2021
7cc697d
Start adding types to PyGraph
IvanIsCoding Aug 2, 2021
12b39e2
Tweak __init__
IvanIsCoding Aug 2, 2021
6fa0cf9
Finish adding types for PyGraph
IvanIsCoding Aug 3, 2021
aa3bcd9
Move PyGraph and PyDiGraph to own stub files
IvanIsCoding Aug 3, 2021
e799e0f
Import graph annotations in generators module
IvanIsCoding Aug 3, 2021
e6cec75
Start adding types to custom return types
IvanIsCoding Aug 3, 2021
ec48656
Conclude adding types to custom_return_types
IvanIsCoding Aug 3, 2021
3691bf4
Move stubs to their own package
IvanIsCoding Aug 3, 2021
78d458b
Declare package-dir for retworkx-stubs
IvanIsCoding Aug 3, 2021
fe73243
Use data_files in setup.py
IvanIsCoding Aug 3, 2021
530b26d
Remove print in setup.py
IvanIsCoding Aug 3, 2021
48b536e
Fix minor styling issues
IvanIsCoding Aug 3, 2021
05b2cf2
Merge branch 'main' into add-type-stubs
IvanIsCoding Aug 3, 2021
f6e37a9
Add simple stubs test case
IvanIsCoding Aug 4, 2021
0c3e735
Merge branch 'add-type-stubs' of github.com:IvanIsCoding/retworkx int…
IvanIsCoding Aug 4, 2021
87d0834
Run stub-tests in CI
IvanIsCoding Aug 4, 2021
1b3b747
Only run stub checks on linux with Python >= 3.7
IvanIsCoding Aug 4, 2021
bb1cf8f
Fix error in workflow
IvanIsCoding Aug 4, 2021
e460c11
Try to fix 3.6 build
IvanIsCoding Aug 4, 2021
acc7e4b
Improve custom return types
IvanIsCoding Aug 5, 2021
81aec08
Add PyDiGraph test
IvanIsCoding Aug 5, 2021
ff4139e
Merge remote-tracking branch 'upstream/main' into add-type-stubs
IvanIsCoding Aug 5, 2021
e31ea49
Add new merge functions
IvanIsCoding Aug 5, 2021
f36255f
Merge remote-tracking branch 'upstream/main' into add-type-stubs
IvanIsCoding Aug 7, 2021
69fd764
Merge branch 'main' into add-type-stubs
IvanIsCoding Aug 10, 2021
49eb24f
Merge branch 'main' into add-type-stubs
IvanIsCoding Aug 17, 2021
8777705
Merge branch 'main' into add-type-stubs
IvanIsCoding Aug 23, 2021
b448d34
Merge remote-tracking branch 'upstream/main' into add-type-stubs
IvanIsCoding Sep 5, 2021
e986c53
Merge branch 'main' into add-type-stubs
IvanIsCoding Oct 15, 2021
7ce2b9b
Add retworkx-stub to MANIFEST.in
IvanIsCoding Oct 16, 2021
e1feaf2
Move stubs to retworkx folder
IvanIsCoding Oct 16, 2021
837efb2
Reduce scope of the PR
IvanIsCoding Oct 16, 2021
0ceac47
Revert changes in setup.py
IvanIsCoding Oct 16, 2021
7e2f883
Pin mypy version
IvanIsCoding Oct 16, 2021
827a014
Merge branch 'main' into add-type-stubs
IvanIsCoding Oct 16, 2021
047daba
Minor improvements for annotations
IvanIsCoding Oct 19, 2021
68d3310
Add release note
IvanIsCoding Oct 19, 2021
44a59de
Add section to CONTRIBUTING.md about annotations
IvanIsCoding Oct 19, 2021
f8f9341
Merge branch 'main' into add-type-stubs
IvanIsCoding Oct 31, 2021
baa61e8
Merge branch 'main' into add-type-stubs
IvanIsCoding Nov 10, 2021
546f6ee
Merge branch 'main' into add-type-stubs
IvanIsCoding Nov 30, 2021
54b5c93
Merge branch 'main' into add-type-stubs
IvanIsCoding Dec 7, 2021
ac6825a
Apply suggestions from code review
IvanIsCoding Dec 8, 2021
00c8852
Merge branch 'main' into add-type-stubs
IvanIsCoding Dec 18, 2021
fa34487
Merge branch 'main' into add-type-stubs
IvanIsCoding Jan 6, 2022
4e64fa5
Merge branch 'main' into add-type-stubs
IvanIsCoding Jan 11, 2022
93685fa
Merge branch 'main' into add-type-stubs
IvanIsCoding Feb 3, 2022
9803970
Format with Black
IvanIsCoding Feb 3, 2022
c208f2d
Merge branch 'main' into add-type-stubs
IvanIsCoding Feb 16, 2022
7190bf5
Update dependencies
IvanIsCoding Feb 16, 2022
b35bb02
Correct pytest-mypy-testing version
IvanIsCoding Feb 16, 2022
16df41c
Merge branch 'main' into add-type-stubs
IvanIsCoding Apr 29, 2022
6506370
Merge branch 'main' into add-type-stubs
IvanIsCoding May 3, 2022
28ce1cc
Merge remote-tracking branch 'origin/main' into add-type-stubs
IvanIsCoding May 5, 2022
7d964af
Move tests to new folder
IvanIsCoding May 6, 2022
e025617
Black fmt
IvanIsCoding May 6, 2022
a83cce7
Add reveal_type to builtins to help flake8
IvanIsCoding May 6, 2022
59a9f90
Fix path to tests
IvanIsCoding May 6, 2022
c3dad55
Fix error due to line split
IvanIsCoding May 6, 2022
ed8ea1c
Do not test stubs on Python 3.7
IvanIsCoding May 6, 2022
63c87a6
Add test cases handling restricted methods
IvanIsCoding May 6, 2022
ed0c053
Handle lint
IvanIsCoding May 6, 2022
54a795c
Use Sequence instead of lists
IvanIsCoding May 6, 2022
8f22bb7
Minor updates in tests
IvanIsCoding May 6, 2022
342d2f5
Completely rewrite custom type annotations
IvanIsCoding May 6, 2022
937d049
Merge remote-tracking branch 'origin/main' into add-type-stubs
IvanIsCoding May 6, 2022
23c9a6d
Make PyDAG be generic
IvanIsCoding May 7, 2022
d0cb298
Shorten custom return types
IvanIsCoding May 7, 2022
fc16d41
Correct extend_from_weighted_edge_list signature
IvanIsCoding May 7, 2022
b34afe4
Fix signature
IvanIsCoding May 7, 2022
2bb0ac3
Yet another signature fix
IvanIsCoding May 7, 2022
42f77ac
Use Self from typing_extensions
IvanIsCoding May 17, 2022
ff58a0c
Address PR comments
IvanIsCoding May 17, 2022
0cab9fe
Merge remote-tracking branch 'origin/main' into add-type-stubs
IvanIsCoding May 17, 2022
a7aaf48
Address lint
IvanIsCoding May 17, 2022
20777a6
Add __str__ and from_complex_adjacency_matrix
IvanIsCoding May 17, 2022
e1a8e19
Merge branch 'main' into add-type-stubs
IvanIsCoding Jul 14, 2022
e1798e5
Merge remote-tracking branch 'origin/main' into add-type-stubs
IvanIsCoding Aug 1, 2022
f6bc49f
Rename to rustworkx
IvanIsCoding Aug 1, 2022
0cd9c9f
Change name of in custom_return_types.pyi
IvanIsCoding Aug 1, 2022
dc0d9b3
Skip tests if Python < 3.8
IvanIsCoding Aug 1, 2022
ad83bab
Ignore no tests found
IvanIsCoding Aug 2, 2022
2da1178
Merge branch 'main' into add-type-stubs
IvanIsCoding Sep 18, 2022
f91ab74
Merge remote-tracking branch 'upstream/main' into add-type-stubs
IvanIsCoding Feb 1, 2023
05c7d60
Try using pytest==7.1.3
IvanIsCoding Feb 1, 2023
37d3c23
Improvements after running mypy.stubtest
IvanIsCoding Feb 1, 2023
5e9c86e
More improvements after mypy.stubtest
IvanIsCoding Feb 1, 2023
c24f8f8
Make .pyi file names identical to Rust names
IvanIsCoding Feb 1, 2023
a441d39
Make mypy.stubtest happy
IvanIsCoding Feb 2, 2023
265fbe9
Use mypy.stubtest on CI
IvanIsCoding Feb 2, 2023
c16ae22
Run Black
IvanIsCoding Feb 2, 2023
9a21cae
Remove things that we do not need anymore
IvanIsCoding Feb 2, 2023
119cdb3
More things I forgot to remove
IvanIsCoding Feb 2, 2023
fb27883
Skip Python 3.7 for mypy
IvanIsCoding Feb 2, 2023
968f9d3
Add test stubs without Python 3.7
IvanIsCoding Feb 2, 2023
5ed884b
Don't use latest mypy release
IvanIsCoding Feb 21, 2023
4eab027
Merge branch 'main' into add-type-stubs
IvanIsCoding Feb 21, 2023
91a1f27
Merge branch 'main' into add-type-stubs
mergify[bot] Mar 9, 2023
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
5 changes: 4 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
- name: Black Codestyle Format
run: black --check --diff retworkx tests
- name: Python Lint
run: flake8 --per-file-ignores='retworkx/__init__.py:F405,F403' setup.py retworkx tests
run: flake8 --per-file-ignores='retworkx/__init__.py:F405,F403 tests/stubs/*:E501' setup.py retworkx tests
- name: retworkx-core Rust Tests
run: pushd retworkx-core && cargo test && popd
- name: retworkx-core Docs
Expand Down Expand Up @@ -88,6 +88,9 @@ jobs:
if: runner.os == 'Linux'
- name: 'Run tests'
run: tox -epy
- name: 'Run stubs-tests'
run: tox -estubs
if: runner.os == 'Linux' && ${{ matrix.python-version }} != 3.7
coverage:
needs: [tests]
name: Coverage
Expand Down
19 changes: 19 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,25 @@ web browser by running:
cargo doc --open
```

### Type Annotations

If you have added new methods or changed method signatures, we encourage you to add annotations for
those methods in stub files. The stub files are in the `retworkx` directory and have a `.pyi` file extension.
They contain annotated signatures for Python functions, stripped of their implementation.

While this step is optional, it is very helpful for end-users. Adding annotations lets users type check
their code with [mypy](http://mypy-lang.org/), which can be helpful for finding bugs.

Just like with tests for the code, annotations are also tested via tox. On Linux and Mac OS
machines, the tests can be executed via:

```
tox -estubs
```

We also encourage type annotation contributions
to add tests in the `tests/stubs` directory to verify the correctness of the annotations.

### Release Notes

It is important to document any end user facing changes when we release a new
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ include Cargo.toml
include Cargo.lock
recursive-include src *
recursive-include retworkx-core *
recursive-include retworkx *.pyi py.typed
6 changes: 6 additions & 0 deletions releasenotes/notes/graph-annotations-1d436930bf60c5c2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
features:
- |
Added type annotations to :class:`~retworkx.PyDiGraph` and
:class:`~retworkx.PyGraph`. They can now be statically type checked
with `mypy <http://mypy-lang.org>`__.
18 changes: 18 additions & 0 deletions retworkx/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

# This file contains only type annotations for PyO3 functions and classes
# For implementation details, see __init__.py and lib.rs

from .retworkx import *
from typing import Generic, TypeVar

S = TypeVar("S")
T = TypeVar("T")

class PyDAG(Generic[S, T], PyDiGraph[S, T]): ...
60 changes: 60 additions & 0 deletions retworkx/custom_return_types.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

# This file contains only type annotations for PyO3 functions and classes
# For implementation details, see __init__.py and lib.rs

from typing import Any, Generic, List, Iterable, Mapping, TypeVar, Tuple, overload
from collections.abc import ABC, Sequence
from typing_extensions import Self

S = TypeVar("S")
T_co = TypeVar("T_co", covariant=True)

class RetworkxCustomVecIter(Generic[T_co], Sequence[T_co], ABC):
def __init__(self) -> None: ...
def __eq__(self, other: Sequence[T_co]) -> bool: ...
@overload
def __getitem__(self, index: int) -> T_co: ...
@overload
def __getitem__(self: Self, index: slice) -> Self: ...
def __getstate__(self) -> Any: ...
def __hash__(self) -> int: ...
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved
def __len__(self) -> int: ...
def __ne__(self, other: Sequence[T_co]) -> bool: ...
def __setstate__(self, state) -> None: ...
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved

class RetworkxCustomHashMapIter(Generic[S, T_co], Mapping[S, T_co], ABC):
def __init__(self) -> None: ...
def items(self) -> Iterable[Tuple[S, T_co]]: ...
def keys(self) -> Iterable[S]: ...
def values(self) -> Iterable[T_co]: ...
def __contains__(self, other: S) -> bool: ...
def __eq__(self, other: Mapping[S, T_co]) -> bool: ...
def __getitem__(self, index: S) -> T_co: ...
def __getstate__(self) -> Any: ...
def __hash__(self) -> int: ...
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved
def __iter__(self) -> Iterable[S]: ...
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved
def __len__(self) -> int: ...
def __ne__(self, other: Mapping[S, T_co]) -> bool: ...
def __setstate__(self, state) -> None: ...

class NodeIndices(RetworkxCustomVecIter[int]): ...
class PathLengthMapping(RetworkxCustomHashMapIter[int, float]): ...
class PathMapping(RetworkxCustomHashMapIter[int, NodeIndices]): ...
class AllPairsPathLengthMapping(RetworkxCustomHashMapIter[int, PathLengthMapping]): ...
class AllPairsPathMapping(RetworkxCustomHashMapIter[int, PathMapping]): ...
class BFSSuccessors(Generic[T_co], RetworkxCustomVecIter[Tuple[T_co, List[T_co]]]): ...
class EdgeIndexMap(Generic[T_co], RetworkxCustomHashMapIter[int, Tuple[int, int, T_co]]): ...
class EdgeIndices(RetworkxCustomVecIter[int]): ...
class Chains(RetworkxCustomVecIter[EdgeIndices]): ...
class EdgeList(RetworkxCustomVecIter[Tuple[int, int]]): ...
class NodeMap(RetworkxCustomHashMapIter[int, int]): ...
class NodesCountMapping(RetworkxCustomHashMapIter[int, int]): ...
class Pos2DMapping(RetworkxCustomHashMapIter[int, Tuple[float, float]]): ...
class WeightedEdgeList(Generic[T_co], RetworkxCustomVecIter[Tuple[int, int, T_co]]): ...
1 change: 1 addition & 0 deletions retworkx/py.typed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
partial
155 changes: 155 additions & 0 deletions retworkx/pydigraph.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

# This file contains only type annotations for PyO3 functions and classes
# For implementation details, see __init__.py and lib.rs

import numpy as np
from .custom_return_types import *
from .pygraph import PyGraph

from typing import Any, Callable, Dict, Generic, TypeVar, Optional, List, Tuple, Sequence

S = TypeVar("S")
T = TypeVar("T")

class PyDiGraph(Generic[S, T]):
check_cycle: bool = ...
multigraph: bool = ...
def __init__(
self,
/,
check_cycle: bool = ...,
multigraph: bool = ...,
) -> None: ...
Copy link
Member

Choose a reason for hiding this comment

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

mypy can't infer the specialisation of a PyDiGraph just from constructing it and adding things using the add_child method. I'm not certain if this is a failure of mypy or if it's because of the ambiguity between T and Union[T, None] that I've mentioned elsewhere.

def add_child(self, parent: int, obj: S, edge: T, /) -> int: ...
def add_edge(self, parent: int, child: int, edge: T, /) -> int: ...
def add_edges_from(
self,
obj_list: Sequence[Tuple[int, int, T]],
/,
) -> List[int]: ...
def add_edges_from_no_data(
self: PyDiGraph[S, Optional[T]], obj_list: Sequence[Tuple[int, int]], /
) -> List[int]: ...
def add_node(self, obj: S, /) -> int: ...
def add_nodes_from(self, obj_list: Sequence[S], /) -> NodeIndices: ...
def add_parent(self, child: int, obj: S, edge: T, /) -> int: ...
def adj(self, node: int, /) -> Dict[int, T]: ...
def adj_direction(self, node: int, direction: bool, /) -> Dict[int, T]: ...
def compose(
self,
other: PyDiGraph[S, T],
node_map: Dict[int, Tuple[int, T]],
/,
node_map_func: Optional[Callable[[S], int]] = ...,
edge_map_func: Optional[Callable[[T], int]] = ...,
) -> Dict[int, int]: ...
def copy(self) -> PyDiGraph[S, T]: ...
def edge_index_map(self) -> EdgeIndexMap: ...
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved
def edge_indices(self) -> EdgeIndices: ...
def edge_list(self) -> EdgeList: ...
def edges(self) -> List[T]: ...
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved
def extend_from_edge_list(
self: PyDiGraph[Optional[S], Optional[T]], edge_list: Sequence[Tuple[int, int]], /
) -> None: ...
def extend_from_weighted_edge_list(
self: PyDiGraph[Optional[S], T],
edge_list: Sequence[Tuple[int, int, T]],
/,
) -> None: ...
def find_adjacent_node_by_edge(self, node: int, predicate: Callable[[T], bool], /) -> S: ...
def find_node_by_weight(self, obj: Callable[[S], bool]) -> Optional[int]: ...
def find_predecessors_by_edge(
self, node: int, filter_fn: Callable[[T], bool], /
) -> List[S]: ...
def find_successors_by_edge(self, node: int, filter_fn: Callable[[T], bool], /) -> List[S]: ...
@classmethod
def from_adjacency_matrix(matrix: np.array, /) -> PyDiGraph: ...
IvanIsCoding marked this conversation as resolved.
Show resolved Hide resolved
def get_all_edge_data(self, node_a: int, node_b: int, /) -> List[T]: ...
def get_edge_data(self, node_a: int, node_b: int, /) -> T: ...
def get_node_data(self, node: int, /) -> S: ...
def has_edge(self, node_a: int, node_b: int, /) -> bool: ...
def in_degree(self, node: int, /) -> int: ...
def in_edges(self, node: int, /) -> WeightedEdgeList[T]: ...
def insert_node_on_in_edges(self, node: int, ref_node: int, /) -> None: ...
def insert_node_on_in_edges_multiple(self, node: int, ref_nodes: Sequence[int], /) -> None: ...
def insert_node_on_out_edges(self, node: int, ref_node: int, /) -> None: ...
def insert_node_on_out_edges_multiple(self, node: int, ref_nodes: Sequence[int], /) -> None: ...
def is_symmetric(self) -> bool: ...
def merge_nodes(self, u: int, v: int, /) -> None: ...
def neighbors(self, node: int, /) -> NodeIndices: ...
def node_indexes(self) -> NodeIndices: ...
def nodes(self) -> List[S]: ...
def num_edges(self) -> int: ...
def num_nodes(self) -> int: ...
def out_degree(self, node: int, /) -> int: ...
def out_edges(self, node: int, /) -> WeightedEdgeList[T]: ...
def predecessor_indices(self, node: int, /) -> NodeIndices: ...
def predecessors(self, node: int, /) -> List[S]: ...
@classmethod
def read_edge_list(
path: str,
/,
comment: Optional[str] = ...,
deliminator: Optional[str] = ...,
) -> PyDiGraph: ...
def remove_edge(self, parent: int, child: int, /) -> None: ...
def remove_edge_from_index(self, edge: int, /) -> None: ...
def remove_edges_from(self, index_list: Sequence[Tuple[int, int]], /) -> None: ...
def remove_node(self, node: int, /) -> None: ...
def remove_node_retain_edges(
self,
node: int,
/,
use_outgoing: Optional[bool] = ...,
condition: Optional[Callable[[S, S], bool]] = ...,
) -> None: ...
def remove_nodes_from(self, index_list: Sequence[int], /) -> None: ...
def subgraph(self, nodes: Sequence[int], /) -> PyDiGraph[S, T]: ...
def substitute_node_with_subgraph(
self,
node: int,
other: PyDiGraph[S, T],
edge_map_fn: Callable[[int, int, T], Optional[int]],
/,
node_filter: Optional[Callable[[S], bool]] = ...,
edge_weight_map: Optional[Callable[[T], T]] = ...,
) -> NodeMap: ...
def successor_indices(self, node: int, /) -> NodeIndices: ...
def successors(self, node: int, /) -> List[S]: ...
def to_dot(
self,
/,
node_attr: Optional[Callable[[S], Dict[str, str]]] = ...,
edge_attr: Optional[Callable[[T], Dict[str, str]]] = ...,
graph_attr: Optional[Dict[str, str]] = ...,
filename: Optional[str] = ...,
) -> Optional[str]: ...
def to_undirected(
self,
/,
multigraph: bool = ...,
weight_combo_fn: Optional[Callable[[T, T], T]] = ...,
) -> PyGraph[S, T]: ...
def update_edge(self, source: int, target: int, edge: T, /) -> None: ...
def update_edge_by_index(self, edge_index: int, edge: T, /) -> None: ...
def weighted_edge_list(self) -> WeightedEdgeList[T]: ...
def write_edge_list(
self,
path: str,
/,
deliminator: Optional[str] = ...,
weight_fn: Optional[Callable[[T], str]] = ...,
) -> None: ...
def __delitem__(self, idx: int, /) -> None: ...
def __getitem__(self, idx: int, /) -> S: ...
def __getstate__(self) -> Any: ...
def __len__(self) -> int: ...
def __setitem__(self, idx: int, value: S, /) -> None: ...
def __setstate__(self, state, /) -> None: ...
Loading