diff --git a/pydot_ng/__init__.py b/pydot_ng/__init__.py index dd8855b..ae4c5b4 100644 --- a/pydot_ng/__init__.py +++ b/pydot_ng/__init__.py @@ -1,25 +1,17 @@ # -*- coding: Latin-1 -*- -"""Graphviz's dot language Python interface. - -This module provides with a full interface to create handle modify -and process graphs in Graphviz's dot language. - -References: - -pydot Homepage: http://code.google.com/p/pydot/ -Graphviz: http://www.graphviz.org/ -DOT Language: http://www.graphviz.org/doc/info/lang.html - -Copyright (c) 2005-2011 Ero Carrera - -Distributed under MIT license [http://opensource.org/licenses/mit-license.html]. -""" +# Graphviz's dot language Python interface. +# This module provides with a full interface to create handle modify +# and process graphs in Graphviz's dot language. +# References: +# pydot Homepage: http://code.google.com/p/pydot/ +# Graphviz: http://www.graphviz.org/ +# DOT Language: http://www.graphviz.org/doc/info/lang.html +# Copyright (c) 2005-2011 Ero Carrera +# Distributed under MIT license +# [http://opensource.org/licenses/mit-license.html]. from __future__ import division, print_function -__author__ = 'Ero Carrera' -__license__ = 'MIT' - import os import re import subprocess @@ -32,8 +24,11 @@ from operator import itemgetter try: from . import _dotparser as dot_parser except Exception: - print("Couldn't import _dotparser, loading of dot files will not be possible.") + print("Couldn't import _dotparser, " + "loading of dot files will not be possible.") +__author__ = 'Ero Carrera' +__license__ = 'MIT' PY3 = not sys.version_info < (3, 0, 0) @@ -103,23 +98,25 @@ CLUSTER_ATTRIBUTES = set([ ]) -def is_string_like(obj): # from John Hunter, types-free version - """Check if obj is string.""" +def is_string_like(obj): + """Check if obj is string. + from John Hunter, types-free version""" try: obj + '' except (TypeError, ValueError): return False return True + def get_fobj(fname, mode='w+'): """Obtain a proper file object. Parameters ---------- fname : string, file object, file descriptor - If a string or file descriptor, then we create a file object. If *fname* - is a file object, then we do nothing and ignore the specified *mode* - parameter. + If a string or file descriptor, then we create a file object. If + *fname* is a file object, then we do nothing and ignore the specified + *mode* parameter. mode : str The mode of the file to be opened. @@ -129,9 +126,10 @@ def get_fobj(fname, mode='w+'): The file object. close : bool If *fname* was a string, then *close* will be *True* to signify that - the file object should be closed after writing to it. Otherwise, *close* - will be *False* signifying that the user, in essence, created the file - object already and that subsequent operations should not close it. + the file object should be closed after writing to it. Otherwise, + *close* will be *False* signifying that the user, in essence, + created the file object already and that subsequent operations + should not close it. """ if is_string_like(fname): @@ -431,7 +429,8 @@ def __find_executables(path): """ success = False - progs = {'dot': '', 'twopi': '', 'neato': '', 'circo': '', 'fdp': '', 'sfdp': ''} + progs = {'dot': '', 'twopi': '', 'neato': '', 'circo': '', 'fdp': '', + 'sfdp': ''} was_quoted = False path = path.strip() @@ -515,7 +514,8 @@ def find_graphviz(): def RegOpenKeyEx(key, subkey, opt, sam): result = ctypes.c_uint(0) - ctypes.windll.advapi32.RegOpenKeyExA(key, subkey, opt, sam, ctypes.byref(result)) + ctypes.windll.advapi32.RegOpenKeyExA(key, subkey, opt, sam, + ctypes.byref(result)) return result.value def RegQueryValueEx(hkey, valuename): @@ -556,8 +556,9 @@ def find_graphviz(): path = RegQueryValueEx(hkey, "InstallPath") RegCloseKey(hkey) - # The regitry variable might exist, left by old installations - # but with no value, in those cases we keep searching... + # The regitry variable might exist, left by + # old installations but with no value, in those cases + # we keep searching... if not path: continue @@ -565,11 +566,9 @@ def find_graphviz(): path = os.path.join(path, "bin") progs = __find_executables(path) if progs is not None: - #print("Used Windows registry") return progs except Exception: - #raise pass else: break @@ -579,7 +578,6 @@ def find_graphviz(): for path in os.environ['PATH'].split(os.pathsep): progs = __find_executables(path) if progs is not None: - #print("Used path") return progs # Method 3 (Windows only) @@ -590,16 +588,15 @@ def find_graphviz(): if 'PROGRAMFILES' in os.environ: # Note, we could also use the win32api to get this # information, but win32api may not be installed. - path = os.path.join(os.environ['PROGRAMFILES'], 'ATT', 'GraphViz', 'bin') + path = os.path.join(os.environ['PROGRAMFILES'], 'ATT', + 'GraphViz', 'bin') else: - #Just in case, try the default... + # Just in case, try the default... path = r"C:\Program Files\att\Graphviz\bin" progs = __find_executables(path) if progs is not None: - - #print("Used default install location") return progs for path in ( @@ -611,7 +608,6 @@ def find_graphviz(): progs = __find_executables(path) if progs is not None: - #print("Used path") return progs # Failed to find GraphViz @@ -626,23 +622,18 @@ class Common(object): """ def __getstate__(self): - dict = copy.copy(self.obj_dict) - return dict def __setstate__(self, state): - self.obj_dict = state def __get_attribute__(self, attr): """Look for default attributes for this node""" attr_val = self.obj_dict['attributes'].get(attr, None) - if attr_val is None: # get the defaults for nodes/edges - default_node_name = self.obj_dict['type'] # The defaults for graphs are set on a node named 'graph' @@ -676,11 +667,9 @@ class Common(object): return None def set_parent_graph(self, parent_graph): - self.obj_dict['parent_graph'] = parent_graph def get_parent_graph(self): - return self.obj_dict.get('parent_graph', None) def set(self, name, value): @@ -693,7 +682,6 @@ class Common(object): which are defined for all the existing attributes. """ - self.obj_dict['attributes'][name] = value def get(self, name): @@ -706,37 +694,28 @@ class Common(object): which are defined for all the existing attributes. """ - return self.obj_dict['attributes'].get(name, None) def get_attributes(self): - """""" - return self.obj_dict['attributes'] def set_sequence(self, seq): - self.obj_dict['sequence'] = seq def get_sequence(self): - return self.obj_dict['sequence'] def create_attribute_methods(self, obj_attributes): - - #for attr in self.obj_dict['attributes']: for attr in obj_attributes: - # Generate all the Setter methods. - # self.__setattr__( 'set_' + attr, lambda x, a=attr: self.obj_dict['attributes'].__setitem__(a, x) ) # Generate all the Getter methods. - # - self.__setattr__('get_' + attr, lambda a=attr: self.__get_attribute__(a)) + self.__setattr__('get_' + attr, + lambda a=attr: self.__get_attribute__(a)) class Error(Exception): @@ -750,7 +729,8 @@ class Error(Exception): class InvocationException(Exception): - """To indicate that a ploblem occurred while running any of the GraphViz executables. + """To indicate that a ploblem occurred while running any of the + GraphViz executables. """ def __init__(self, value): self.value = value @@ -773,11 +753,9 @@ class Node(Common): """ def __init__(self, name='', obj_dict=None, **attrs): - - # # Nodes will take attributes of all other types because the defaults - # for any GraphViz object are dealt with as if they were Node definitions - # + # for any GraphViz object are dealt with as if they were + # Node definitions if obj_dict is not None: self.obj_dict = obj_dict @@ -785,7 +763,6 @@ class Node(Common): self.obj_dict = dict() # Copy the attributes - # self.obj_dict['attributes'] = dict(attrs) self.obj_dict['type'] = 'node' self.obj_dict['parent_graph'] = None @@ -793,7 +770,6 @@ class Node(Common): self.obj_dict['sequence'] = None # Remove the compass point - # port = None if isinstance(name, basestring) and not name.startswith('"'): idx = name.find(':') @@ -810,17 +786,14 @@ class Node(Common): def set_name(self, node_name): """Set the node's name.""" - self.obj_dict['name'] = node_name def get_name(self): """Get the node's name.""" - return self.obj_dict['name'] def get_port(self): """Get the node's port.""" - return self.obj_dict['port'] def add_style(self, style): @@ -837,14 +810,13 @@ class Node(Common): def to_string(self): """Returns a string representation of the node in dot language. """ - # RMF: special case defaults for node, edge and graph properties. - # node = quote_if_necessary(self.obj_dict['name']) node_attr = list() - for attr, value in sorted(self.obj_dict['attributes'].items(), key=itemgetter(0)): + for attr, value in sorted(self.obj_dict['attributes'].items(), + key=itemgetter(0)): if value is not None: node_attr.append('%s=%s' % (attr, quote_if_necessary(value))) else: @@ -891,20 +863,15 @@ class Edge(Common): """ def __init__(self, src='', dst='', obj_dict=None, **attrs): - if isinstance(src, (list, tuple)) and dst == '': src, dst = src if obj_dict is not None: - self.obj_dict = obj_dict - else: - self.obj_dict = dict() # Copy the attributes - # self.obj_dict['attributes'] = dict(attrs) self.obj_dict['type'] = 'edge' self.obj_dict['parent_graph'] = None @@ -925,12 +892,10 @@ class Edge(Common): def get_source(self): """Get the edges source node name.""" - return self.obj_dict['points'][0] def get_destination(self): """Get the edge's destination node name.""" - return self.obj_dict['points'][1] def __hash__(self): @@ -954,7 +919,6 @@ class Edge(Common): # If the graph is undirected, the edge has neither # source nor destination. - # if ((self.get_source() == edge.get_source() and self.get_destination() == edge.get_destination()) or (edge.get_source() == self.get_destination() and @@ -965,11 +929,9 @@ class Edge(Common): if (self.get_source() == edge.get_source() and self.get_destination() == edge.get_destination()): return True - return False def parse_node_ref(self, node_str): - if not isinstance(node_str, str): return node_str @@ -985,11 +947,8 @@ class Edge(Common): if node_port_idx > 0: a = node_str[:node_port_idx] b = node_str[node_port_idx + 1:] - node = quote_if_necessary(a) - node += ':' + quote_if_necessary(b) - return node return node_str @@ -997,7 +956,6 @@ class Edge(Common): def to_string(self): """Returns a string representation of the edge in dot language. """ - src = self.parse_node_ref(self.get_source()) dst = self.parse_node_ref(self.get_destination()) @@ -1026,7 +984,8 @@ class Edge(Common): edge_attr = list() - for attr, value in sorted(self.obj_dict['attributes'].items(), key=itemgetter(0)): + for attr, value in sorted(self.obj_dict['attributes'].items(), + key=itemgetter(0)): if value is not None: edge_attr.append('%s=%s' % (attr, quote_if_necessary(value))) else: @@ -1077,8 +1036,9 @@ class Graph(Common): """ def __init__( - self, graph_name='G', obj_dict=None, graph_type='digraph', strict=False, - suppress_disconnected=False, simplify=False, **attrs): + self, graph_name='G', obj_dict=None, graph_type='digraph', + strict=False, suppress_disconnected=False, simplify=False, + **attrs): if obj_dict is not None: self.obj_dict = obj_dict @@ -1163,7 +1123,6 @@ class Graph(Common): only one edge between two nodes. removing the duplicated ones. """ - self.obj_dict['simplify'] = simplify def get_simplify(self): @@ -1171,27 +1130,22 @@ class Graph(Common): Refer to set_simplify for more information. """ - return self.obj_dict['simplify'] def set_type(self, graph_type): """Set the graph's type, 'graph' or 'digraph'.""" - self.obj_dict['type'] = graph_type def get_type(self): """Get the graph's type, 'graph' or 'digraph'.""" - return self.obj_dict['type'] def set_name(self, graph_name): """Set the graph's name.""" - self.obj_dict['name'] = graph_name def get_name(self): """Get the graph's name.""" - return self.obj_dict['name'] def set_strict(self, val): @@ -1199,7 +1153,6 @@ class Graph(Common): This option is only valid for top level graphs. """ - self.obj_dict['strict'] = val def get_strict(self, val): @@ -1207,7 +1160,6 @@ class Graph(Common): This option is only valid for top level graphs. """ - return self.obj_dict['strict'] def set_suppress_disconnected(self, val): @@ -1217,7 +1169,6 @@ class Graph(Common): edges. This option works also for subgraphs and has effect only in the current graph/subgraph. """ - self.obj_dict['suppress_disconnected'] = val def get_suppress_disconnected(self, val): @@ -1225,7 +1176,6 @@ class Graph(Common): Refer to set_suppress_disconnected for more information. """ - return self.obj_dict['suppress_disconnected'] def get_next_sequence_number(self): @@ -1239,19 +1189,21 @@ class Graph(Common): It takes a node object as its only argument and returns None. """ - if not isinstance(graph_node, Node): - raise TypeError('add_node() received a non node class object: ' + str(graph_node)) + raise TypeError(''.join([ + 'add_node() received a non node class object: ', + str(graph_node)])) node = self.get_node(graph_node.get_name()) if not node: - self.obj_dict['nodes'][graph_node.get_name()] = [graph_node.obj_dict] + self.obj_dict['nodes'][graph_node.get_name()] =\ + [graph_node.obj_dict] - #self.node_dict[graph_node.get_name()] = graph_node.attributes graph_node.set_parent_graph(self.get_parent_graph()) else: - self.obj_dict['nodes'][graph_node.get_name()].append(graph_node.obj_dict) + self.obj_dict['nodes'][graph_node.get_name()].\ + append(graph_node.obj_dict) graph_node.set_sequence(self.get_next_sequence_number()) @@ -1296,7 +1248,6 @@ class Graph(Common): Node instances is returned. An empty list is returned otherwise. """ - match = list() if name in self.obj_dict['nodes']: @@ -1310,7 +1261,6 @@ class Graph(Common): def get_nodes(self): """Get the list of Node instances.""" - return self.get_node_list() def get_node_list(self): @@ -1319,7 +1269,6 @@ class Graph(Common): This method returns the list of Node instances composing the graph. """ - node_objs = list() for node, obj_dict_list in self.obj_dict['nodes'].items(): @@ -1334,12 +1283,11 @@ class Graph(Common): def add_edge(self, graph_edge): """Adds an edge object to the graph. - It takes a edge object as its only argument and returns - None. + It takes a edge object as its only argument and returns None. """ - if not isinstance(graph_edge, Edge): - raise TypeError('add_edge() received a non edge class object: ' + str(graph_edge)) + raise TypeError(''.join(['add_edge() received a non edge class ' + 'object: ', str(graph_edge)])) edge_points = (graph_edge.get_source(), graph_edge.get_destination()) @@ -1384,7 +1332,8 @@ class Graph(Common): dst = dst.get_name() if (src, dst) in self.obj_dict['edges']: - if index is not None and index < len(self.obj_dict['edges'][(src, dst)]): + if (index is not None and index < + len(self.obj_dict['edges'][(src, dst)])): del self.obj_dict['edges'][(src, dst)][index] return True else: @@ -1424,8 +1373,8 @@ class Graph(Common): for edge_obj_dict in edges_obj_dict: match.append( - Edge(edge_points[0], edge_points[1], obj_dict=edge_obj_dict) - ) + Edge(edge_points[0], edge_points[1], + obj_dict=edge_obj_dict)) return match @@ -1438,7 +1387,6 @@ class Graph(Common): This method returns the list of Edge instances composing the graph. """ - edge_objs = list() for edge, obj_dict_list in self.obj_dict['edges'].items(): @@ -1457,8 +1405,11 @@ class Graph(Common): None. """ - if not isinstance(sgraph, Subgraph) and not isinstance(sgraph, Cluster): - raise TypeError('add_subgraph() received a non subgraph class object:' + str(sgraph)) + if (not isinstance(sgraph, Subgraph) and + not isinstance(sgraph, Cluster)): + raise TypeError(''.join([ + 'add_subgraph() received a non subgraph class object:', + str(sgraph)])) if sgraph.get_name() in self.obj_dict['subgraphs']: @@ -1489,7 +1440,6 @@ class Graph(Common): sgraphs_obj_dict = self.obj_dict['subgraphs'].get(name) for obj_dict_list in sgraphs_obj_dict: - #match.extend(Subgraph(obj_dict = obj_d) for obj_d in obj_dict_list) match.append(Subgraph(obj_dict=obj_dict_list)) return match @@ -1516,7 +1466,6 @@ class Graph(Common): return sgraph_objs def set_parent_graph(self, parent_graph): - self.obj_dict['parent_graph'] = parent_graph for obj_list in self.obj_dict['nodes'].values(): @@ -1544,14 +1493,17 @@ class Graph(Common): graph.append('strict ') if self.obj_dict['name'] == '': - if 'show_keyword' in self.obj_dict and self.obj_dict['show_keyword']: + if ('show_keyword' in self.obj_dict and + self.obj_dict['show_keyword']): graph.append('subgraph {\n') else: graph.append('{\n') else: - graph.append('%s %s {\n' % (self.obj_dict['type'], self.obj_dict['name'])) + graph.append('%s %s {\n' % (self.obj_dict['type'], + self.obj_dict['name'])) - for attr, value in sorted(self.obj_dict['attributes'].items(), key=itemgetter(0)): + for attr, value in sorted(self.obj_dict['attributes'].items(), + key=itemgetter(0)): if value is not None: graph.append('%s=%s' % (attr, quote_if_necessary(value))) else: @@ -1566,7 +1518,8 @@ class Graph(Common): edge_obj_dicts.extend(e) if edge_obj_dicts: - edge_src_set, edge_dst_set = list(zip(*[obj['points'] for obj in edge_obj_dicts])) + edge_src_set, edge_dst_set = list( + zip(*[obj['points'] for obj in edge_obj_dicts])) edge_src_set, edge_dst_set = set(edge_src_set), set(edge_dst_set) else: edge_src_set, edge_dst_set = set(), set() @@ -1620,7 +1573,8 @@ class Subgraph(Graph): This class implements the methods to work on a representation of a subgraph in Graphviz's dot language. - subgraph(graph_name='subG', suppress_disconnected=False, attribute=value, ...) + subgraph(graph_name='subG', suppress_disconnected=False, attribute=value, + ...) graph_name: the subgraph's name @@ -1641,17 +1595,17 @@ class Subgraph(Graph): subgraph_instance.obj_dict['attributes']['label'] subgraph_instance.obj_dict['attributes']['fontname'] """ - # RMF: subgraph should have all the attributes of graph so it can be passed # as a graph to all methods - # + def __init__( self, graph_name='', obj_dict=None, suppress_disconnected=False, simplify=False, **attrs): Graph.__init__( self, graph_name=graph_name, obj_dict=obj_dict, - suppress_disconnected=suppress_disconnected, simplify=simplify, **attrs) + suppress_disconnected=suppress_disconnected, simplify=simplify, + **attrs) if obj_dict is None: self.obj_dict['type'] = 'subgraph' @@ -1664,7 +1618,8 @@ class Cluster(Graph): This class implements the methods to work on a representation of a cluster in Graphviz's dot language. - cluster(graph_name='subG', suppress_disconnected=False, attribute=value, ...) + cluster(graph_name='subG', suppress_disconnected=False, attribute=value, + ...) graph_name: the cluster's name (the string 'cluster' will be always prepended) @@ -1686,14 +1641,12 @@ class Cluster(Graph): cluster_instance.obj_dict['attributes']['fontname'] """ - def __init__( - self, graph_name='subG', obj_dict=None, suppress_disconnected=False, - simplify=False, **attrs): + def __init__(self, graph_name='subG', obj_dict=None, + suppress_disconnected=False, simplify=False, **attrs): - Graph.__init__( - self, graph_name=graph_name, obj_dict=obj_dict, - suppress_disconnected=suppress_disconnected, simplify=simplify, **attrs - ) + Graph.__init__(self, graph_name=graph_name, obj_dict=obj_dict, + suppress_disconnected=suppress_disconnected, + simplify=simplify, **attrs) if obj_dict is None: self.obj_dict['type'] = 'subgraph' @@ -1740,7 +1693,8 @@ class Dot(Graph): for frmt in self.formats + ['raw']: self.__setattr__( 'write_' + frmt, - lambda path, f=frmt, prog=self.prog: self.write(path, format=f, prog=prog) + lambda path, f=frmt, prog=self.prog: self.write(path, format=f, + prog=prog) ) f = self.__dict__['write_' + frmt] @@ -1759,13 +1713,13 @@ class Dot(Graph): """Add the paths of the required image files. If the graph needs graphic objects to be used as shapes or otherwise - those need to be in the same folder as the graph is going to be rendered - from. Alternatively the absolute path to the files can be specified when - including the graphics in the graph. + those need to be in the same folder as the graph is going to be + rendered from. Alternatively the absolute path to the files can be + specified when including the graphics in the graph. - The files in the location pointed to by the path(s) specified as arguments - to this method will be copied to the same temporary location where the - graph is going to be rendered. + The files in the location pointed to by the path(s) specified as + arguments to this method will be copied to the same temporary location + where the graph is going to be rendered. """ if isinstance(file_paths, basestring): @@ -1783,14 +1737,16 @@ class Dot(Graph): self.prog = prog def set_graphviz_executables(self, paths): - """This method allows to manually specify the location of the GraphViz executables. + """This method allows to manually specify the location of the + GraphViz executables. - The argument to this method should be a dictionary where the keys are as follows: + The argument to this method should be a dictionary where the keys + are as follows: {'dot': '', 'twopi': '', 'neato': '', 'circo': '', 'fdp': ''} - and the values are the paths to the corresponding executable, including the name - of the executable itself. + and the values are the paths to the corresponding executable, + including the name of the executable itself. """ self.progs = paths @@ -1893,9 +1849,11 @@ class Dot(Graph): raise InvocationException( 'GraphViz\'s executable "%s" not found' % prog) - if not os.path.exists(self.progs[prog]) or not os.path.isfile(self.progs[prog]): + if (not os.path.exists(self.progs[prog]) or + not os.path.isfile(self.progs[prog])): raise InvocationException( - 'GraphViz\'s executable "%s" is not a file or doesn\'t exist' % self.progs[prog]) + 'GraphViz\'s executable "%s" is not a file or doesn\'t exist' + % self.progs[prog]) tmp_fd, tmp_name = tempfile.mkstemp() os.close(tmp_fd) @@ -1910,7 +1868,8 @@ class Dot(Graph): f_data = f.read() f.close() - # And copy it under a file with the same name in the temporary directory + # And copy it under a file with the same name in the + # temporary directory f = open(os.path.join(tmp_dir, os.path.basename(img)), 'wb') f.write(f_data) f.close() @@ -1949,7 +1908,6 @@ class Dot(Graph): if PY3: stderr_output = stderr_output.decode(sys.stderr.encoding) - #pid, status = os.waitpid(p.pid, 0) status = p.wait() if status != 0: @@ -1961,7 +1919,6 @@ class Dot(Graph): # For each of the image files... for img in self.shape_files: - # remove it os.unlink(os.path.join(tmp_dir, os.path.basename(img))) diff --git a/pydot_ng/_dotparser.py b/pydot_ng/_dotparser.py index df191fd..ebb2aab 100644 --- a/pydot_ng/_dotparser.py +++ b/pydot_ng/_dotparser.py @@ -1,19 +1,15 @@ -"""Graphviz's dot language parser. +# Graphviz's dot language parser. -The dotparser parses graphviz files in dot and dot files and transforms them -into a class representation defined by pydot. +# The dotparser parses graphviz files in dot and dot files and transforms them +# into a class representation defined by pydot. -The module needs pyparsing (tested with version 1.2.2) and pydot +# The module needs pyparsing (tested with version 1.2.2) and pydot -Author: Michael Krause -Fixes by: Ero Carrera -""" +# Author: Michael Krause +# Fixes by: Ero Carrera from __future__ import division, print_function -__author__ = ['Michael Krause', 'Ero Carrera'] -__license__ = 'MIT' - import sys import pydot_ng as pydot import codecs @@ -27,6 +23,9 @@ from pyparsing import ( ParseResults, CharsNotIn, QuotedString ) +__author__ = ['Michael Krause', 'Ero Carrera'] +__license__ = 'MIT' + PY3 = not sys.version_info < (3, 0, 0) @@ -134,7 +133,8 @@ def update_parent_graph_hierarchy(g, parent_graph=None, level=0): for key, objs in item_dict[key_name].items(): for obj in objs: - if 'parent_graph' in obj and obj['parent_graph'].get_parent_graph() == g: + if ('parent_graph' in obj and + obj['parent_graph'].get_parent_graph() == g): if obj['parent_graph'] is g: pass else: @@ -142,13 +142,15 @@ def update_parent_graph_hierarchy(g, parent_graph=None, level=0): if key_name == 'edges' and len(key) == 2: for idx, vertex in enumerate(obj['points']): - if isinstance(vertex, (pydot.Graph, pydot.Subgraph, pydot.Cluster)): + if isinstance(vertex, (pydot.Graph, pydot.Subgraph, + pydot.Cluster)): vertex.set_parent_graph(parent_graph) if isinstance(vertex, pydot.frozendict): if vertex['parent_graph'] is g: pass else: - vertex['parent_graph'].set_parent_graph(parent_graph) + vertex['parent_graph'].\ + set_parent_graph(parent_graph) def add_defaults(element, defaults): @@ -158,7 +160,8 @@ def add_defaults(element, defaults): d[key] = value -def add_elements(g, toks, defaults_graph=None, defaults_node=None, defaults_edge=None): +def add_elements(g, toks, defaults_graph=None, defaults_node=None, + defaults_edge=None): if defaults_graph is None: defaults_graph = {} if defaults_node is None: @@ -181,7 +184,8 @@ def add_elements(g, toks, defaults_graph=None, defaults_node=None, defaults_edge elif isinstance(element, ParseResults): for e in element: - add_elements(g, [e], defaults_graph, defaults_node, defaults_edge) + add_elements(g, [e], defaults_graph, defaults_node, + defaults_edge) elif isinstance(element, DefaultStatement): if element.default_type == 'graph': @@ -198,7 +202,8 @@ def add_elements(g, toks, defaults_graph=None, defaults_node=None, defaults_edge defaults_edge.update(element.attrs) else: - raise ValueError("Unknown DefaultStatement: %s " % element.default_type) + raise ValueError("Unknown DefaultStatement: {0} ". + format(element.default_type)) elif isinstance(element, P_AttrList): g.obj_dict['attributes'].update(element.attrs) @@ -290,7 +295,8 @@ def push_edge_stmt(str, loc, toks): e.append(pydot.Edge(n_prev, n_next[0] + n_next_port, **attrs)) elif isinstance(toks[2][0], pydot.Graph): - e.append(pydot.Edge(n_prev, pydot.frozendict(toks[2][0].obj_dict), **attrs)) + e.append(pydot.Edge(n_prev, pydot.frozendict(toks[2][0].obj_dict), + **attrs)) elif isinstance(toks[2][0], pydot.Node): node = toks[2][0] @@ -304,7 +310,8 @@ def push_edge_stmt(str, loc, toks): elif isinstance(toks[2][0], type('')): for n_next in [n for n in tuple(toks)[2::2]]: - if isinstance(n_next, P_AttrList) or not isinstance(n_next[0], type('')): + if isinstance(n_next, P_AttrList) or not isinstance(n_next[0], + type('')): continue n_next_port = do_node_ports(n_next) @@ -352,10 +359,6 @@ def graph_definition(): rparen = Literal(")") equals = Literal("=") comma = Literal(",") - # dot = Literal(".") - # slash = Literal("/") - # bslash = Literal("\\") - # star = Literal("*") semi = Literal(";") at = Literal("@") minus = Literal("-") @@ -372,7 +375,8 @@ def graph_definition(): identifier = Word(alphanums + "_.").setName("identifier") # dblQuotedString - double_quoted_string = QuotedString('"', multiline=True, unquoteResults=False) + double_quoted_string = QuotedString('"', multiline=True, + unquoteResults=False) noncomma_ = "".join([c for c in printables if c != ","]) alphastring_ = OneOrMore(CharsNotIn(noncomma_ + ' ')) @@ -389,7 +393,7 @@ def graph_definition(): ID = ( identifier | html_text | - double_quoted_string | # .setParseAction(strip_quotes) | + double_quoted_string | alphastring_ ).setName("ID") @@ -421,7 +425,8 @@ def graph_definition(): lbrack.suppress() + Optional(a_list) + rbrack.suppress() ).setName("attr_list") - attr_stmt = (Group(graph_ | node_ | edge_) + attr_list).setName("attr_stmt") + attr_stmt = (Group(graph_ | node_ | edge_) + attr_list).\ + setName("attr_stmt") edgeop = (Literal("--") | Literal("->")).setName("edgeop") @@ -436,9 +441,11 @@ def graph_definition(): edgeRHS = OneOrMore(edgeop + edge_point) edge_stmt = edge_point + edgeRHS + Optional(attr_list) - subgraph = Group(subgraph_ + Optional(ID) + graph_stmt).setName("subgraph") + subgraph = Group(subgraph_ + Optional(ID) + graph_stmt).\ + setName("subgraph") - edge_point << Group(subgraph | graph_stmt | node_id).setName('edge_point') + edge_point << Group(subgraph | graph_stmt | node_id).\ + setName('edge_point') node_stmt = ( node_id + Optional(attr_list) + Optional(semi.suppress()) diff --git a/test/test_pydot.py b/test/test_pydot.py index d8ff895..e006216 100644 --- a/test/test_pydot.py +++ b/test/test_pydot.py @@ -39,25 +39,18 @@ MY_REGRESSION_TESTS_DIR = os.path.join(TEST_DIR, 'my_tests') class TestGraphAPI(unittest.TestCase): def setUp(self): - self._reset_graphs() def _reset_graphs(self): - self.graph_directed = pydot.Graph('testgraph', graph_type='digraph') def test_keep_graph_type(self): - g = pydot.Dot(graph_name='Test', graph_type='graph') - self.assertEqual(g.get_type(), 'graph') - g = pydot.Dot(graph_name='Test', graph_type='digraph') - self.assertEqual(g.get_type(), 'digraph') def test_add_style(self): - node = pydot.Node('mynode') node.add_style('abc') self.assertEqual(node.get_style(), 'abc') @@ -67,7 +60,6 @@ class TestGraphAPI(unittest.TestCase): self.assertEqual(node.get_style(), 'abc,def,ghi') def test_create_simple_graph_with_node(self): - g = pydot.Dot() g.set_type('digraph') node = pydot.Node('legend') @@ -75,10 +67,10 @@ class TestGraphAPI(unittest.TestCase): g.add_node(node) node.set('label', 'mine') - self.assertEqual(g.to_string(), 'digraph G {\nlegend [label=mine, shape=box];\n}\n') + self.assertEqual(g.to_string(), + 'digraph G {\nlegend [label=mine, shape=box];\n}\n') def test_attribute_with_implicit_value(self): - d = 'digraph {\na -> b[label="hi", decorate];\n}' g = pydot.graph_from_dot_data(d) attrs = g.get_edges()[0].get_attributes() @@ -86,7 +78,6 @@ class TestGraphAPI(unittest.TestCase): self.assertEqual('decorate' in attrs, True) def test_subgraphs(self): - g = pydot.Graph() s = pydot.Subgraph("foo") @@ -99,7 +90,6 @@ class TestGraphAPI(unittest.TestCase): self.assertEqual(g.get_subgraph_list()[0].get_name(), s.get_name()) def test_graph_pickling(self): - import pickle g = pydot.Graph() @@ -113,7 +103,6 @@ class TestGraphAPI(unittest.TestCase): self.assertEqual(type(pickle.dumps(g)), bytes) def test_unicode_ids(self): - node1 = '"aánñoöüé€"' node2 = '"îôø®çßΩ"' @@ -139,7 +128,6 @@ class TestGraphAPI(unittest.TestCase): @unittest.skip("failing checksum") def test_graph_with_shapefiles(self): - shapefile_dir = os.path.join(TEST_DIR, 'from-past-to-future') dot_file = os.path.join(shapefile_dir, 'from-past-to-future.dot') @@ -154,29 +142,19 @@ class TestGraphAPI(unittest.TestCase): f.close() g = pydot.graph_from_dot_data(graph_data) - g.set_shape_files(pngs) - jpe_data = g.create(format='jpe') - hexdigest = sha256(jpe_data).hexdigest() - hexdigest_original = self._render_with_graphviz(dot_file) - self.assertEqual(hexdigest, hexdigest_original) def test_multiple_graphs(self): - graph_data = 'graph A { a->b };\ngraph B {c->d}' - graphs = pydot.graph_from_dot_data(graph_data) - self.assertEqual(len(graphs), 2) - self.assertEqual([g.get_name() for g in graphs], ['A', 'B']) def _render_with_graphviz(self, filename): - p = subprocess.Popen( (DOT_BINARY_PATH, '-Tjpe'), cwd=os.path.dirname(filename), @@ -197,25 +175,16 @@ class TestGraphAPI(unittest.TestCase): if stdout_output: stdout_output = NULL_SEP.join(stdout_output) - #pid, status = os.waitpid(p.pid, 0) # this returns a status code we should check p.wait() return sha256(stdout_output).hexdigest() def _render_with_pydot(self, filename): - #f = open(filename, 'rt') - #graph_data = f.read() - #f.close() - - #g = pydot.parse_from_dot_data(graph_data) g = pydot.graph_from_dot_file(filename) - if not isinstance(g, list): g = [g] - jpe_data = NULL_SEP.join([_g.create(format='jpe') for _g in g]) - return sha256(jpe_data).hexdigest() def test_my_regression_tests(self): @@ -230,10 +199,9 @@ class TestGraphAPI(unittest.TestCase): dot_files = [ fname for fname in os.listdir(directory) if fname.endswith('.dot') - ] # and fname.startswith('')] + ] for dot in dot_files: - #print 'Processing: %s' % dot os.sys.stdout.write('#') os.sys.stdout.flush() @@ -244,7 +212,6 @@ class TestGraphAPI(unittest.TestCase): original_data_hexdigest = self._render_with_graphviz(fname) except Exception: print('Failed rendering BAD(%s)' % dot) - #print 'Error:', str(excp) raise if parsed_data_hexdigest != original_data_hexdigest: @@ -253,92 +220,67 @@ class TestGraphAPI(unittest.TestCase): self.assertEqual(parsed_data_hexdigest, original_data_hexdigest) def test_numeric_node_id(self): - self._reset_graphs() - self.graph_directed.add_node(pydot.Node(1)) - self.assertEqual(self.graph_directed.get_nodes()[0].get_name(), '1') def test_quoted_node_id(self): - self._reset_graphs() - self.graph_directed.add_node(pydot.Node('"node"')) - - self.assertEqual(self.graph_directed.get_nodes()[0].get_name(), '"node"') + self.assertEqual(self.graph_directed.get_nodes()[0].get_name(), + '"node"') def test_quoted_node_id_to_string_no_attributes(self): - self._reset_graphs() - self.graph_directed.add_node(pydot.Node('"node"')) - - self.assertEqual(self.graph_directed.get_nodes()[0].to_string(), '"node";') + self.assertEqual(self.graph_directed.get_nodes()[0].to_string(), + '"node";') def test_keyword_node_id(self): - self._reset_graphs() - self.graph_directed.add_node(pydot.Node('node')) - - self.assertEqual(self.graph_directed.get_nodes()[0].get_name(), 'node') + self.assertEqual(self.graph_directed.get_nodes()[0].get_name(), + 'node') def test_keyword_node_id_to_string_no_attributes(self): - self._reset_graphs() - self.graph_directed.add_node(pydot.Node('node')) - self.assertEqual(self.graph_directed.get_nodes()[0].to_string(), '') def test_keyword_node_id_to_string_with_attributes(self): - self._reset_graphs() - self.graph_directed.add_node(pydot.Node('node', shape='box')) - - self.assertEqual(self.graph_directed.get_nodes()[0].to_string(), 'node [shape=box];') + self.assertEqual(self.graph_directed.get_nodes()[0].to_string(), + 'node [shape=box];') def test_names_of_a_thousand_nodes(self): - self._reset_graphs() - names = set(['node_%05d' % i for i in xrange(10 ** 4)]) for name in names: - self.graph_directed.add_node(pydot.Node(name, label=name)) - self.assertEqual(set([n.get_name() for n in self.graph_directed.get_nodes()]), names) + self.assertEqual(set([n.get_name() for n in + self.graph_directed.get_nodes()]), names) def test_executable_not_found_exception(self): paths = {'dot': 'invalid_executable_path'} - graph = pydot.Dot('graphname', graph_type='digraph') - graph.set_graphviz_executables(paths) - self.assertRaises(pydot.InvocationException, graph.create) def test_graph_add_node_argument_type(self): - self._reset_graphs() - self.assertRaises(TypeError, self.graph_directed.add_node, 1) self.assertRaises(TypeError, self.graph_directed.add_node, 'a') def test_graph_add_edge_argument_type(self): - self._reset_graphs() - self.assertRaises(TypeError, self.graph_directed.add_edge, 1) self.assertRaises(TypeError, self.graph_directed.add_edge, 'a') def test_graph_add_subgraph_argument_type(self): - self._reset_graphs() - self.assertRaises(TypeError, self.graph_directed.add_subgraph, 1) self.assertRaises(TypeError, self.graph_directed.add_subgraph, 'a') @@ -346,7 +288,6 @@ class TestGraphAPI(unittest.TestCase): import string g = pydot.Dot() g.add_node(pydot.Node("test", label=string.printable)) - #print g.to_string() data = g.create(format='jpe') self.assertEqual(len(data) > 0, True)