612 lines
18 KiB
Python
612 lines
18 KiB
Python
from ast import literal_eval
|
|
import codecs
|
|
from contextlib import contextmanager
|
|
import io
|
|
import pytest
|
|
import networkx as nx
|
|
from networkx.readwrite.gml import literal_stringizer, literal_destringizer
|
|
import os
|
|
import tempfile
|
|
from textwrap import dedent
|
|
|
|
|
|
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 [e for e in sorted(G.edges())] == [
|
|
("Node 1", "Node 2"),
|
|
("Node 2", "Node 3"),
|
|
("Node 3", "Node 1"),
|
|
]
|
|
|
|
assert [e for e in 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.OrderedGraph()
|
|
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_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")],
|
|
]
|
|
try: # fails under IronPython
|
|
data.append(chr(0x14444))
|
|
except ValueError:
|
|
data.append(chr(0x1444))
|
|
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=dict(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, dict(int=-1, data=dict(data=data)))]
|
|
assert list(G.edges(data=True)) == [(0, 0, dict(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 convertable 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)
|
|
G = nx.Graph()
|
|
G.graph["data"] = []
|
|
assert_generate_error(G)
|
|
assert_generate_error(G, stringizer=len)
|
|
|
|
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"]}
|