#!/usr/bin/env python # encoding: utf-8 from ast import literal_eval import codecs import io import pytest import networkx as nx from networkx.readwrite.gml import literal_stringizer, literal_destringizer import os import tempfile try: unicode except NameError: unicode = str try: unichr except NameError: unichr = chr class TestGraph(object): @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: ' + unichr(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' + unichr(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', unichr(0x4444): (1, 2)}, (2, "3")]] try: # fails under IronPython data.append(unichr(0x14444)) except ValueError: data.append(unichr(0x1444)) try: # fails under Python 2.7 data.append(literal_eval('{2.3j, 1 - 2.3j, ()}')) except ValueError: data.append([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, unicode('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' + unichr(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 + 'graph[]'.encode('ascii')) 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', unicode(']')]) 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)