Repository: networkx/networkx

Issue:
discrepancy between documentation and implementation of nx_agraph.to_agraph function
[Documentation](http://networkx.readthedocs.io/en/networkx-1.11/tutorial/tutorial.html#graph-attributes) says to add graph attribute I should do:

```
>>> G = nx.Graph(day="Friday")
>>> G.graph
{'day': 'Friday'}
```
or
```
>>> G.graph['day']='Monday'
>>> G.graph
{'day': 'Monday'}
```

Yet based on [the code that converts to an `pygraphviz.AGraph`](https://github.com/networkx/networkx/blob/v1.11/networkx/drawing/nx_agraph.py#L140-L143) it seems like the following would be more appropriate:
```
>>> G = nx.Graph(graph={"day":"Friday"})
>>> G.graph['graph']
{'day': 'Friday'}
```
or
```
>>> G.graph['graph'] = dict()
>>> G.graph['graph']['day']='Monday'
>>> G.graph['graph']
{'day': 'Monday'}
```

This is actually how I have to construct my `nx.DiGraph` in order to end up with a `pygraphviz.AGraph` that actually includes the `label` attribute that I want to give it.


Discussion:
After thinking this over for a few minutes, it seems most likely that the bug is in `to_agraph` (and maybe also `from_agraph`) so I might put some thought into test cases and a fix for this problem in the near future.

Resolving patch:
diff --git a/networkx/drawing/nx_agraph.py b/networkx/drawing/nx_agraph.py
--- a/networkx/drawing/nx_agraph.py
+++ b/networkx/drawing/nx_agraph.py
@@ -1,3 +1,11 @@
+#    Copyright (C) 2004-2017 by
+#    Aric Hagberg <hagberg@lanl.gov>
+#    Dan Schult <dschult@colgate.edu>
+#    Pieter Swart <swart@lanl.gov>
+#    All rights reserved.
+#    BSD license.
+#
+# Author: Aric Hagberg (hagberg@lanl.gov)
 """
 ***************
 Graphviz AGraph
@@ -15,14 +23,6 @@
 --------
 Pygraphviz: http://pygraphviz.github.io/
 """
-# Author: Aric Hagberg (hagberg@lanl.gov)
-
-#    Copyright (C) 2004-2016 by
-#    Aric Hagberg <hagberg@lanl.gov>
-#    Dan Schult <dschult@colgate.edu>
-#    Pieter Swart <swart@lanl.gov>
-#    All rights reserved.
-#    BSD license.
 import os
 import sys
 import tempfile
@@ -34,7 +34,8 @@
            'pygraphviz_layout',
            'view_pygraphviz']
 
-def from_agraph(A,create_using=None):
+
+def from_agraph(A, create_using=None):
     """Return a NetworkX Graph or DiGraph from a PyGraphviz graph.
 
     Parameters
@@ -69,45 +70,49 @@ def from_agraph(A,create_using=None):
     if create_using is None:
         if A.is_directed():
             if A.is_strict():
-                create_using=nx.DiGraph()
+                create_using = nx.DiGraph()
             else:
-                create_using=nx.MultiDiGraph()
+                create_using = nx.MultiDiGraph()
         else:
             if A.is_strict():
-                create_using=nx.Graph()
+                create_using = nx.Graph()
             else:
-                create_using=nx.MultiGraph()
+                create_using = nx.MultiGraph()
 
     # assign defaults
-    N=nx.empty_graph(0,create_using)
-    N.name=''
+    N = nx.empty_graph(0, create_using)
+    N.name = ''
     if A.name is not None:
-        N.name=A.name
+        N.name = A.name
+
+    # add graph attributes
+    N.graph.update(A.graph_attr)
 
     # add nodes, attributes to N.node_attr
     for n in A.nodes():
-        str_attr=dict((str(k),v) for k,v in n.attr.items())
-        N.add_node(str(n),**str_attr)
+        str_attr = {str(k): v for k, v in n.attr.items()}
+        N.add_node(str(n), **str_attr)
 
     # add edges, assign edge data as dictionary of attributes
     for e in A.edges():
-        u,v=str(e[0]),str(e[1])
-        attr=dict(e.attr)
-        str_attr=dict((str(k),v) for k,v in attr.items())
+        u, v = str(e[0]), str(e[1])
+        attr = dict(e.attr)
+        str_attr = {str(k): v for k, v in attr.items()}
         if not N.is_multigraph():
             if e.name is not None:
-                str_attr['key']=e.name
-            N.add_edge(u,v,**str_attr)
+                str_attr['key'] = e.name
+            N.add_edge(u, v, **str_attr)
         else:
-            N.add_edge(u,v,key=e.name,**str_attr)
+            N.add_edge(u, v, key=e.name, **str_attr)
 
     # add default attributes for graph, nodes, and edges
     # hang them on N.graph_attr
-    N.graph['graph']=dict(A.graph_attr)
-    N.graph['node']=dict(A.node_attr)
-    N.graph['edge']=dict(A.edge_attr)
+    N.graph['graph'] = dict(A.graph_attr)
+    N.graph['node'] = dict(A.node_attr)
+    N.graph['edge'] = dict(A.edge_attr)
     return N
 
+
 def to_agraph(N):
     """Return a pygraphviz graph from a NetworkX graph N.
 
@@ -133,34 +138,36 @@ def to_agraph(N):
     except ImportError:
         raise ImportError('requires pygraphviz ',
                           'http://pygraphviz.github.io/')
-    directed=N.is_directed()
-    strict=N.number_of_selfloops()==0 and not N.is_multigraph()
-    A=pygraphviz.AGraph(name=N.name,strict=strict,directed=directed)
+    directed = N.is_directed()
+    strict = N.number_of_selfloops() == 0 and not N.is_multigraph()
+    A = pygraphviz.AGraph(name=N.name, strict=strict, directed=directed)
 
     # default graph attributes
-    A.graph_attr.update(N.graph.get('graph',{}))
-    A.node_attr.update(N.graph.get('node',{}))
-    A.edge_attr.update(N.graph.get('edge',{}))
+    A.graph_attr.update(N.graph.get('graph', {}))
+    A.node_attr.update(N.graph.get('node', {}))
+    A.edge_attr.update(N.graph.get('edge', {}))
+
+    A.graph_attr.update(N.graph)
 
     # add nodes
-    for n,nodedata in N.nodes(data=True):
-        A.add_node(n,**nodedata)
+    for n, nodedata in N.nodes(data=True):
+        A.add_node(n, **nodedata)
 
     # loop over edges
 
     if N.is_multigraph():
-        for u,v,key,edgedata in N.edges(data=True,keys=True):
-            str_edgedata=dict((k,str(v)) for k,v in edgedata.items() if k != 'key')
-            A.add_edge(u,v,key=str(key),**str_edgedata)
+        for u, v, key, edgedata in N.edges(data=True, keys=True):
+            str_edata = {k: str(v) for k, v in edgedata.items() if k != 'key'}
+            A.add_edge(u, v, key=str(key), **str_edata)
     else:
-        for u,v,edgedata in N.edges(data=True):
-            str_edgedata=dict((k,str(v)) for k,v in edgedata.items())
-            A.add_edge(u,v,**str_edgedata)
-
+        for u, v, edgedata in N.edges(data=True):
+            str_edgedata = {k: str(v) for k, v in edgedata.items()}
+            A.add_edge(u, v, **str_edgedata)
 
     return A
 
-def write_dot(G,path):
+
+def write_dot(G, path):
     """Write NetworkX graph G to Graphviz dot format on path.
 
     Parameters
@@ -175,11 +182,12 @@ def write_dot(G,path):
     except ImportError:
         raise ImportError('requires pygraphviz ',
                           'http://pygraphviz.github.io/')
-    A=to_agraph(G)
+    A = to_agraph(G)
     A.write(path)
     A.clear()
     return
 
+
 def read_dot(path):
     """Return a NetworkX graph from a dot file on path.
 
@@ -193,11 +201,11 @@ def read_dot(path):
     except ImportError:
         raise ImportError('read_dot() requires pygraphviz ',
                           'http://pygraphviz.github.io/')
-    A=pygraphviz.AGraph(file=path)
+    A = pygraphviz.AGraph(file=path)
     return from_agraph(A)
 
 
-def graphviz_layout(G,prog='neato',root=None, args=''):
+def graphviz_layout(G, prog='neato', root=None, args=''):
     """Create node positions for G using Graphviz.
 
     Parameters
@@ -212,7 +220,7 @@ def graphviz_layout(G,prog='neato',root=None, args=''):
       Extra arguments to Graphviz layout program
 
     Returns : dictionary
-      Dictionary of x,y, positions keyed by node.
+      Dictionary of x, y, positions keyed by node.
 
     Examples
     --------
@@ -225,9 +233,10 @@ def graphviz_layout(G,prog='neato',root=None, args=''):
     This is a wrapper for pygraphviz_layout.
 
     """
-    return pygraphviz_layout(G,prog=prog,root=root,args=args)
+    return pygraphviz_layout(G, prog=prog, root=root, args=args)
+
 
-def pygraphviz_layout(G,prog='neato',root=None, args=''):
+def pygraphviz_layout(G, prog='neato', root=None, args=''):
     """Create node positions for G using Graphviz.
 
     Parameters
@@ -242,7 +251,7 @@ def pygraphviz_layout(G,prog='neato',root=None, args=''):
       Extra arguments to Graphviz layout program
 
     Returns : dictionary
-      Dictionary of x,y, positions keyed by node.
+      Dictionary of x, y, positions keyed by node.
 
     Examples
     --------
@@ -257,23 +266,24 @@ def pygraphviz_layout(G,prog='neato',root=None, args=''):
         raise ImportError('requires pygraphviz ',
                           'http://pygraphviz.github.io/')
     if root is not None:
-        args+="-Groot=%s"%root
-    A=to_agraph(G)
-    A.layout(prog=prog,args=args)
-    node_pos={}
+        args += "-Groot=%s" % root
+    A = to_agraph(G)
+    A.layout(prog=prog, args=args)
+    node_pos = {}
     for n in G:
-        node=pygraphviz.Node(A,n)
+        node = pygraphviz.Node(A, n)
         try:
-            xx,yy=node.attr["pos"].split(',')
-            node_pos[n]=(float(xx),float(yy))
+            xx, yy = node.attr["pos"].split(',')
+            node_pos[n] = (float(xx), float(yy))
         except:
-            print("no position for node",n)
-            node_pos[n]=(0.0,0.0)
+            print("no position for node", n)
+            node_pos[n] = (0.0, 0.0)
     return node_pos
 
+
 @nx.utils.open_file(5, 'w')
 def view_pygraphviz(G, edgelabel=None, prog='dot', args='',
-                       suffix='', path=None):
+                    suffix='', path=None):
     """Views the graph G using the specified layout algorithm.
 
     Parameters
@@ -340,7 +350,7 @@ def view_pygraphviz(G, edgelabel=None, prog='dot', args='',
     def update_attrs(which, attrs):
         # Update graph attributes. Return list of those which were added.
         added = []
-        for k,v in attrs.items():
+        for k, v in attrs.items():
             if k not in G.graph[which]:
                 G.graph[which][k] = v
                 added.append(k)
@@ -375,13 +385,13 @@ def func(data):
 
         # update all the edge labels
         if G.is_multigraph():
-            for u,v,key,data in G.edges(keys=True, data=True):
+            for u, v, key, data in G.edges(keys=True, data=True):
                 # PyGraphviz doesn't convert the key to a string. See #339
-                edge = A.get_edge(u,v,str(key))
+                edge = A.get_edge(u, v, str(key))
                 edge.attr['label'] = str(func(data))
         else:
-            for u,v,data in G.edges(data=True):
-                edge = A.get_edge(u,v)
+            for u, v, data in G.edges(data=True):
+                edge = A.get_edge(u, v)
                 edge.attr['label'] = str(func(data))
 
     if path is None:
@@ -399,6 +409,7 @@ def func(data):
 
     return path.name, A
 
+
 def display_pygraphviz(graph, path, format=None, prog=None, args=''):
     """Internal function to display a graph in OS dependent manner.
 
@@ -436,6 +447,7 @@ def display_pygraphviz(graph, path, format=None, prog=None, args=''):
     path.close()
     nx.utils.default_opener(filename)
 
+
 # fixture for nose tests
 def setup_module(module):
     from nose import SkipTest


Test patch:
diff --git a/networkx/drawing/tests/test_agraph.py b/networkx/drawing/tests/test_agraph.py
--- a/networkx/drawing/tests/test_agraph.py
+++ b/networkx/drawing/tests/test_agraph.py
@@ -2,7 +2,7 @@
 import os
 import tempfile
 from nose import SkipTest
-from nose.tools import assert_true,assert_equal
+from nose.tools import assert_true, assert_equal
 import networkx as nx
 
 
@@ -16,13 +16,16 @@ def setupClass(cls):
             raise SkipTest('PyGraphviz not available.')
 
     def build_graph(self, G):
-        G.add_edges_from([('A','B'),('A','C'),('A','C'),('B','C'),('A','D')])
+        edges = [('A', 'B'), ('A', 'C'), ('A', 'C'), ('B', 'C'), ('A', 'D')]
+        G.add_edges_from(edges)
         G.add_node('E')
+        G.graph['metal'] = 'bronze'
         return G
 
     def assert_equal(self, G1, G2):
-        assert_true( sorted(G1.nodes()) == sorted(G2.nodes()) )
-        assert_true( sorted(G1.edges()) == sorted(G2.edges()) )
+        assert_equal(sorted(G1.nodes()), sorted(G2.nodes()))
+        assert_equal(sorted(G1.edges()), sorted(G2.edges()))
+        assert_equal(G1.graph['metal'], G2.graph['metal'])
 
     def agraph_checks(self, G):
         G = self.build_graph(G)
@@ -36,7 +39,7 @@ def agraph_checks(self, G):
         os.unlink(fname)
         self.assert_equal(H, Hin)
 
-        (fd,fname) = tempfile.mkstemp()
+        (fd, fname) = tempfile.mkstemp()
         with open(fname, 'w') as fh:
             nx.drawing.nx_agraph.write_dot(H, fh)