Repository: networkx/networkx

Issue:
Deprecate or improve `utils.misc.to_tuple`
`to_tuple` is a simple function that recursively converts nested lists to tuples. It is only used in one place within networkx in the `json_graph` module:

https://github.com/networkx/networkx/blob/2e490a307b97518c98066c058136981dcae95606/networkx/readwrite/json_graph/node_link.py#L167

The usage here makes sense as there's a potential use-case for a node that is stored in json as a nested list (though this case isn't tested). However, it should be noted that this specific usage depends on the fact that `to_tuple` is a no-op for every input *except* Python lists.

Given the fact that it is only used once internally, has a relatively specific use-case, and doesn't seem to have any other obvious connection to common operations in NetworkX, I would propose to do the following:
 - Move the definition over to the `json_graph.node_link` module as a private function, and
 - Deprecate the public function `nx.utils.misc.to_tuple`

If deprecating/removing the function seems too strong, then another option would be to improve the documentation and testing. Documentation should have usage examples to illustrate where it might be useful and also highlight the fact that the function is a no-op for every input except lists.

At face value I'd vote for deprecation because I don't see a strong connection to the rest of the library, but please share your opinion and any other ideas for how to handle this!


Discussion:
Playing devil's advocate against myself... one argument against deprecation is there isn't really a built-in one-liner or simple fix that we can point to instead (at least off the top of my head). I still lean towards deprecation since the behavior is so specific, but definitely another angle to consider!
I agree that this makes sense as a private function in the `json_graph` code. And we can deprecate the public version and move the code to the new place.

Resolving patch:
diff --git a/networkx/conftest.py b/networkx/conftest.py
--- a/networkx/conftest.py
+++ b/networkx/conftest.py
@@ -233,6 +233,7 @@ def set_warnings():
         "ignore", category=DeprecationWarning, message="to_numpy_recarray"
     )
     warnings.filterwarnings("ignore", category=DeprecationWarning, message="info")
+    warnings.filterwarnings("ignore", category=DeprecationWarning, message="to_tuple")
 
 
 @pytest.fixture(autouse=True)
diff --git a/networkx/readwrite/json_graph/node_link.py b/networkx/readwrite/json_graph/node_link.py
--- a/networkx/readwrite/json_graph/node_link.py
+++ b/networkx/readwrite/json_graph/node_link.py
@@ -1,6 +1,5 @@
 from itertools import chain, count
 import networkx as nx
-from networkx.utils import to_tuple
 
 __all__ = ["node_link_data", "node_link_graph"]
 
@@ -8,6 +7,23 @@
 _attrs = dict(source="source", target="target", name="id", key="key", link="links")
 
 
+def _to_tuple(x):
+    """Converts lists to tuples, including nested lists.
+
+    All other non-list inputs are passed through unmodified. This function is
+    intended to be used to convert potentially nested lists from json files
+    into valid nodes.
+
+    Examples
+    --------
+    >>> _to_tuple([1, 2, [3, 4]])
+    (1, 2, (3, 4))
+    """
+    if not isinstance(x, (tuple, list)):
+        return x
+    return tuple(map(_to_tuple, x))
+
+
 def node_link_data(G, attrs=None):
     """Returns data in node-link format that is suitable for JSON serialization
     and use in Javascript documents.
@@ -164,7 +180,7 @@ def node_link_graph(data, directed=False, multigraph=True, attrs=None):
     graph.graph = data.get("graph", {})
     c = count()
     for d in data["nodes"]:
-        node = to_tuple(d.get(name, next(c)))
+        node = _to_tuple(d.get(name, next(c)))
         nodedata = {str(k): v for k, v in d.items() if k != name}
         graph.add_node(node, **nodedata)
     for d in data[links]:
diff --git a/networkx/utils/misc.py b/networkx/utils/misc.py
--- a/networkx/utils/misc.py
+++ b/networkx/utils/misc.py
@@ -437,6 +437,10 @@ def groups(many_to_one):
 def to_tuple(x):
     """Converts lists to tuples.
 
+    .. deprecated:: 2.8
+
+       to_tuple is deprecated and will be removed in NetworkX 3.0.
+
     Examples
     --------
     >>> from networkx.utils import to_tuple
@@ -444,6 +448,12 @@ def to_tuple(x):
     >>> to_tuple(a_list)
     (1, 2, (1, 4))
     """
+    warnings.warn(
+        "to_tuple is deprecated and will be removed in NetworkX 3.0.",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+
     if not isinstance(x, (tuple, list)):
         return x
     return tuple(map(to_tuple, x))


Test patch: