Repository: networkx/networkx

Issue:
upgrade 2.8.6  -> 3.0 breaks shortest_path()
<!--- Provide a general summary of the issue in the Title above -->
I just re-installed networkx without specifying version so I was upgraded from 2.8.6 to 3.0
Running unaltered code calling nx.shortest_path(*args, **kwargs) under 2.8.6 runs fine and produces correct result but 3.0 fails with an error, missing arguments.

Looking at the source, the 3.0 version got a decorator which doesn't seem to work properly.

The error is reproduced using stripped-down [lollipop example](https://networkx.org/documentation/stable/auto_examples/basic/plot_properties.html#sphx-glr-auto-examples-basic-plot-properties-py)

### Current Behavior

under 3.0:
```
import networkx as nx
G = nx.lollipop_graph(4, 6)
print(nx.shortest_path(G=G, source=list(G.nodes())[1], target=list(G.nodes())[9]))
```
produces

```
Traceback (most recent call last):
  File "C:\Users\XYZ\AppData\Roaming\JetBrains\PyCharm2022.3\scratches\scratch_1.py", line 7, in <module>
    print(nx.shortest_path(G=G, source=list(G.nodes())[1], target=list(G.nodes())[9]))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\modules\venv\networkx_3\Lib\site-packages\networkx\classes\backends.py", line 134, in wrapper
    graph = args[0]
            ~~~~^^^
IndexError: tuple index out of range
```

### Expected Behavior

under 2.8.6:
```
import networkx as nx
G = nx.lollipop_graph(4, 6)
print(nx.shortest_path(G=G, source=list(G.nodes())[1], target=list(G.nodes())[9]))
```
produces
`[1, 3, 4, 5, 6, 7, 8, 9]`

### Environment
Python version: anaconda 3.10, networkx installed via pip
NetworkX version: 2.8.6 and 3.0



Discussion:
The error only occurs if you give a keyword name to the input G.  
We need to fix that...  but try it with input `G` instead of `G=G` and it should work just fine.

Perhaps we should make that input a position-only input. But maybe it would be possible to make
the backend code find keyword arguments as well as position arguments. 
(if len(args)<1 look for a keyword `G`? But then we can't apply it to functions that name the graph something else -- if there are any such functions :})
> The error only occurs if you give a keyword name to the input G.
> We need to fix that... but try it with input `G` instead of `G=G` and it should work just fine.

I confirm this.

> Perhaps we should make that input a position-only input.

That would reduce compatibility to 3.8+ if I'm not mistaken. IDK if it is a good solution but what about raising an exception if len(args) != 0?
Urghh, yes this is an issue. Thanks for opening the issue! We should have caught this before.

> That would reduce compatibility to 3.8+ if I'm not mistaken. IDK if it is a good solution but what about raising an exception if len(args) != 0?

We already are py3.8+ for new networkx releases.
> That would reduce compatibility to 3.8+ if I'm not mistaken. IDK if it is a good solution but what about raising an exception if len(args) != 0?

I meant !=1 of course.
Anyway, thanks for all the effort put into this superb package, it greatly helped me to solve some of the problems I had faced in my apps.

We'd have to make any function that uses the `_dispatch` decorator make the first input position-only.
Would it be more flexible to allow the `_dispatch` decorator to include as an argument the name of the possibly keyword input variable? It could default to `G` and not change much code anywhere -- and we'd have to document that anyone decorating a function with first argument called something other than `G` will need to use this argument for the decorator.

Then inside the decorator code we check if `len(args)<1` and if so, we look up the keyword arg.
(of course, maybe we want to allow dispatch to position arguments other than  the first position...  but one step at a time here... :}  Not sure what is best...
> We'd have to make any function that uses the _dispatch decorator make the first input position-only.
Would it be more flexible to allow the _dispatch decorator to include as an argument the name of the possibly keyword input variable? It could default to G and not change much code anywhere -- and we'd have to document that anyone decorating a function with first argument called something other than G will need to use this argument for the decorator.

We anyway have to revisit this specific bit of the decorator as right now the assumption is that there is only 1 argument with a graph object. The current `_dispatch` implementation wouldn't work for functions where you expect 2 graph inputs (like isomorphism). IMO these changes should happen inside the `_dispatch` decorator and we should do better job of standardizing function arguments (like it should either be just `G` or `graph` or something) elsewhere in the codebase.
Sounds good.  I think it is pretty universal within NetworkX that the graph goes first and it is named `G`.
I guess that might be the first fix then...  Change `_dispatch` to check if `len(args)` is zero and if so, look at `kwargs` for an entry called `G`.  That seems easier than going through the codebase to add position-only syntax for each function that takes a graph as the first argument.  :)
CC @jim22k

Resolving patch:
diff --git a/networkx/classes/backends.py b/networkx/classes/backends.py
--- a/networkx/classes/backends.py
+++ b/networkx/classes/backends.py
@@ -128,7 +128,13 @@ def _dispatch(func=None, *, name=None):
 
     @functools.wraps(func)
     def wrapper(*args, **kwds):
-        graph = args[0]
+        if args:
+            graph = args[0]
+        else:
+            try:
+                graph = kwds["G"]
+            except KeyError:
+                raise TypeError(f"{name}() missing positional argument: 'G'") from None
         if hasattr(graph, "__networkx_plugin__") and plugins:
             plugin_name = graph.__networkx_plugin__
             if plugin_name in plugins:


Test patch:
diff --git a/networkx/classes/tests/test_backends.py b/networkx/classes/tests/test_backends.py
new file mode 100644
--- /dev/null
+++ b/networkx/classes/tests/test_backends.py
@@ -0,0 +1,14 @@
+import pytest
+
+import networkx as nx
+
+pytest.importorskip("scipy")
+pytest.importorskip("numpy")
+
+
+def test_dispatch_kwds_vs_args():
+    G = nx.path_graph(4)
+    nx.pagerank(G)
+    nx.pagerank(G=G)
+    with pytest.raises(TypeError):
+        nx.pagerank()