Spaces:
Running
Running
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: | |
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, 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): | |
# 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) | |
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", | |
} | |
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"]} | |
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) | |