Repository: networkx/networkx

Issue:
readwrite.cytoscape_graph(data) removes "source" and "target" fields from edges
Hi,

I realized the function to get a graph from cytoscape data modifies the data that is received as argument. Is there any reason for that? I couldn't find that in the documentation, and I found it when browsing the code.

Thank you in advance!



Discussion:
> I realized the function to get a graph from cytoscape data modifies the data that is received as argument. Is there any reason for that?

This looks like a defect to me, I don't think the input data dict should be modified. An MRE:

```python
>>> G = nx.path_graph(4)
>>> data_dict = nx.cytoscape_data(G)
>>> data_dict
{'data': [],
 'directed': False,
 'multigraph': False,
 'elements': {'nodes': [{'data': {'id': '0', 'value': 0, 'name': '0'}},
   {'data': {'id': '1', 'value': 1, 'name': '1'}},
   {'data': {'id': '2', 'value': 2, 'name': '2'}},
   {'data': {'id': '3', 'value': 3, 'name': '3'}}],
  'edges': [{'data': {'source': 0, 'target': 1}},
   {'data': {'source': 1, 'target': 2}},
   {'data': {'source': 2, 'target': 3}}]}}
>>> H = nx.cytoscape_graph(data_dict)
>>> data_dict   # 'source' and 'target' keys no longer present for edges
{'data': [],
 'directed': False,
 'multigraph': False,
 'elements': {'nodes': [{'data': {'id': '0', 'value': 0, 'name': '0'}},
   {'data': {'id': '1', 'value': 1, 'name': '1'}},
   {'data': {'id': '2', 'value': 2, 'name': '2'}},
   {'data': {'id': '3', 'value': 3, 'name': '3'}}],
  'edges': [{'data': {}}, {'data': {}}, {'data': {}}]}}
```

> I couldn't find that in the documentation, and I found it when browsing the code.

Unfortunately `cytoscape_graph` is also missing a docstring! Thanks for catching this as well.
FWIW this should be a very straightforward fix that is suitable for new contributors. To do so would require the following:
 1. Add/modify a test in `networkx/readwrite/json_graph/tests/test_cytoscape.py` that will fail with the current behavior but pass with the desired behavior (i.e. the input data is not modified when `cytoscape_graph` is called).
 2. Modify the `cytoscape_graph` function defined in `networkx/readwrite/json_graph/cytoscape.py` so that the input data is not modified by the function

Resolving patch:
diff --git a/networkx/readwrite/json_graph/cytoscape.py b/networkx/readwrite/json_graph/cytoscape.py
--- a/networkx/readwrite/json_graph/cytoscape.py
+++ b/networkx/readwrite/json_graph/cytoscape.py
@@ -98,8 +98,8 @@ def cytoscape_graph(data, attrs=None):
 
     for d in data["elements"]["edges"]:
         edge_data = d["data"].copy()
-        sour = d["data"].pop("source")
-        targ = d["data"].pop("target")
+        sour = d["data"]["source"]
+        targ = d["data"]["target"]
         if multigraph:
             key = d["data"].get("key", 0)
             graph.add_edge(sour, targ, key=key)


Test patch:
diff --git a/networkx/readwrite/json_graph/tests/test_cytoscape.py b/networkx/readwrite/json_graph/tests/test_cytoscape.py
--- a/networkx/readwrite/json_graph/tests/test_cytoscape.py
+++ b/networkx/readwrite/json_graph/tests/test_cytoscape.py
@@ -1,6 +1,7 @@
 import json
 import pytest
 import networkx as nx
+import copy
 from networkx.readwrite.json_graph import cytoscape_data, cytoscape_graph
 
 
@@ -10,6 +11,14 @@ def test_graph(self):
         H = cytoscape_graph(cytoscape_data(G))
         nx.is_isomorphic(G, H)
 
+    def test_input_data_is_not_modified_when_building_graph(self):
+        G = nx.path_graph(4)
+        input_data = cytoscape_data(G)
+        orig_data = copy.deepcopy(input_data)
+        # Ensure input is unmodified by cytoscape_graph (gh-4173)
+        cytoscape_graph(input_data)
+        assert input_data == orig_data
+
     def test_graph_attributes(self):
         G = nx.path_graph(4)
         G.add_node(1, color="red")