Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
ff76ef8
Remove retworkx and Python 3.9 support
IvanIsCoding Sep 21, 2025
729755d
Add release notes for Python 3.9 EoL
IvanIsCoding Sep 21, 2025
2fbb343
Add retworkx removal release note
IvanIsCoding Sep 21, 2025
7df0fe9
Merge remote-tracking branch 'upstream/main' into remove-39-retworkx
IvanIsCoding Sep 21, 2025
3084019
Drop 3.9 support in pyproject.toml and uv
IvanIsCoding Sep 21, 2025
1dd621c
Bump other 39 -> 310 related things
IvanIsCoding Sep 21, 2025
f52487b
Merge remote-tracking branch 'upstream/main' into remove-39-retworkx
IvanIsCoding Oct 24, 2025
e5ca8f4
use 3.14 not 3.14-dev
IvanIsCoding Oct 24, 2025
78ce745
Move note to upgrade
IvanIsCoding Oct 24, 2025
711cfdc
Upgrade packages to support 3.14
IvanIsCoding Nov 2, 2025
03afe1b
Allowlist missing __all__ for mypy
IvanIsCoding Nov 2, 2025
a222850
Add 3.14 to workflow
IvanIsCoding Nov 2, 2025
7c87aa7
Add release note
IvanIsCoding Nov 2, 2025
fe13da0
Use Python 3.12 for release
IvanIsCoding Nov 2, 2025
03e6329
Skip Python 3.9
IvanIsCoding Nov 2, 2025
1ebe14a
Skip 3.14 free-threaded build
IvanIsCoding Nov 2, 2025
5b76c1f
Bump nox as well
IvanIsCoding Nov 3, 2025
e33e188
Merge branch 'main' into remove-39-retworkx
IvanIsCoding Nov 4, 2025
b875800
Merge branch 'main' into upgrade-314
IvanIsCoding Nov 4, 2025
795ad00
Use ruff
IvanIsCoding Nov 30, 2025
9b124f5
Use ruff in CI
IvanIsCoding Nov 30, 2025
6ea0993
Merge branch 'main' into ruff-format
IvanIsCoding Nov 30, 2025
435fb73
Merge branch 'main' into ruff-format
IvanIsCoding Dec 5, 2025
a2c5ea0
Merge branch 'main' into upgrade-314
IvanIsCoding Dec 5, 2025
7bd603c
Merge branch 'main' into remove-39-retworkx
IvanIsCoding Dec 5, 2025
b9d1a15
Use macos-15-intel for tests
IvanIsCoding Dec 13, 2025
cfa1ae6
Update wheels.yml to use macos-15-intel
IvanIsCoding Dec 13, 2025
418b6fc
Merge branch 'remove-39-retworkx' into upgrade-314
IvanIsCoding Dec 13, 2025
9e0b0b3
Merge branch 'upgrade-314' into ruff-format
IvanIsCoding Dec 13, 2025
11b9252
Merge branch 'main' into upgrade-314
IvanIsCoding Dec 13, 2025
b5600b9
Merge branch 'main' into ruff-format
IvanIsCoding Dec 13, 2025
bf72c35
Merge remote-tracking branch 'upstream/main' into upgrade-314
IvanIsCoding Dec 16, 2025
f53d0bc
Apply suggestions from code review
IvanIsCoding Dec 16, 2025
42ad1e1
Merge branch 'upgrade-314' into ruff-format
IvanIsCoding Dec 16, 2025
25f0345
Reflect cibuildwheel in uv
IvanIsCoding Dec 16, 2025
c5d5c0d
Merge branch 'upgrade-314' into ruff-format
IvanIsCoding Dec 16, 2025
c4b9f4e
Merge branch 'main' into upgrade-314
IvanIsCoding Jan 1, 2026
f9a9a09
Merge branch 'upgrade-314' into ruff-format
IvanIsCoding Jan 1, 2026
9993727
Merge branch 'main' into ruff-format
IvanIsCoding Jan 29, 2026
d0082b8
Merge branch 'main' into ruff-format
IvanIsCoding Jan 29, 2026
3bc5342
Merge branch 'main' into ruff-format
IvanIsCoding Jan 29, 2026
9a802ea
Merge branch 'main' into ruff-format
IvanIsCoding Jan 29, 2026
9ee3e3b
Minimize uv lock diff
IvanIsCoding Jan 30, 2026
989ead8
Merge branch 'main' into ruff-format
IvanIsCoding Jan 30, 2026
d55900d
Merge branch 'main' into ruff-format
IvanIsCoding Jan 30, 2026
0886043
Merge branch 'main' into ruff-format
IvanIsCoding Feb 1, 2026
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 .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ jobs:
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --workspace --all-targets -- -D warnings
- name: Black Codestyle Format
run: black --check --diff rustworkx tests
- name: Ruff Codestyle Format
run: ruff format --check --diff rustworkx tests
- name: Python Lint
run: ruff check rustworkx setup.py tests
- name: Check stray release notes
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ cargo clippy --workspace --all-targets -- -D warnings

Python is used primarily for tests and some small pieces of packaging
and namespace configuration code in the actual library.
[black](https://github.com/psf/black) and [flake8](https://flake8.pycqa.org/en/latest/) are used to enforce consistent
[ruff](https://github.com/astral-sh/ruff) is used to enforce consistent
style in the python code in the repository. You can run them via Nox using:

```bash
Expand All @@ -295,7 +295,7 @@ nox -e lint
This will also run `cargo fmt` in check mode to ensure that you ran `cargo fmt`
and will fail if the Rust code doesn't conform to the style rules.

If black returns a code formatting error you can run `nox -e black` to automatically
If ruff returns a code formatting error you can run `nox -e format` to automatically
update the code formatting to conform to the style.

### Building documentation
Expand Down
11 changes: 8 additions & 3 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def test_with_version(session):

@nox.session(python=["3"])
def lint(session):
black(session)
format(session)
typos(session)
session.install(*lint_deps)
session.run("ruff", "check", "rustworkx", "setup.py")
Expand Down Expand Up @@ -71,10 +71,15 @@ def docs_clean(session):
session.chdir("docs")
session.run("rm", "-rf", "build", "source/apiref", external=True)

@nox.session(python=["3"])
def format(session):
session.install(*[d for d in lint_deps if "ruff" in d])
session.run("ruff", "format", "rustworkx", "tests", *session.posargs)

@nox.session(python=["3"])
def black(session):
session.install(*[d for d in lint_deps if "black" in d])
session.run("black", "rustworkx", "tests", *session.posargs)
# Legacy black formatting session is aliased
format(session)

@nox.session(python=["3"])
def typos(session):
Expand Down
15 changes: 9 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ test = [
"stestr>=4.1",
]
lint = [
"black~=25.9",
"ruff==0.14.3",
"setuptools-rust",
"typos~=1.39",
Expand All @@ -103,12 +102,8 @@ releaseinfra = [
"cibuildwheel==3.3.0; python_version >= '3.11'",
]

[tool.black]
line-length = 100
target-version = ['py310', 'py311', 'py312', 'py313', 'py314']

[tool.ruff]
line-length = 105 # more lenient than black due to long function signatures
line-length = 100
src = ["rustworkx", "setup.py", "tests"]
lint.select = [
"E", # pycodestyle
Expand All @@ -124,6 +119,14 @@ extend-exclude = ["doc"]
"rustworkx/__init__.py" = ["F405", "F403"]
"*.pyi" = ["F403", "F405", "PYI001", "PYI002"]

[tool.ruff.lint.pycodestyle]
max-line-length = 105 # lint has a larger limit than the formatter because of long signatures

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
docstring-code-format = true

[tool.typos.default]
extend-ignore-words-re = [
"[Ss]toer",
Expand Down
18 changes: 9 additions & 9 deletions rustworkx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,14 @@ class PyDAG(PyDiGraph):
attribute to True. For example::

import rustworkx as rx

dag = rx.PyDAG()
dag.check_cycle = True

or at object creation::

import rustworkx as rx

dag = rx.PyDAG(check_cycle=True)

With check_cycle set to true any calls to :meth:`PyDAG.add_edge` will
Expand All @@ -110,6 +112,7 @@ class PyDAG(PyDiGraph):
For example::

import rustworkx as rx

dag = rx.PyDAG(multigraph=False)

This can only be set at ``PyDiGraph`` initialization and not adjusted after
Expand Down Expand Up @@ -303,7 +306,7 @@ def floyd_warshall(
tells rustworkx/rust how to extract a numerical weight as a ``float``
for edge object. Some simple examples are::

floyd_warshall(graph, weight_fn= lambda x: 1)
floyd_warshall(graph, weight_fn=lambda x: 1)

to return a weight of 1 for all edges. Also::

Expand Down Expand Up @@ -489,7 +492,7 @@ def all_pairs_dijkstra_shortest_paths(graph, edge_cost_fn):
of node indices making the path. For example::

{
0: {1: [0, 1], 2: [0, 1, 2]},
0: {1: [0, 1], 2: [0, 1, 2]},
1: {2: [1, 2]},
2: {0: [2, 0]},
}
Expand Down Expand Up @@ -669,8 +672,7 @@ def is_isomorphic(

graph_a = rustworkx.PyGraph()
graph_b = rustworkx.PyGraph()
rustworkx.is_isomorphic(graph_a, graph_b,
lambda x, y: x == y)
rustworkx.is_isomorphic(graph_a, graph_b, lambda x, y: x == y)

.. note::

Expand Down Expand Up @@ -717,8 +719,7 @@ def is_isomorphic_node_match(first, second, matcher, id_order=True):

graph_a = rustworkx.PyDAG()
graph_b = rustworkx.PyDAG()
rustworkx.is_isomorphic_node_match(graph_a, graph_b,
lambda x, y: x == y)
rustworkx.is_isomorphic_node_match(graph_a, graph_b, lambda x, y: x == y)

.. note::

Expand Down Expand Up @@ -768,8 +769,7 @@ def is_subgraph_isomorphic(

graph_a = rustworkx.PyGraph()
graph_b = rustworkx.PyGraph()
rustworkx.is_subgraph_isomorphic(graph_a, graph_b,
lambda x, y: x == y)
rustworkx.is_subgraph_isomorphic(graph_a, graph_b, lambda x, y: x == y)


:param first: The first graph to compare. Can either be a
Expand Down Expand Up @@ -2018,7 +2018,7 @@ def all_pairs_bellman_ford_shortest_paths(graph, edge_cost_fn):
of node indices making the path. For example::

{
0: {1: [0, 1], 2: [0, 1, 2]},
0: {1: [0, 1], 2: [0, 1, 2]},
1: {2: [1, 2]},
2: {0: [2, 0]},
}
Expand Down
8 changes: 4 additions & 4 deletions rustworkx/visualization/matplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,23 +142,23 @@ def mpl_draw(graph, pos=None, ax=None, arrows=True, with_labels=False, **kwds):
:param func labels: An optional callback function that will be passed a
node payload and return a string label for the node. For example::

labels=str
labels = str

could be used to just return a string cast of the node's data payload.
Or something like::

labels=lambda node: node['label']
labels = lambda node: node["label"]

could be used if the node payloads are dictionaries.
:param func edge_labels: An optional callback function that will be passed
an edge payload and return a string label for the edge. For example::

edge_labels=str
edge_labels = str

could be used to just return a string cast of the edge's data payload.
Or something like::

edge_labels=lambda edge: edge['label']
edge_labels = lambda edge: edge["label"]

could be used if the edge payloads are dictionaries. If this is set
edge labels will be drawn in the visualization.
Expand Down
3 changes: 0 additions & 3 deletions tests/digraph/test_bfs_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ def tree_edge(self, edge):

def test_digraph_bfs_tree_edges_restricted(self):
class TreeEdgesRecorderRestricted(rustworkx.visit.BFSVisitor):

prohibited = [(0, 2), (1, 2)]

def __init__(self):
Expand All @@ -75,7 +74,6 @@ def tree_edge(self, edge):

def test_digraph_bfs_goal_search_with_stop_search_exception(self):
class GoalSearch(rustworkx.visit.BFSVisitor):

goal = 3

def __init__(self):
Expand Down Expand Up @@ -107,7 +105,6 @@ class StopIfGoalFound(Exception):
pass

class GoalSearch(rustworkx.visit.BFSVisitor):

goal = 3

def __init__(self):
Expand Down
2 changes: 0 additions & 2 deletions tests/digraph/test_dfs_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ def tree_edge(self, edge):

def test_digraph_dfs_tree_edges_restricted(self):
class TreeEdgesRecorderRestricted(rustworkx.visit.DFSVisitor):

prohibited = [(0, 1), (5, 3)]

def __init__(self):
Expand All @@ -75,7 +74,6 @@ def tree_edge(self, edge):

def test_digraph_dfs_goal_search(self):
class GoalSearch(rustworkx.visit.DFSVisitor):

goal = 3

def __init__(self):
Expand Down
3 changes: 0 additions & 3 deletions tests/digraph/test_dijkstra_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ def edge_relaxed(self, edge):

def test_digraph_dijkstra_goal_search_with_stop_search_exception(self):
class GoalSearch(rustworkx.visit.DijkstraVisitor):

goal = 3

def __init__(self):
Expand Down Expand Up @@ -107,7 +106,6 @@ class StopIfGoalFound(Exception):
pass

class GoalSearch(rustworkx.visit.DijkstraVisitor):

goal = 3

def __init__(self):
Expand Down Expand Up @@ -143,7 +141,6 @@ def reconstruct_path(self):

def test_digraph_dijkstra_goal_search_with_prohibited_edges(self):
class GoalSearch(rustworkx.visit.DijkstraVisitor):

goal = 3
prohibited = [(5, 3)]

Expand Down
3 changes: 0 additions & 3 deletions tests/graph/test_bfs_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ def tree_edge(self, edge):

def test_graph_bfs_tree_edges_restricted(self):
class TreeEdgesRecorderRestricted(rustworkx.visit.BFSVisitor):

prohibited = [(0, 2), (1, 2)]

def __init__(self):
Expand All @@ -75,7 +74,6 @@ def tree_edge(self, edge):

def test_graph_bfs_goal_search_with_stop_search_exception(self):
class GoalSearch(rustworkx.visit.BFSVisitor):

goal = 3

def __init__(self):
Expand Down Expand Up @@ -107,7 +105,6 @@ class StopIfGoalFound(Exception):
pass

class GoalSearch(rustworkx.visit.BFSVisitor):

goal = 3

def __init__(self):
Expand Down
2 changes: 0 additions & 2 deletions tests/graph/test_dfs_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ def tree_edge(self, edge):

def test_graph_dfs_tree_edges_restricted(self):
class TreeEdgesRecorderRestricted(rustworkx.visit.DFSVisitor):

prohibited = [(0, 2), (1, 2)]

def __init__(self):
Expand All @@ -75,7 +74,6 @@ def tree_edge(self, edge):

def test_graph_dfs_goal_search(self):
class GoalSearch(rustworkx.visit.DFSVisitor):

goal = 3

def __init__(self):
Expand Down
3 changes: 0 additions & 3 deletions tests/graph/test_dijkstra_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ def edge_relaxed(self, edge):

def test_graph_dijkstra_goal_search_with_stop_search_exception(self):
class GoalSearch(rustworkx.visit.DijkstraVisitor):

goal = 3

def __init__(self):
Expand Down Expand Up @@ -107,7 +106,6 @@ class StopIfGoalFound(Exception):
pass

class GoalSearch(rustworkx.visit.DijkstraVisitor):

goal = 3

def __init__(self):
Expand Down Expand Up @@ -143,7 +141,6 @@ def reconstruct_path(self):

def test_graph_dijkstra_goal_search_with_prohibited_edges(self):
class GoalSearch(rustworkx.visit.DijkstraVisitor):

goal = 3
prohibited = [(5, 3)]

Expand Down
5 changes: 2 additions & 3 deletions tests/graph/test_dot.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,15 @@ def test_graph_empty_dicts(self):
graph = rustworkx.undirected_gnp_random_graph(3, 0.9, seed=42)
dot_str = graph.to_dot(lambda _: {}, lambda _: {})
self.assertEqual(
"graph {\n0 ;\n1 ;\n2 ;\n1 -- 0 ;\n2 -- 0 ;\n" "2 -- 1 ;\n}\n",
"graph {\n0 ;\n1 ;\n2 ;\n1 -- 0 ;\n2 -- 0 ;\n2 -- 1 ;\n}\n",
dot_str,
)

def test_graph_graph_attrs(self):
graph = rustworkx.undirected_gnp_random_graph(3, 0.9, seed=42)
dot_str = graph.to_dot(lambda _: {}, lambda _: {}, {"bgcolor": "red"})
self.assertEqual(
"graph {\nbgcolor=red ;\n0 ;\n1 ;\n2 ;\n1 -- 0 ;\n" "2 -- 0 ;\n2 -- 1 ;\n}\n",
"graph {\nbgcolor=red ;\n0 ;\n1 ;\n2 ;\n1 -- 0 ;\n2 -- 0 ;\n2 -- 1 ;\n}\n",
dot_str,
)

Expand Down Expand Up @@ -150,7 +150,6 @@ def test_from_dot_digraph(self):
self.assertEqual(len(g.edges()), 1)

def test_graph_roundtrip_with_attrs(self):

graph = rustworkx.PyGraph()
graph.add_node(
{
Expand Down
1 change: 0 additions & 1 deletion tests/graph/test_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@


class TestHyperbolicGreedyRouting(unittest.TestCase):

def test_invalid_node_error(self):
graph = rx.PyGraph()
positions = []
Expand Down
1 change: 0 additions & 1 deletion tests/graph/test_steiner_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ def test_metric_closure(self):
if edge in edges:
found = True
if not found:

if (
edge[1],
edge[0],
Expand Down
6 changes: 2 additions & 4 deletions tests/test_custom_return_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1202,10 +1202,8 @@ def test_str(self):
# Since all_pairs_dijkstra_path_lengths() is parallel the order of the
# output is non-deterministic
valid_values = [
"AllPairsPathLengthMapping{1: PathLengthMapping{}, " "0: PathLengthMapping{1: 3.14}}",
"AllPairsPathLengthMapping{"
"0: PathLengthMapping{1: 3.14}, "
"1: PathLengthMapping{}}",
"AllPairsPathLengthMapping{1: PathLengthMapping{}, 0: PathLengthMapping{1: 3.14}}",
"AllPairsPathLengthMapping{0: PathLengthMapping{1: 3.14}, 1: PathLengthMapping{}}",
]
self.assertIn(str(res), valid_values)

Expand Down
2 changes: 0 additions & 2 deletions tests/test_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@


class TestDispatchPyGraph(unittest.TestCase):

class_type = "PyGraph"

def setUp(self):
Expand Down Expand Up @@ -103,5 +102,4 @@ def test_betweenness_centrality(self):


class TestDispatchPyDiGraph(TestDispatchPyGraph):

class_type = "PyDiGraph"
Loading