diff --git a/README.md b/README.md index 8dbcb06..e1ff666 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![Build Status](https://www.travis-ci.com/pydot/pydot.svg?branch=master)](https://www.travis-ci.com/pydot/pydot) [![PyPI](https://img.shields.io/pypi/v/pydot.svg)](https://pypi.org/project/pydot/) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) About diff --git a/dot_parser.py b/dot_parser.py index e018f9f..2240e96 100644 --- a/dot_parser.py +++ b/dot_parser.py @@ -12,14 +12,25 @@ import sys from pyparsing import ( - nestedExpr, Literal, CaselessLiteral, - Word, OneOrMore, + nestedExpr, + Literal, + CaselessLiteral, + Word, + OneOrMore, Forward, - Group, Optional, Combine, - restOfLine, cStyleComment, nums, alphanums, + Group, + Optional, + Combine, + restOfLine, + cStyleComment, + nums, + alphanums, printables, - ParseException, ParseResults, CharsNotIn, - QuotedString) + ParseException, + ParseResults, + CharsNotIn, + QuotedString, +) import pydot @@ -35,15 +46,14 @@ class P_AttrList(object): - def __init__(self, toks): self.attrs = {} i = 0 while i < len(toks): attrname = toks[i] - if i+2 < len(toks) and toks[i+1] == '=': - attrvalue = toks[i+2] + if i + 2 < len(toks) and toks[i + 1] == '=': + attrvalue = toks[i + 2] i += 3 else: attrvalue = None @@ -56,14 +66,16 @@ def __repr__(self): class DefaultStatement(P_AttrList): - def __init__(self, default_type, attrs): self.default_type = default_type self.attrs = attrs def __repr__(self): - return "%s(%s, %r)" % (self.__class__.__name__, - self.default_type, self.attrs) + return "%s(%s, %r)" % ( + self.__class__.__name__, + self.default_type, + self.attrs, + ) top_graphs = list() @@ -75,9 +87,11 @@ def push_top_graph_stmt(str, loc, toks): for element in toks: - if (isinstance(element, (ParseResults, tuple, list)) and - len(element) == 1 and - isinstance(element[0], str_type)): + if ( + isinstance(element, (ParseResults, tuple, list)) + and len(element) == 1 + and isinstance(element[0], str_type) + ): element = element[0] @@ -113,7 +127,8 @@ def push_top_graph_stmt(str, loc, toks): else: raise ValueError( - 'Unknown element statement: {s}'.format(s=element)) + 'Unknown element statement: {s}'.format(s=element) + ) for g in top_graphs: update_parent_graph_hierarchy(g) @@ -140,8 +155,10 @@ 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: @@ -149,16 +166,18 @@ 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) + parent_graph + ) def add_defaults(element, defaults): @@ -168,8 +187,9 @@ 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: @@ -197,8 +217,9 @@ def add_elements(g, toks, defaults_graph=None, 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): @@ -221,7 +242,9 @@ def add_elements(g, toks, defaults_graph=None, else: raise ValueError( 'Unknown DefaultStatement: {s}'.format( - s=element.default_type)) + s=element.default_type + ) + ) elif isinstance(element, P_AttrList): @@ -229,7 +252,8 @@ def add_elements(g, toks, defaults_graph=None, else: raise ValueError( - 'Unknown element statement: {s}'.format(s=element)) + 'Unknown element statement: {s}'.format(s=element) + ) def push_graph_stmt(str, loc, toks): @@ -268,8 +292,7 @@ def push_default_stmt(str, loc, toks): if default_type in ['graph', 'node', 'edge']: return DefaultStatement(default_type, attrs) else: - raise ValueError( - 'Unknown default statement: {s}'.format(s=toks)) + raise ValueError('Unknown default statement: {s}'.format(s=toks)) def push_attr_list(str, loc, toks): @@ -290,7 +313,7 @@ def get_port(node): def do_node_ports(node): node_port = '' if len(node) > 1: - node_port = ''.join([str(a)+str(b) for a, b in node[1]]) + node_port = ''.join([str(a) + str(b) for a, b in node[1]]) return node_port @@ -314,13 +337,13 @@ def push_edge_stmt(str, loc, toks): n_next_list = [[n.get_name()] for n in toks[2][0]] for n_next in [n for n in n_next_list]: n_next_port = do_node_ports(n_next) - e.append(pydot.Edge(n_prev, n_next[0]+n_next_port, **attrs)) + 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): @@ -338,18 +361,21 @@ def push_edge_stmt(str, loc, toks): for n_next in [n for n in tuple(toks)[2::2]]: - if (isinstance(n_next, P_AttrList) or - not isinstance(n_next[0], str_type)): + if isinstance(n_next, P_AttrList) or not isinstance( + n_next[0], str_type + ): continue n_next_port = do_node_ports(n_next) - e.append(pydot.Edge(n_prev, n_next[0]+n_next_port, **attrs)) + e.append(pydot.Edge(n_prev, n_next[0] + n_next_port, **attrs)) - n_prev = n_next[0]+n_next_port + n_prev = n_next[0] + n_next_port else: raise Exception( 'Edge target {r} with type {s} unsupported.'.format( - r=toks[2][0], s=type(toks[2][0]))) + r=toks[2][0], s=type(toks[2][0]) + ) + ) return e @@ -417,16 +443,18 @@ def parse_html(s, loc, toks): opener = '<' closer = '>' - html_text = nestedExpr( - opener, closer, (CharsNotIn(opener + closer)) - ).setParseAction(parse_html).leaveWhitespace() + html_text = ( + nestedExpr(opener, closer, (CharsNotIn(opener + closer))) + .setParseAction(parse_html) + .leaveWhitespace() + ) - ID = (identifier | html_text | double_quoted_string | - alphastring_).setName("ID") + ID = ( + identifier | html_text | double_quoted_string | alphastring_ + ).setName("ID") float_number = Combine( - Optional(minus) + - OneOrMore(Word(nums + ".")) + Optional(minus) + OneOrMore(Word(nums + ".")) ).setName("float_number") righthand_id = (float_number | ID).setName("righthand_id") @@ -434,39 +462,36 @@ def parse_html(s, loc, toks): port_angle = (at + ID).setName("port_angle") port_location = ( - OneOrMore(Group(colon + ID)) | - Group(colon + lparen + ID + comma + ID + rparen) + OneOrMore(Group(colon + ID)) + | Group(colon + lparen + ID + comma + ID + rparen) ).setName("port_location") port = ( - Group(port_location + Optional(port_angle)) | - Group(port_angle + Optional(port_location)) + Group(port_location + Optional(port_angle)) + | Group(port_angle + Optional(port_location)) ).setName("port") - node_id = (ID + Optional(port)) + node_id = ID + Optional(port) a_list = OneOrMore( - ID + - Optional(equals + righthand_id) + - Optional(comma.suppress()) + ID + Optional(equals + righthand_id) + Optional(comma.suppress()) ).setName("a_list") attr_list = OneOrMore( - lbrack.suppress() + - Optional(a_list) + - rbrack.suppress() + 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") stmt_list = Forward() graph_stmt = Group( - lbrace.suppress() + - Optional(stmt_list) + - rbrace.suppress() + - Optional(semi.suppress()) + lbrace.suppress() + + Optional(stmt_list) + + rbrace.suppress() + + Optional(semi.suppress()) ).setName("graph_stmt") edge_point = Forward() @@ -474,27 +499,39 @@ def parse_html(s, loc, toks): 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())).setName("node_stmt") + node_id + Optional(attr_list) + Optional(semi.suppress()) + ).setName("node_stmt") assignment = (ID + equals + righthand_id).setName("assignment") - stmt = (assignment | edge_stmt | attr_stmt | - subgraph | graph_stmt | node_stmt).setName("stmt") + stmt = ( + assignment + | edge_stmt + | attr_stmt + | subgraph + | graph_stmt + | node_stmt + ).setName("stmt") stmt_list << OneOrMore(stmt + Optional(semi.suppress())) graphparser = OneOrMore( - (Optional(strict_) + Group((graph_ | digraph_)) + - Optional(ID) + graph_stmt).setResultsName("graph")) + ( + Optional(strict_) + + Group((graph_ | digraph_)) + + Optional(ID) + + graph_stmt + ).setResultsName("graph") + ) - singleLineComment = Group( - "//" + restOfLine) | Group("#" + restOfLine) + singleLineComment = Group("//" + restOfLine) | Group("#" + restOfLine) # actions diff --git a/pydot.py b/pydot.py index 1ed9284..67540e4 100644 --- a/pydot.py +++ b/pydot.py @@ -17,7 +17,8 @@ warnings.warn( "`pydot` could not import `dot_parser`, " "so `pydot` will be unable to parse DOT files. " - "The error was: {e}".format(e=e)) + "The error was: {e}".format(e=e) + ) __author__ = 'Ero Carrera' @@ -109,6 +110,7 @@ def is_windows(): def is_anaconda(): # type: () -> bool import glob + conda_pattern = os.path.join(sys.prefix, 'conda-meta\\graphviz*.json') return glob.glob(conda_pattern) != [] @@ -162,9 +164,9 @@ def call_graphviz(program, arguments, working_dir, **kwargs): # This version freezes dictionaries used as values within dictionaries. # class frozendict(dict): - def _blocked_attribute(obj): raise AttributeError('A frozendict cannot be modified.') + _blocked_attribute = property(_blocked_attribute) __delitem__ = __setitem__ = clear = _blocked_attribute @@ -216,7 +218,8 @@ def __repr__(self): id_re_alpha_nums = re.compile('^[_a-zA-Z][a-zA-Z0-9_,]*$', re.UNICODE) id_re_alpha_nums_with_ports = re.compile( - '^[_a-zA-Z][a-zA-Z0-9_,:\"]*[a-zA-Z0-9_,\"]+$', re.UNICODE) + '^[_a-zA-Z][a-zA-Z0-9_,:\"]*[a-zA-Z0-9_,\"]+$', re.UNICODE +) id_re_num = re.compile('^[0-9,]+$', re.UNICODE) id_re_with_port = re.compile('^([^:]*):([^:]*)$', re.UNICODE) id_re_dbl_quoted = re.compile('^\".*\"$', re.S | re.UNICODE) @@ -242,13 +245,17 @@ def needs_quotes(s): if s in dot_keywords: return False - chars = [ord(c) for c in s if ord(c) > 0x7f or ord(c) == 0] + chars = [ord(c) for c in s if ord(c) > 0x7F or ord(c) == 0] if chars and not id_re_dbl_quoted.match(s) and not id_re_html.match(s): return True - for test_re in [id_re_alpha_nums, id_re_num, - id_re_dbl_quoted, id_re_html, - id_re_alpha_nums_with_ports]: + for test_re in [ + id_re_alpha_nums, + id_re_num, + id_re_dbl_quoted, + id_re_html, + id_re_alpha_nums_with_ports, + ]: if test_re.match(s): return False @@ -273,9 +280,11 @@ def quote_if_necessary(s): return s if needs_quotes(s): - replace = {'"': r'\"', - "\n": r'\n', - "\r": r'\r',} + replace = { + '"': r'\"', + "\n": r'\n', + "\r": r'\r', + } for (a, b) in replace.items(): s = s.replace(a, b) @@ -372,13 +381,16 @@ def graph_from_adjacency_matrix(matrix, node_prefix=u'', directed=False): else: skip = 0 r = row - node_dest = skip+1 + node_dest = skip + 1 for e in r: if e: graph.add_edge( - Edge('%s%s' % (node_prefix, node_orig), - '%s%s' % (node_prefix, node_dest))) + Edge( + '%s%s' % (node_prefix, node_orig), + '%s%s' % (node_prefix, node_dest), + ) + ) node_dest += 1 node_orig += 1 @@ -405,14 +417,17 @@ def graph_from_incidence_matrix(matrix, node_prefix='', directed=False): for node in row: if node: - nodes.append(c*node) + nodes.append(c * node) c += 1 nodes.sort() if len(nodes) == 2: graph.add_edge( - Edge('%s%s' % (node_prefix, abs(nodes[0])), - '%s%s' % (node_prefix, nodes[1]))) + Edge( + '%s%s' % (node_prefix, abs(nodes[0])), + '%s%s' % (node_prefix, nodes[1]), + ) + ) if not directed: graph.set_simplify(True) @@ -520,21 +535,21 @@ def create_attribute_methods(self, obj_attributes): # Generate all the Setter methods. # self.__setattr__( - 'set_'+attr, - lambda x, a=attr: - self.obj_dict['attributes'].__setitem__(a, x) + '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) + 'get_' + attr, lambda a=attr: self.__get_attribute__(a) ) class Error(Exception): - """General error handling class. - """ + """General error handling class.""" def __init__(self, value): self.value = value @@ -544,8 +559,7 @@ def __str__(self): class InvocationException(Exception): - """Indicate problem while running any GraphViz executable. - """ + """Indicate problem while running any GraphViz executable.""" def __init__(self, value): self.value = value @@ -595,7 +609,7 @@ def __init__(self, name='', obj_dict=None, **attrs): port = None if isinstance(name, str_type) and not name.startswith('"'): idx = name.find(':') - if idx > 0 and idx+1 < len(name): + if idx > 0 and idx + 1 < len(name): name, port = name[:idx], name[idx:] if isinstance(name, int): @@ -644,9 +658,7 @@ def to_string(self): if value == '': value = '""' if value is not None: - node_attr.append( - '%s=%s' % (attr, quote_if_necessary(value)) - ) + node_attr.append('%s=%s' % (attr, quote_if_necessary(value))) else: node_attr.append(attr) @@ -699,8 +711,7 @@ def __init__(self, src='', dst='', obj_dict=None, **attrs): src = src.get_name() if isinstance(dst, (Node, Subgraph, Cluster)): dst = dst.get_name() - points = (quote_if_necessary(src), - quote_if_necessary(dst)) + points = (quote_if_necessary(src), quote_if_necessary(dst)) self.obj_dict['points'] = points if obj_dict is None: # Copy the attributes @@ -725,9 +736,7 @@ def get_destination(self): return self.obj_dict['points'][1] def __hash__(self): - return hash( - hash(self.get_source()) + hash(self.get_destination()) - ) + return hash(hash(self.get_source()) + hash(self.get_destination())) def __eq__(self, edge): """Compare two edges. @@ -748,20 +757,26 @@ def __eq__(self, edge): # 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 - edge.get_destination() == self.get_source())): + if ( + self.get_source() == edge.get_source() + and self.get_destination() == edge.get_destination() + ) or ( + edge.get_source() == self.get_destination() + and edge.get_destination() == self.get_source() + ): return True else: - if (self.get_source() == edge.get_source() and - self.get_destination() == edge.get_destination()): + if ( + self.get_source() == edge.get_source() + and self.get_destination() == edge.get_destination() + ): return True return False if not PY3: + def __ne__(self, other): result = self.__eq__(other) if result is NotImplemented: @@ -778,13 +793,16 @@ def parse_node_ref(self, node_str): node_port_idx = node_str.rfind(':') - if (node_port_idx > 0 and node_str[0] == '"' and - node_str[node_port_idx-1] == '"'): + if ( + node_port_idx > 0 + and node_str[0] == '"' + and node_str[node_port_idx - 1] == '"' + ): return node_str if node_port_idx > 0: a = node_str[:node_port_idx] - b = node_str[node_port_idx+1:] + b = node_str[node_port_idx + 1 :] node = quote_if_necessary(a) node += ':' + quote_if_necessary(b) @@ -806,9 +824,9 @@ def to_string(self): edge = [src] if ( - self.get_parent_graph() and - self.get_parent_graph().get_top_graph_type() and - self.get_parent_graph().get_top_graph_type() == 'digraph' + self.get_parent_graph() + and self.get_parent_graph().get_top_graph_type() + and self.get_parent_graph().get_top_graph_type() == 'digraph' ): edge.append('->') @@ -829,8 +847,7 @@ def to_string(self): if value == '': value = '""' if value is not None: - edge_attr.append( - '%s=%s' % (attr, quote_if_necessary(value))) + edge_attr.append('%s=%s' % (attr, quote_if_necessary(value))) else: edge_attr.append(attr) @@ -878,9 +895,16 @@ class Graph(Common): graph_instance.obj_dict['attributes']['fontname'] """ - def __init__(self, graph_name='G', obj_dict=None, - graph_type='digraph', strict=False, - suppress_disconnected=False, simplify=False, **attrs): + def __init__( + 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 @@ -891,10 +915,13 @@ def __init__(self, graph_name='G', obj_dict=None, self.obj_dict['attributes'] = dict(attrs) if graph_type not in ['graph', 'digraph']: - raise Error(( - 'Invalid type "{t}". ' - 'Accepted graph types are: ' - 'graph, digraph').format(t=graph_type)) + raise Error( + ( + 'Invalid type "{t}". ' + 'Accepted graph types are: ' + 'graph, digraph' + ).format(t=graph_type) + ) self.obj_dict['name'] = quote_if_necessary(graph_name) self.obj_dict['type'] = graph_type @@ -1043,8 +1070,10 @@ def add_node(self, graph_node): """ if not isinstance(graph_node, Node): raise TypeError( - 'add_node() received ' + - 'a non node class object: ' + str(graph_node)) + 'add_node() received ' + + 'a non node class object: ' + + str(graph_node) + ) node = self.get_node(graph_node.get_name()) @@ -1082,8 +1111,7 @@ def del_node(self, name, index=None): if name in self.obj_dict['nodes']: - if (index is not None and - index < len(self.obj_dict['nodes'][name])): + if index is not None and index < len(self.obj_dict['nodes'][name]): del self.obj_dict['nodes'][name][index] return True else: @@ -1107,8 +1135,11 @@ def get_node(self, name): if name in self.obj_dict['nodes']: match.extend( - [Node(obj_dict=obj_dict) - for obj_dict in self.obj_dict['nodes'][name]]) + [ + Node(obj_dict=obj_dict) + for obj_dict in self.obj_dict['nodes'][name] + ] + ) return match @@ -1126,10 +1157,7 @@ def get_node_list(self): for node in self.obj_dict['nodes']: obj_dict_list = self.obj_dict['nodes'][node] - node_objs.extend([ - Node(obj_dict=obj_d) - for obj_d in obj_dict_list - ]) + node_objs.extend([Node(obj_dict=obj_d) for obj_d in obj_dict_list]) return node_objs @@ -1141,11 +1169,11 @@ def add_edge(self, graph_edge): """ if not isinstance(graph_edge, Edge): raise TypeError( - 'add_edge() received a non edge class object: ' + - str(graph_edge)) + 'add_edge() received a non edge class object: ' + + str(graph_edge) + ) - edge_points = (graph_edge.get_source(), - graph_edge.get_destination()) + edge_points = (graph_edge.get_source(), graph_edge.get_destination()) if edge_points in self.obj_dict['edges']: edge_list = self.obj_dict['edges'][edge_points] @@ -1186,8 +1214,9 @@ def del_edge(self, src_or_list, dst=None, index=None): 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: @@ -1216,18 +1245,20 @@ def get_edge(self, src_or_list, dst=None): match = list() if edge_points in self.obj_dict['edges'] or ( - self.get_top_graph_type() == 'graph' and - edge_points_reverse in self.obj_dict['edges'] + self.get_top_graph_type() == 'graph' + and edge_points_reverse in self.obj_dict['edges'] ): edges_obj_dict = self.obj_dict['edges'].get( edge_points, - self.obj_dict['edges'].get(edge_points_reverse, None)) + self.obj_dict['edges'].get(edge_points_reverse, None), + ) 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 @@ -1244,10 +1275,7 @@ def get_edge_list(self): for edge in self.obj_dict['edges']: obj_dict_list = self.obj_dict['edges'][edge] - edge_objs.extend([ - Edge(obj_dict=obj_d) - for obj_d in obj_dict_list - ]) + edge_objs.extend([Edge(obj_dict=obj_d) for obj_d in obj_dict_list]) return edge_objs @@ -1257,11 +1285,13 @@ def add_subgraph(self, sgraph): It takes a subgraph object as its only argument and returns None. """ - if (not isinstance(sgraph, Subgraph) and - not isinstance(sgraph, Cluster)): + if not isinstance(sgraph, Subgraph) and not isinstance( + sgraph, Cluster + ): raise TypeError( - 'add_subgraph() received a non subgraph class object:' + - str(sgraph)) + 'add_subgraph() received a non subgraph class object:' + + str(sgraph) + ) if sgraph.get_name() in self.obj_dict['subgraphs']: @@ -1269,8 +1299,7 @@ def add_subgraph(self, sgraph): sgraph_list.append(sgraph.obj_dict) else: - self.obj_dict['subgraphs'][sgraph.get_name()] = [ - sgraph.obj_dict] + self.obj_dict['subgraphs'][sgraph.get_name()] = [sgraph.obj_dict] sgraph.set_sequence(self.get_next_sequence_number()) sgraph.set_parent_graph(self.get_parent_graph()) @@ -1309,10 +1338,9 @@ def get_subgraph_list(self): for sgraph in self.obj_dict['subgraphs']: obj_dict_list = self.obj_dict['subgraphs'][sgraph] - sgraph_objs.extend([ - Subgraph(obj_dict=obj_d) - for obj_d in obj_dict_list - ]) + sgraph_objs.extend( + [Subgraph(obj_dict=obj_d) for obj_d in obj_dict_list] + ) return sgraph_objs @@ -1344,18 +1372,18 @@ def to_string(self): if self.obj_dict.get('strict', None) is not None: - if (self == self.get_parent_graph() and - self.obj_dict['strict']): + if self == self.get_parent_graph() and self.obj_dict['strict']: graph.append('strict ') graph_type = self.obj_dict['type'] - if (graph_type == 'subgraph' and - not self.obj_dict.get('show_keyword', True)): + if graph_type == 'subgraph' and not self.obj_dict.get( + 'show_keyword', True + ): graph_type = '' s = '{type} {name} {{\n'.format( - type=graph_type, - name=self.obj_dict['name']) + type=graph_type, name=self.obj_dict['name'] + ) graph.append(s) for attr in sorted(self.obj_dict['attributes']): @@ -1366,8 +1394,7 @@ def to_string(self): if val == '': val = '""' if val is not None: - graph.append('%s=%s' % - (attr, quote_if_necessary(val))) + graph.append('%s=%s' % (attr, quote_if_necessary(val))) else: graph.append(attr) @@ -1380,8 +1407,9 @@ def to_string(self): edge_obj_dicts.extend(self.obj_dict['edges'][k]) 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() @@ -1407,8 +1435,10 @@ def to_string(self): if self.obj_dict.get('suppress_disconnected', False): - if (node.get_name() not in edge_src_set and - node.get_name() not in edge_dst_set): + if ( + node.get_name() not in edge_src_set + and node.get_name() not in edge_dst_set + ): continue graph.append(node.to_string() + '\n') @@ -1416,8 +1446,7 @@ def to_string(self): elif obj['type'] == 'edge': edge = Edge(obj_dict=obj) - if (self.obj_dict.get('simplify', False) and - edge in edges_done): + if self.obj_dict.get('simplify', False) and edge in edges_done: continue graph.append(edge.to_string() + '\n') @@ -1425,7 +1454,7 @@ def to_string(self): else: sgraph = Subgraph(obj_dict=obj) - graph.append(sgraph.to_string()+'\n') + graph.append(sgraph.to_string() + '\n') graph.append('}\n') @@ -1467,13 +1496,22 @@ class Subgraph(Graph): # 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): + 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, + self, + graph_name=graph_name, + obj_dict=obj_dict, suppress_disconnected=suppress_disconnected, - simplify=simplify, **attrs) + simplify=simplify, + **attrs + ) if obj_dict is None: @@ -1512,18 +1550,27 @@ 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, + self, + graph_name=graph_name, + obj_dict=obj_dict, suppress_disconnected=suppress_disconnected, - simplify=simplify, **attrs) + simplify=simplify, + **attrs + ) if obj_dict is None: self.obj_dict['type'] = 'subgraph' - self.obj_dict['name'] = quote_if_necessary('cluster_'+graph_name) + self.obj_dict['name'] = quote_if_necessary('cluster_' + graph_name) self.create_attribute_methods(CLUSTER_ATTRIBUTES) @@ -1541,15 +1588,43 @@ def __init__(self, *argsl, **argsd): self.shape_files = list() self.formats = [ - 'canon', 'cmap', 'cmapx', - 'cmapx_np', 'dia', 'dot', - 'fig', 'gd', 'gd2', 'gif', - 'hpgl', 'imap', 'imap_np', 'ismap', - 'jpe', 'jpeg', 'jpg', 'mif', - 'mp', 'pcl', 'pdf', 'pic', 'plain', - 'plain-ext', 'png', 'ps', 'ps2', - 'svg', 'svgz', 'vml', 'vmlz', - 'vrml', 'vtx', 'wbmp', 'xdot', 'xlib'] + 'canon', + 'cmap', + 'cmapx', + 'cmapx_np', + 'dia', + 'dot', + 'fig', + 'gd', + 'gd2', + 'gif', + 'hpgl', + 'imap', + 'imap_np', + 'ismap', + 'jpe', + 'jpeg', + 'jpg', + 'mif', + 'mp', + 'pcl', + 'pdf', + 'pic', + 'plain', + 'plain-ext', + 'png', + 'ps', + 'ps2', + 'svg', + 'svgz', + 'vml', + 'vmlz', + 'vrml', + 'vtx', + 'wbmp', + 'xdot', + 'xlib', + ] self.prog = 'dot' @@ -1557,23 +1632,20 @@ def __init__(self, *argsl, **argsd): # the methods enabling the creation # of output in any of the supported formats. for frmt in self.formats: - def new_method( - f=frmt, prog=self.prog, - encoding=None): + + def new_method(f=frmt, prog=self.prog, encoding=None): """Refer to docstring of method `create`.""" - return self.create( - format=f, prog=prog, encoding=encoding) + return self.create(format=f, prog=prog, encoding=encoding) + name = 'create_{fmt}'.format(fmt=frmt) self.__setattr__(name, new_method) - for frmt in self.formats+['raw']: - def new_method( - path, f=frmt, prog=self.prog, - encoding=None): + for frmt in self.formats + ['raw']: + + def new_method(path, f=frmt, prog=self.prog, encoding=None): """Refer to docstring of method `write.`""" - self.write( - path, format=f, prog=prog, - encoding=encoding) + self.write(path, format=f, prog=prog, encoding=encoding) + name = 'write_{fmt}'.format(fmt=frmt) self.__setattr__(name, new_method) @@ -1751,8 +1823,7 @@ def create(self, prog=None, format='ps', encoding=None): except OSError as e: if e.errno == errno.ENOENT: args = list(e.args) - args[1] = '"{prog}" not found in path.'.format( - prog=prog) + args[1] = '"{prog}" not found in path.'.format(prog=prog) raise OSError(*args) else: raise @@ -1776,12 +1847,12 @@ def create(self, prog=None, format='ps', encoding=None): ) print(message) - assert process.returncode == 0, ( - '"{prog}" with args {arguments} returned code: {code}'.format( - prog=prog, - arguments=arguments, - code=process.returncode, - ) - ) + assert ( + process.returncode == 0 + ), '"{prog}" with args {arguments} returned code: {code}'.format( + prog=prog, + arguments=arguments, + code=process.returncode, + ) return stdout_data diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8c81552 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,4 @@ +[tool.black] +line-length = 79 +skip-string-normalization = true # To be removed in next commit. +target-version = ['py27'] diff --git a/setup.py b/setup.py index 97da696..ae3d3ee 100644 --- a/setup.py +++ b/setup.py @@ -63,9 +63,11 @@ def get_version(): 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Topic :: Scientific/Engineering :: Visualization', - 'Topic :: Software Development :: Libraries :: Python Modules'], + 'Topic :: Software Development :: Libraries :: Python Modules', + ], long_description=get_long_description(), long_description_content_type="text/markdown", py_modules=['pydot', 'dot_parser'], install_requires=['pyparsing>=2.1.4'], - tests_require=['chardet']) + tests_require=['chardet'], +) diff --git a/test/pydot_unittest.py b/test/pydot_unittest.py index 5fd968a..fae9f81 100644 --- a/test/pydot_unittest.py +++ b/test/pydot_unittest.py @@ -26,13 +26,11 @@ class TestGraphAPI(unittest.TestCase): - def setUp(self): self._reset_graphs() def _reset_graphs(self): - self.graph_directed = pydot.Graph('testgraph', - graph_type='digraph') + self.graph_directed = pydot.Graph('testgraph', graph_type='digraph') def test_keep_graph_type(self): g = pydot.Dot(graph_name='Test', graph_type='graph') @@ -78,10 +76,8 @@ def test_subgraphs(self): g.add_subgraph(s) - self.assertEqual(g.get_subgraphs()[0].get_name(), - s.get_name()) - self.assertEqual(g.get_subgraph_list()[0].get_name(), - s.get_name()) + self.assertEqual(g.get_subgraphs()[0].get_name(), s.get_name()) + self.assertEqual(g.get_subgraph_list()[0].get_name(), s.get_name()) def test_graph_pickling(self): g = pydot.Graph() @@ -125,18 +121,34 @@ def test_graph_simplify(self): g.add_edge(pydot.Edge('b', 'a')) g.add_edge(pydot.Edge('b', 'a')) test_combinations = [ - ('graph', False, - 'graph G { a -- b; a -- b; b -- a; b -- a; }',), - ('graph', True, - 'graph G { a -- b; }',), - ('digraph', False, - 'digraph G { a -> b; a -> b; b -> a; b -> a; }',), - ('digraph', True, - 'digraph G { a -> b; b -> a; }',)] + ( + 'graph', + False, + 'graph G { a -- b; a -- b; b -- a; b -- a; }', + ), + ( + 'graph', + True, + 'graph G { a -- b; }', + ), + ( + 'digraph', + False, + 'digraph G { a -> b; a -> b; b -> a; b -> a; }', + ), + ( + 'digraph', + True, + 'digraph G { a -> b; b -> a; }', + ), + ] expected_concat = observed_concat = '' for (graph_type, simplify, expected) in test_combinations: expected_concat += 'graph_type %s, simplify %s: %s\n' % ( - graph_type, simplify, expected) + graph_type, + simplify, + expected, + ) g.set_type(graph_type) g.set_simplify(simplify) try: @@ -144,7 +156,10 @@ def test_graph_simplify(self): except (NameError, TypeError) as e: observed = '%s: %s' % (type(e).__name__, e) observed_concat += 'graph_type %s, simplify %s: %s\n' % ( - graph_type, simplify, observed) + graph_type, + simplify, + observed, + ) self.maxDiff = None self.assertMultiLineEqual(expected_concat, observed_concat) @@ -152,8 +167,10 @@ def test_graph_with_shapefiles(self): shapefile_dir = os.path.join(test_dir, 'from-past-to-future') # image files are omitted from sdist if not os.path.isdir(shapefile_dir): - warnings.warn('Skipping tests that involve images, ' - 'they can be found in the `git` repository.') + warnings.warn( + 'Skipping tests that involve images, ' + 'they can be found in the `git` repository.' + ) return dot_file = os.path.join(shapefile_dir, 'from-past-to-future.dot') @@ -175,7 +192,8 @@ def test_graph_with_shapefiles(self): hexdigest = sha256(jpe_data).hexdigest() hexdigest_original = self._render_with_graphviz( - dot_file, encoding='ascii') + dot_file, encoding='ascii' + ) self.assertEqual(hexdigest, hexdigest_original) def test_multiple_graphs(self): @@ -202,8 +220,9 @@ def _render_with_pydot(self, filename, encoding): c = pydot.graph_from_dot_file(filename, encoding=encoding) jpe_data = bytearray() for g in c: - jpe_data.extend(g.create(prog=TEST_PROGRAM, format='jpe', - encoding=encoding)) + jpe_data.extend( + g.create(prog=TEST_PROGRAM, format='jpe', encoding=encoding) + ) return sha256(jpe_data).hexdigest() def test_my_regression_tests(self): @@ -216,11 +235,10 @@ def test_graphviz_regression_tests(self): def _render_and_compare_dot_files(self, directory): # files that confuse `chardet` - encodings = { - 'Latin1.dot': 'latin-1'} + encodings = {'Latin1.dot': 'latin-1'} dot_files = [ - fname for fname in os.listdir(directory) - if fname.endswith('.dot')] + fname for fname in os.listdir(directory) if fname.endswith('.dot') + ] for fname in dot_files: fpath = os.path.join(directory, fname) with open(fpath, 'rb') as f: @@ -255,28 +273,23 @@ def test_quoted_node_id_to_string_no_attributes(self): 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(), '' - ) + 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.graph_directed.get_nodes()[0].to_string(), 'node [shape=box];' ) def test_names_of_a_thousand_nodes(self): self._reset_graphs() - names = {'node_%05d' % i for i in range(10**3)} + names = {'node_%05d' % i for i in range(10 ** 3)} for name in names: self.graph_directed.add_node(pydot.Node(name, label=name)) @@ -286,22 +299,22 @@ def test_names_of_a_thousand_nodes(self): def test_executable_not_found_exception(self): graph = pydot.Dot('graphname', graph_type='digraph') - self.assertRaises(Exception, graph.create, prog='dothehe') + self.assertRaises(Exception, graph.create, prog='dothehe') 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') + 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') + 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') + self.assertRaises(TypeError, self.graph_directed.add_subgraph, 1) + self.assertRaises(TypeError, self.graph_directed.add_subgraph, 'a') def test_quoting(self): g = pydot.Dot() @@ -328,7 +341,8 @@ def test_edge_point_namestr(self): self._reset_graphs() self.graph_directed.add_edge(pydot.Edge('a', 'b')) self.assertEqual( - self.graph_directed.get_edges()[0].to_string(), 'a -> b;') + self.graph_directed.get_edges()[0].to_string(), 'a -> b;' + ) def test_edge_point_object_node(self): self._reset_graphs() @@ -336,7 +350,8 @@ def test_edge_point_object_node(self): pydot.Edge(pydot.Node('a'), pydot.Node('b')) ) self.assertEqual( - self.graph_directed.get_edges()[0].to_string(), 'a -> b;') + self.graph_directed.get_edges()[0].to_string(), 'a -> b;' + ) def test_edge_point_object_subgraph(self): self._reset_graphs() @@ -344,7 +359,8 @@ def test_edge_point_object_subgraph(self): pydot.Edge(pydot.Subgraph('a'), pydot.Subgraph('b')) ) self.assertEqual( - self.graph_directed.get_edges()[0].to_string(), 'a -> b;') + self.graph_directed.get_edges()[0].to_string(), 'a -> b;' + ) def test_edge_point_object_cluster(self): self._reset_graphs() @@ -353,26 +369,30 @@ def test_edge_point_object_cluster(self): ) self.assertEqual( self.graph_directed.get_edges()[0].to_string(), - 'cluster_a -> cluster_b;' + 'cluster_a -> cluster_b;', ) def test_graph_from_adjacency_matrix(self): g = pydot.graph_from_adjacency_matrix( - [[0, 1, 0], [1, 0, 0], [0, 1, 1]], directed=True) + [[0, 1, 0], [1, 0, 0], [0, 1, 1]], directed=True + ) s = ' '.join(g.to_string().split()) self.assertEqual(s, 'digraph G { 1 -> 2; 2 -> 1; 3 -> 2; 3 -> 3; }') g = pydot.graph_from_adjacency_matrix( - [[0, 1, 0], [1, 0, 0], [0, 0, 1]], directed=False) + [[0, 1, 0], [1, 0, 0], [0, 0, 1]], directed=False + ) s = ' '.join(g.to_string().split()) self.assertEqual(s, 'graph G { 1 -- 2; 3 -- 3; }') def test_graph_from_incidence_matrix(self): g = pydot.graph_from_incidence_matrix( - [[-1, 1, 0], [1, -1, 0], [0, 1, -1]], directed=True) + [[-1, 1, 0], [1, -1, 0], [0, 1, -1]], directed=True + ) s = ' '.join(g.to_string().split()) self.assertEqual(s, 'digraph G { 1 -> 2; 2 -> 1; 3 -> 2; }') g = pydot.graph_from_incidence_matrix( - [[1, 1, 0], [0, 1, 1]], directed=False) + [[1, 1, 0], [0, 1, 1]], directed=False + ) s = ' '.join(g.to_string().split()) self.assertEqual(s, 'graph G { 1 -- 2; 2 -- 3; }') @@ -381,18 +401,22 @@ def check_path(): not_check = parse_args() if not_check: return - assert not os.path.isfile('setup.py'), ( - 'running out of source does not test the installed `pydot`.' - ) + assert not os.path.isfile( + 'setup.py' + ), 'running out of source does not test the installed `pydot`.' def parse_args(): """Return arguments.""" parser = argparse.ArgumentParser() parser.add_argument( - '--no-check', action='store_true', - help=('do not require that no `setup.py` be present ' - 'in the current working directory.')) + '--no-check', + action='store_true', + help=( + 'do not require that no `setup.py` be present ' + 'in the current working directory.' + ), + ) args, unknown = parser.parse_known_args() # avoid confusing `unittest` sys.argv = [sys.argv[0]] + unknown