123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730 |
- import codecs
- import io
- import math
- import os
- import tempfile
- 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):
- (fd, fname) = tempfile.mkstemp()
- fh = open(fname, "w")
- fh.write(self.simple_data)
- fh.close()
- 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))
- os.close(fd)
- os.unlink(fname)
- 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")
- def test_tuplelabels(self):
- # 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=literal_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):
- # 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
- fobj = tempfile.NamedTemporaryFile()
- 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):
- node = "node" + chr(169)
- G = nx.Graph()
- G.add_node(node)
- fobj = tempfile.NamedTemporaryFile()
- 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):
- node = 1.0
- G = nx.Graph()
- G.add_node(node)
- fobj = tempfile.NamedTemporaryFile()
- 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):
- 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")
- fobj = tempfile.NamedTemporaryFile()
- 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):
- 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 tempfile.TemporaryFile() 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 u'u\4200' target u'u\4200' ] "
- + "node [ id u'u\4200' 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):
- # 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)
- fd, fname = tempfile.mkstemp()
- try:
- 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
- finally:
- os.close(fd)
- os.unlink(fname)
- @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)
|