about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/networkx/readwrite/tests/test_gml.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/readwrite/tests/test_gml.py')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/readwrite/tests/test_gml.py744
1 files changed, 744 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/readwrite/tests/test_gml.py b/.venv/lib/python3.12/site-packages/networkx/readwrite/tests/test_gml.py
new file mode 100644
index 00000000..f575ad26
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/readwrite/tests/test_gml.py
@@ -0,0 +1,744 @@
+import codecs
+import io
+import math
+from ast import literal_eval
+from contextlib import contextmanager
+from textwrap import dedent
+
+import pytest
+
+import networkx as nx
+from networkx.readwrite.gml import literal_destringizer, literal_stringizer
+
+
+class TestGraph:
+    @classmethod
+    def setup_class(cls):
+        cls.simple_data = """Creator "me"
+Version "xx"
+graph [
+ comment "This is a sample graph"
+ directed 1
+ IsPlanar 1
+ pos  [ x 0 y 1 ]
+ node [
+   id 1
+   label "Node 1"
+   pos [ x 1 y 1 ]
+ ]
+ node [
+    id 2
+    pos [ x 1 y 2 ]
+    label "Node 2"
+    ]
+  node [
+    id 3
+    label "Node 3"
+    pos [ x 1 y 3 ]
+  ]
+  edge [
+    source 1
+    target 2
+    label "Edge from node 1 to node 2"
+    color [line "blue" thickness 3]
+
+  ]
+  edge [
+    source 2
+    target 3
+    label "Edge from node 2 to node 3"
+  ]
+  edge [
+    source 3
+    target 1
+    label "Edge from node 3 to node 1"
+  ]
+]
+"""
+
+    def test_parse_gml_cytoscape_bug(self):
+        # example from issue #321, originally #324 in trac
+        cytoscape_example = """
+Creator "Cytoscape"
+Version 1.0
+graph   [
+    node    [
+        root_index  -3
+        id  -3
+        graphics    [
+            x   -96.0
+            y   -67.0
+            w   40.0
+            h   40.0
+            fill    "#ff9999"
+            type    "ellipse"
+            outline "#666666"
+            outline_width   1.5
+        ]
+        label   "node2"
+    ]
+    node    [
+        root_index  -2
+        id  -2
+        graphics    [
+            x   63.0
+            y   37.0
+            w   40.0
+            h   40.0
+            fill    "#ff9999"
+            type    "ellipse"
+            outline "#666666"
+            outline_width   1.5
+        ]
+        label   "node1"
+    ]
+    node    [
+        root_index  -1
+        id  -1
+        graphics    [
+            x   -31.0
+            y   -17.0
+            w   40.0
+            h   40.0
+            fill    "#ff9999"
+            type    "ellipse"
+            outline "#666666"
+            outline_width   1.5
+        ]
+        label   "node0"
+    ]
+    edge    [
+        root_index  -2
+        target  -2
+        source  -1
+        graphics    [
+            width   1.5
+            fill    "#0000ff"
+            type    "line"
+            Line    [
+            ]
+            source_arrow    0
+            target_arrow    3
+        ]
+        label   "DirectedEdge"
+    ]
+    edge    [
+        root_index  -1
+        target  -1
+        source  -3
+        graphics    [
+            width   1.5
+            fill    "#0000ff"
+            type    "line"
+            Line    [
+            ]
+            source_arrow    0
+            target_arrow    3
+        ]
+        label   "DirectedEdge"
+    ]
+]
+"""
+        nx.parse_gml(cytoscape_example)
+
+    def test_parse_gml(self):
+        G = nx.parse_gml(self.simple_data, label="label")
+        assert sorted(G.nodes()) == ["Node 1", "Node 2", "Node 3"]
+        assert sorted(G.edges()) == [
+            ("Node 1", "Node 2"),
+            ("Node 2", "Node 3"),
+            ("Node 3", "Node 1"),
+        ]
+
+        assert sorted(G.edges(data=True)) == [
+            (
+                "Node 1",
+                "Node 2",
+                {
+                    "color": {"line": "blue", "thickness": 3},
+                    "label": "Edge from node 1 to node 2",
+                },
+            ),
+            ("Node 2", "Node 3", {"label": "Edge from node 2 to node 3"}),
+            ("Node 3", "Node 1", {"label": "Edge from node 3 to node 1"}),
+        ]
+
+    def test_read_gml(self, tmp_path):
+        fname = tmp_path / "test.gml"
+        with open(fname, "w") as fh:
+            fh.write(self.simple_data)
+        Gin = nx.read_gml(fname, label="label")
+        G = nx.parse_gml(self.simple_data, label="label")
+        assert sorted(G.nodes(data=True)) == sorted(Gin.nodes(data=True))
+        assert sorted(G.edges(data=True)) == sorted(Gin.edges(data=True))
+
+    def test_labels_are_strings(self):
+        # GML requires labels to be strings (i.e., in quotes)
+        answer = """graph [
+  node [
+    id 0
+    label "1203"
+  ]
+]"""
+        G = nx.Graph()
+        G.add_node(1203)
+        data = "\n".join(nx.generate_gml(G, stringizer=literal_stringizer))
+        assert data == answer
+
+    def test_relabel_duplicate(self):
+        data = """
+graph
+[
+        label   ""
+        directed        1
+        node
+        [
+                id      0
+                label   "same"
+        ]
+        node
+        [
+                id      1
+                label   "same"
+        ]
+]
+"""
+        fh = io.BytesIO(data.encode("UTF-8"))
+        fh.seek(0)
+        pytest.raises(nx.NetworkXError, nx.read_gml, fh, label="label")
+
+    @pytest.mark.parametrize("stringizer", (None, literal_stringizer))
+    def test_tuplelabels(self, stringizer):
+        # https://github.com/networkx/networkx/pull/1048
+        # Writing tuple labels to GML failed.
+        G = nx.Graph()
+        G.add_edge((0, 1), (1, 0))
+        data = "\n".join(nx.generate_gml(G, stringizer=stringizer))
+        answer = """graph [
+  node [
+    id 0
+    label "(0,1)"
+  ]
+  node [
+    id 1
+    label "(1,0)"
+  ]
+  edge [
+    source 0
+    target 1
+  ]
+]"""
+        assert data == answer
+
+    def test_quotes(self, tmp_path):
+        # https://github.com/networkx/networkx/issues/1061
+        # Encoding quotes as HTML entities.
+        G = nx.path_graph(1)
+        G.name = "path_graph(1)"
+        attr = 'This is "quoted" and this is a copyright: ' + chr(169)
+        G.nodes[0]["demo"] = attr
+        with open(tmp_path / "test.gml", "w+b") as fobj:
+            nx.write_gml(G, fobj)
+            fobj.seek(0)
+            # Should be bytes in 2.x and 3.x
+            data = fobj.read().strip().decode("ascii")
+        answer = """graph [
+  name "path_graph(1)"
+  node [
+    id 0
+    label "0"
+    demo "This is "quoted" and this is a copyright: ©"
+  ]
+]"""
+        assert data == answer
+
+    def test_unicode_node(self, tmp_path):
+        node = "node" + chr(169)
+        G = nx.Graph()
+        G.add_node(node)
+        with open(tmp_path / "test.gml", "w+b") as fobj:
+            nx.write_gml(G, fobj)
+            fobj.seek(0)
+            # Should be bytes in 2.x and 3.x
+            data = fobj.read().strip().decode("ascii")
+        answer = """graph [
+  node [
+    id 0
+    label "node©"
+  ]
+]"""
+        assert data == answer
+
+    def test_float_label(self, tmp_path):
+        node = 1.0
+        G = nx.Graph()
+        G.add_node(node)
+        with open(tmp_path / "test.gml", "w+b") as fobj:
+            nx.write_gml(G, fobj)
+            fobj.seek(0)
+            # Should be bytes in 2.x and 3.x
+            data = fobj.read().strip().decode("ascii")
+        answer = """graph [
+  node [
+    id 0
+    label "1.0"
+  ]
+]"""
+        assert data == answer
+
+    def test_special_float_label(self, tmp_path):
+        special_floats = [float("nan"), float("+inf"), float("-inf")]
+        try:
+            import numpy as np
+
+            special_floats += [np.nan, np.inf, np.inf * -1]
+        except ImportError:
+            special_floats += special_floats
+
+        G = nx.cycle_graph(len(special_floats))
+        attrs = dict(enumerate(special_floats))
+        nx.set_node_attributes(G, attrs, "nodefloat")
+        edges = list(G.edges)
+        attrs = {edges[i]: value for i, value in enumerate(special_floats)}
+        nx.set_edge_attributes(G, attrs, "edgefloat")
+
+        with open(tmp_path / "test.gml", "w+b") as fobj:
+            nx.write_gml(G, fobj)
+            fobj.seek(0)
+            # Should be bytes in 2.x and 3.x
+            data = fobj.read().strip().decode("ascii")
+            answer = """graph [
+  node [
+    id 0
+    label "0"
+    nodefloat NAN
+  ]
+  node [
+    id 1
+    label "1"
+    nodefloat +INF
+  ]
+  node [
+    id 2
+    label "2"
+    nodefloat -INF
+  ]
+  node [
+    id 3
+    label "3"
+    nodefloat NAN
+  ]
+  node [
+    id 4
+    label "4"
+    nodefloat +INF
+  ]
+  node [
+    id 5
+    label "5"
+    nodefloat -INF
+  ]
+  edge [
+    source 0
+    target 1
+    edgefloat NAN
+  ]
+  edge [
+    source 0
+    target 5
+    edgefloat +INF
+  ]
+  edge [
+    source 1
+    target 2
+    edgefloat -INF
+  ]
+  edge [
+    source 2
+    target 3
+    edgefloat NAN
+  ]
+  edge [
+    source 3
+    target 4
+    edgefloat +INF
+  ]
+  edge [
+    source 4
+    target 5
+    edgefloat -INF
+  ]
+]"""
+            assert data == answer
+
+            fobj.seek(0)
+            graph = nx.read_gml(fobj)
+            for indx, value in enumerate(special_floats):
+                node_value = graph.nodes[str(indx)]["nodefloat"]
+                if math.isnan(value):
+                    assert math.isnan(node_value)
+                else:
+                    assert node_value == value
+
+                edge = edges[indx]
+                string_edge = (str(edge[0]), str(edge[1]))
+                edge_value = graph.edges[string_edge]["edgefloat"]
+                if math.isnan(value):
+                    assert math.isnan(edge_value)
+                else:
+                    assert edge_value == value
+
+    def test_name(self):
+        G = nx.parse_gml('graph [ name "x" node [ id 0 label "x" ] ]')
+        assert "x" == G.graph["name"]
+        G = nx.parse_gml('graph [ node [ id 0 label "x" ] ]')
+        assert "" == G.name
+        assert "name" not in G.graph
+
+    def test_graph_types(self):
+        for directed in [None, False, True]:
+            for multigraph in [None, False, True]:
+                gml = "graph ["
+                if directed is not None:
+                    gml += " directed " + str(int(directed))
+                if multigraph is not None:
+                    gml += " multigraph " + str(int(multigraph))
+                gml += ' node [ id 0 label "0" ]'
+                gml += " edge [ source 0 target 0 ]"
+                gml += " ]"
+                G = nx.parse_gml(gml)
+                assert bool(directed) == G.is_directed()
+                assert bool(multigraph) == G.is_multigraph()
+                gml = "graph [\n"
+                if directed is True:
+                    gml += "  directed 1\n"
+                if multigraph is True:
+                    gml += "  multigraph 1\n"
+                gml += """  node [
+    id 0
+    label "0"
+  ]
+  edge [
+    source 0
+    target 0
+"""
+                if multigraph:
+                    gml += "    key 0\n"
+                gml += "  ]\n]"
+                assert gml == "\n".join(nx.generate_gml(G))
+
+    def test_data_types(self):
+        data = [
+            True,
+            False,
+            10**20,
+            -2e33,
+            "'",
+            '"&&&""',
+            [{(b"\xfd",): "\x7f", chr(0x4444): (1, 2)}, (2, "3")],
+        ]
+        data.append(chr(0x14444))
+        data.append(literal_eval("{2.3j, 1 - 2.3j, ()}"))
+        G = nx.Graph()
+        G.name = data
+        G.graph["data"] = data
+        G.add_node(0, int=-1, data={"data": data})
+        G.add_edge(0, 0, float=-2.5, data=data)
+        gml = "\n".join(nx.generate_gml(G, stringizer=literal_stringizer))
+        G = nx.parse_gml(gml, destringizer=literal_destringizer)
+        assert data == G.name
+        assert {"name": data, "data": data} == G.graph
+        assert list(G.nodes(data=True)) == [(0, {"int": -1, "data": {"data": data}})]
+        assert list(G.edges(data=True)) == [(0, 0, {"float": -2.5, "data": data})]
+        G = nx.Graph()
+        G.graph["data"] = "frozenset([1, 2, 3])"
+        G = nx.parse_gml(nx.generate_gml(G), destringizer=literal_eval)
+        assert G.graph["data"] == "frozenset([1, 2, 3])"
+
+    def test_escape_unescape(self):
+        gml = """graph [
+  name "&"䑄��&unknown;"
+]"""
+        G = nx.parse_gml(gml)
+        assert (
+            '&"\x0f' + chr(0x4444) + "��&unknown;"
+            == G.name
+        )
+        gml = "\n".join(nx.generate_gml(G))
+        alnu = "#1234567890;&#x1234567890abcdef"
+        answer = (
+            """graph [
+  name "&"䑄&"""
+            + alnu
+            + """;&unknown;"
+]"""
+        )
+        assert answer == gml
+
+    def test_exceptions(self, tmp_path):
+        pytest.raises(ValueError, literal_destringizer, "(")
+        pytest.raises(ValueError, literal_destringizer, "frozenset([1, 2, 3])")
+        pytest.raises(ValueError, literal_destringizer, literal_destringizer)
+        pytest.raises(ValueError, literal_stringizer, frozenset([1, 2, 3]))
+        pytest.raises(ValueError, literal_stringizer, literal_stringizer)
+        with open(tmp_path / "test.gml", "w+b") as f:
+            f.write(codecs.BOM_UTF8 + b"graph[]")
+            f.seek(0)
+            pytest.raises(nx.NetworkXError, nx.read_gml, f)
+
+        def assert_parse_error(gml):
+            pytest.raises(nx.NetworkXError, nx.parse_gml, gml)
+
+        assert_parse_error(["graph [\n\n", "]"])
+        assert_parse_error("")
+        assert_parse_error('Creator ""')
+        assert_parse_error("0")
+        assert_parse_error("graph ]")
+        assert_parse_error("graph [ 1 ]")
+        assert_parse_error("graph [ 1.E+2 ]")
+        assert_parse_error('graph [ "A" ]')
+        assert_parse_error("graph [ ] graph ]")
+        assert_parse_error("graph [ ] graph [ ]")
+        assert_parse_error("graph [ data [1, 2, 3] ]")
+        assert_parse_error("graph [ node [ ] ]")
+        assert_parse_error("graph [ node [ id 0 ] ]")
+        nx.parse_gml('graph [ node [ id "a" ] ]', label="id")
+        assert_parse_error("graph [ node [ id 0 label 0 ] node [ id 0 label 1 ] ]")
+        assert_parse_error("graph [ node [ id 0 label 0 ] node [ id 1 label 0 ] ]")
+        assert_parse_error("graph [ node [ id 0 label 0 ] edge [ ] ]")
+        assert_parse_error("graph [ node [ id 0 label 0 ] edge [ source 0 ] ]")
+        nx.parse_gml("graph [edge [ source 0 target 0 ] node [ id 0 label 0 ] ]")
+        assert_parse_error("graph [ node [ id 0 label 0 ] edge [ source 1 target 0 ] ]")
+        assert_parse_error("graph [ node [ id 0 label 0 ] edge [ source 0 target 1 ] ]")
+        assert_parse_error(
+            "graph [ node [ id 0 label 0 ] node [ id 1 label 1 ] "
+            "edge [ source 0 target 1 ] edge [ source 1 target 0 ] ]"
+        )
+        nx.parse_gml(
+            "graph [ node [ id 0 label 0 ] node [ id 1 label 1 ] "
+            "edge [ source 0 target 1 ] edge [ source 1 target 0 ] "
+            "directed 1 ]"
+        )
+        nx.parse_gml(
+            "graph [ node [ id 0 label 0 ] node [ id 1 label 1 ] "
+            "edge [ source 0 target 1 ] edge [ source 0 target 1 ]"
+            "multigraph 1 ]"
+        )
+        nx.parse_gml(
+            "graph [ node [ id 0 label 0 ] node [ id 1 label 1 ] "
+            "edge [ source 0 target 1 key 0 ] edge [ source 0 target 1 ]"
+            "multigraph 1 ]"
+        )
+        assert_parse_error(
+            "graph [ node [ id 0 label 0 ] node [ id 1 label 1 ] "
+            "edge [ source 0 target 1 key 0 ] edge [ source 0 target 1 key 0 ]"
+            "multigraph 1 ]"
+        )
+        nx.parse_gml(
+            "graph [ node [ id 0 label 0 ] node [ id 1 label 1 ] "
+            "edge [ source 0 target 1 key 0 ] edge [ source 1 target 0 key 0 ]"
+            "directed 1 multigraph 1 ]"
+        )
+
+        # Tests for string convertible alphanumeric id and label values
+        nx.parse_gml("graph [edge [ source a target a ] node [ id a label b ] ]")
+        nx.parse_gml(
+            "graph [ node [ id n42 label 0 ] node [ id x43 label 1 ]"
+            "edge [ source n42 target x43 key 0 ]"
+            "edge [ source x43 target n42 key 0 ]"
+            "directed 1 multigraph 1 ]"
+        )
+        assert_parse_error(
+            "graph [edge [ source '\u4200' target '\u4200' ] "
+            + "node [ id '\u4200' label b ] ]"
+        )
+
+        def assert_generate_error(*args, **kwargs):
+            pytest.raises(
+                nx.NetworkXError, lambda: list(nx.generate_gml(*args, **kwargs))
+            )
+
+        G = nx.Graph()
+        G.graph[3] = 3
+        assert_generate_error(G)
+        G = nx.Graph()
+        G.graph["3"] = 3
+        assert_generate_error(G)
+        G = nx.Graph()
+        G.graph["data"] = frozenset([1, 2, 3])
+        assert_generate_error(G, stringizer=literal_stringizer)
+
+    def test_label_kwarg(self):
+        G = nx.parse_gml(self.simple_data, label="id")
+        assert sorted(G.nodes) == [1, 2, 3]
+        labels = [G.nodes[n]["label"] for n in sorted(G.nodes)]
+        assert labels == ["Node 1", "Node 2", "Node 3"]
+
+        G = nx.parse_gml(self.simple_data, label=None)
+        assert sorted(G.nodes) == [1, 2, 3]
+        labels = [G.nodes[n]["label"] for n in sorted(G.nodes)]
+        assert labels == ["Node 1", "Node 2", "Node 3"]
+
+    def test_outofrange_integers(self, tmp_path):
+        # GML restricts integers to 32 signed bits.
+        # Check that we honor this restriction on export
+        G = nx.Graph()
+        # Test export for numbers that barely fit or don't fit into 32 bits,
+        # and 3 numbers in the middle
+        numbers = {
+            "toosmall": (-(2**31)) - 1,
+            "small": -(2**31),
+            "med1": -4,
+            "med2": 0,
+            "med3": 17,
+            "big": (2**31) - 1,
+            "toobig": 2**31,
+        }
+        G.add_node("Node", **numbers)
+
+        fname = tmp_path / "test.gml"
+        nx.write_gml(G, fname)
+        # Check that the export wrote the nonfitting numbers as strings
+        G2 = nx.read_gml(fname)
+        for attr, value in G2.nodes["Node"].items():
+            if attr == "toosmall" or attr == "toobig":
+                assert type(value) == str
+            else:
+                assert type(value) == int
+
+    def test_multiline(self):
+        # example from issue #6836
+        multiline_example = """
+graph
+[
+    node
+    [
+	    id 0
+	    label "multiline node"
+	    label2 "multiline1
+    multiline2
+    multiline3"
+	    alt_name "id 0"
+    ]
+]
+"""
+        G = nx.parse_gml(multiline_example)
+        assert G.nodes["multiline node"] == {
+            "label2": "multiline1 multiline2 multiline3",
+            "alt_name": "id 0",
+        }
+
+
+@contextmanager
+def byte_file():
+    _file_handle = io.BytesIO()
+    yield _file_handle
+    _file_handle.seek(0)
+
+
+class TestPropertyLists:
+    def test_writing_graph_with_multi_element_property_list(self):
+        g = nx.Graph()
+        g.add_node("n1", properties=["element", 0, 1, 2.5, True, False])
+        with byte_file() as f:
+            nx.write_gml(g, f)
+        result = f.read().decode()
+
+        assert result == dedent(
+            """\
+            graph [
+              node [
+                id 0
+                label "n1"
+                properties "element"
+                properties 0
+                properties 1
+                properties 2.5
+                properties 1
+                properties 0
+              ]
+            ]
+        """
+        )
+
+    def test_writing_graph_with_one_element_property_list(self):
+        g = nx.Graph()
+        g.add_node("n1", properties=["element"])
+        with byte_file() as f:
+            nx.write_gml(g, f)
+        result = f.read().decode()
+
+        assert result == dedent(
+            """\
+            graph [
+              node [
+                id 0
+                label "n1"
+                properties "_networkx_list_start"
+                properties "element"
+              ]
+            ]
+        """
+        )
+
+    def test_reading_graph_with_list_property(self):
+        with byte_file() as f:
+            f.write(
+                dedent(
+                    """
+              graph [
+                node [
+                  id 0
+                  label "n1"
+                  properties "element"
+                  properties 0
+                  properties 1
+                  properties 2.5
+                ]
+              ]
+            """
+                ).encode("ascii")
+            )
+            f.seek(0)
+            graph = nx.read_gml(f)
+        assert graph.nodes(data=True)["n1"] == {"properties": ["element", 0, 1, 2.5]}
+
+    def test_reading_graph_with_single_element_list_property(self):
+        with byte_file() as f:
+            f.write(
+                dedent(
+                    """
+              graph [
+                node [
+                  id 0
+                  label "n1"
+                  properties "_networkx_list_start"
+                  properties "element"
+                ]
+              ]
+            """
+                ).encode("ascii")
+            )
+            f.seek(0)
+            graph = nx.read_gml(f)
+        assert graph.nodes(data=True)["n1"] == {"properties": ["element"]}
+
+
+@pytest.mark.parametrize("coll", ([], ()))
+def test_stringize_empty_list_tuple(coll):
+    G = nx.path_graph(2)
+    G.nodes[0]["test"] = coll  # test serializing an empty collection
+    f = io.BytesIO()
+    nx.write_gml(G, f)  # Smoke test - should not raise
+    f.seek(0)
+    H = nx.read_gml(f)
+    assert H.nodes["0"]["test"] == coll  # Check empty list round-trips properly
+    # Check full round-tripping. Note that nodes are loaded as strings by
+    # default, so there needs to be some remapping prior to comparison
+    H = nx.relabel_nodes(H, {"0": 0, "1": 1})
+    assert nx.utils.graphs_equal(G, H)
+    # Same as above, but use destringizer for node remapping. Should have no
+    # effect on node attr
+    f.seek(0)
+    H = nx.read_gml(f, destringizer=int)
+    assert nx.utils.graphs_equal(G, H)