class TestGraphIteration(UnittestPythonCompatibility): """ Test methods for iteration over nodes and edges in a graph """ def setUp(self): """ Build default Graph with node and edge attributes """ self.graph = Graph() self.graph.add_nodes([('g', {'weight': 1.0, 'value': 'gr'}), ('r', {'weight': 1.5, 'value': 'ra'}), ('a', {'weight': 2.0, 'value': 'ap'}), ('p', {'weight': 2.5, 'value': 'ph'}), ('h', {'weight': 3.0})]) self.graph.add_edges([(1, 2), (2, 3), (3, 4), (3, 5), (4, 5)], value=True, weight=43.2, key='edge') def test_iterators_isgenerator(self): """ Node and edge iterators return a generator """ self.assertTrue(isinstance(self.graph.iternodes(), types.GeneratorType)) self.assertTrue(isinstance(self.graph.iteredges(), types.GeneratorType)) def test_iterators_iternodes(self): """ Iternodes returns single node graphs based on sorted node ID which in case of auto_nid returns the nodes in the order they where added. """ for i, n in enumerate(self.graph.iternodes(), start=1): self.assertIsInstance(n, Graph) self.assertEqual(n.nid, i) # The Graph '__iter__' magic method points to iternodes for i, n in enumerate(self.graph, start=1): self.assertIsInstance(n, Graph) self.assertEqual(n.nid, i) def test_iterators_iternodes_reversed(self): """ Iterate over nodes in reversed order based on node ID """ self.assertListEqual([n.nid for n in self.graph.iternodes(reverse=True)], [5, 4, 3, 2, 1]) def test_iterators_iternodes_subgraph(self): """ Iternodes on a subgraph will only iterate over the nodes in the subgraph """ sub = self.graph.getnodes([1, 3, 4]) self.assertEqual(len(sub), 3) self.assertEqual([n.nid for n in sub.iternodes()], [1, 3, 4]) self.assertEqual([n.nid for n in sub], [1, 3, 4]) def test_iterators_iteredges(self): """ Iteredges returns single edge graphs based on sorted edge ID. """ edges = [] for e in self.graph.iteredges(): self.assertIsInstance(e, Graph) edges.append(e.nid) self.assertListEqual(edges, [(1, 2), (2, 1), (2, 3), (3, 2), (3, 4), (3, 5), (4, 3), (4, 5), (5, 3), (5, 4)]) def test_iterators_iteredges_reversed(self): """ Iterate over edges in reversed order based on edge ID """ self.assertListEqual([e.nid for e in self.graph.iteredges(reverse=True)], [(5, 4), (5, 3), (4, 5), (4, 3), (3, 5), (3, 4), (3, 2), (2, 3), (2, 1), (1, 2)])
class TestGraphCopy(UnittestPythonCompatibility): """ Test Graph copy and deepcopy methods """ def setUp(self): """ Build default graph with nodes, edges and attributes """ self.graph = Graph() self.graph.add_nodes([('g', { 'weight': 1.0 }), ('r', { 'weight': 1.5 }), ('a', { 'weight': 2.0 }), ('p', { 'weight': 2.5 }), ('h', { 'weight': 3.0 })]) self.graph.add_edges([(1, 2), (2, 3), (3, 4), (3, 5), (4, 5)], isedge=True) def tearDown(self): """ Test copied state Testing equality in node, edge and adjacency data stores is based on the internal '_storage' object and not so much the storage object itself which is often just a wrapper. """ # Main Graph object is new self.assertTrue(id(self.copied) != id(self.graph)) if self.shallow: # Internal node and edge stores point to parent. self.assertEqual(id(self.copied.nodes._storage), id(self.graph.nodes._storage)) self.assertEqual(id(self.copied.edges._storage), id(self.graph.edges._storage)) # ORM and origin objects point to parent self.assertEqual(id(self.copied.orm), id(self.graph.orm)) self.assertEqual(id(self.copied.origin), id(self.graph.origin)) else: # Internal node and edge stores point to parent. self.assertNotEqual(id(self.copied.nodes._storage), id(self.graph.nodes._storage)) self.assertNotEqual(id(self.copied.edges._storage), id(self.graph.edges._storage)) # ORM and origin objects point to parent self.assertNotEqual(id(self.copied.orm), id(self.graph.orm)) self.assertNotEqual(id(self.copied.origin), id(self.graph.origin)) def test_graph_copy_shallow(self): """ Test making a shallow copy of a graph. This essentially copies the Graph object while linking tot the data store in the parent Graph """ self.shallow = True self.copied = self.graph.copy(deep=False) def test_graph_copy_deep(self): """ Test making a deep copy of a graph (default) copying everything """ self.shallow = False self.copied = self.graph.copy() def test_graph_buildin_copy_shallow(self): """ Test making a shallow copy of a graph using the 'copy' method of the copy class. This calls the Graph.copy method """ self.shallow = True self.copied = copy.copy(self.graph) def test_graph_buildin_copy_deep(self): """ Test making a deep copy of a graph using the 'deepcopy' method of the copy class. This calls the Graph.copy method """ self.shallow = False self.copied = copy.deepcopy(self.graph) def test_graph_buildin_copy_deep_view(self): """ Test copying subgraphs either with the set 'view' only or the full origin graph (full graph) """ # Regular copy self.shallow = False self.copied = copy.deepcopy(self.graph) # Build subgraph, same origin view = self.graph.getnodes([3, 4, 5]) self.assertEqual(id(view.origin), id(self.graph.origin)) # Deep copy with or without view, different origin copy_view = view.copy(deep=True, copy_view=False) copy_full = view.copy(deep=True, copy_view=True) self.assertNotEqual(id(copy_view.origin), id(self.graph.origin)) self.assertNotEqual(id(copy_full.origin), id(self.graph.origin)) # Subgraph 'view' should be identical to the original # regardless the copy mode self.assertEqual(copy_view.nodes.keys(), view.nodes.keys()) self.assertEqual(copy_view.edges.keys(), view.edges.keys()) self.assertEqual(copy_view.adjacency.keys(), view.adjacency.keys()) self.assertEqual(copy_full.nodes.keys(), view.nodes.keys()) self.assertEqual(copy_full.edges.keys(), view.edges.keys()) self.assertEqual(copy_full.adjacency.keys(), view.adjacency.keys()) # The view copy origin should either be identical to the view # (copy_view = True) or to the full graph (copy_view = False) self.assertEqual(list(copy_view.nodes._storage.keys()), list(view.nodes.keys())) self.assertEqual(list(copy_full.nodes._storage.keys()), list(view.origin.nodes.keys())) # The copy_full has its origin equals self and thus copy_full.origin.nodes # equals copy_full.nodes. However, the view is also set which means that # by default the full graph is not accessible without resetting it copy_full.nodes.reset_view() self.assertEqual(copy_full.nodes.keys(), self.graph.nodes.keys())
class TestGraphIteration(UnittestPythonCompatibility): """ Test methods for iteration over nodes and edges in a graph """ def setUp(self): """ Build default Graph with node and edge attributes """ self.graph = Graph() self.graph.add_nodes([('g', {'weight': 1.0, 'value': 'gr'}), ('r', {'weight': 1.5, 'value': 'ra'}), ('a', {'weight': 2.0, 'value': 'ap'}), ('p', {'weight': 2.5, 'value': 'ph'}), ('h', {'weight': 3.0})]) self.graph.add_edges([(1, 2), (2, 3), (3, 4), (3, 5), (4, 5)], value=True, weight=43.2, key='edge') def test_magic_method_eq(self): """ Test Graph equality __eq__ (==) test """ self.assertTrue(self.graph == self.graph) self.assertTrue(self.graph.getnodes([1, 3, 4]) == self.graph.getnodes([1, 3, 4])) self.assertTrue(self.graph == self.graph.copy(deep=True)) self.assertFalse(self.graph.getnodes([1, 2]) == self.graph.getnodes([2, 3])) self.assertFalse(self.graph.getedges([(1, 2), (2, 3)]) == self.graph.getnodes([2, 3])) def test_magic_method_add(self): """ Test Graph addition __add__ (+) support """ # Adding self to self does not change anything self.assertEqual(self.graph + self.graph, self.graph) # Adding sub graphs together to yield the full graph only works if # there is an overlap in the graphs connecting them together. Without # the overlap the connecting edges are lost sub1 = self.graph.getnodes([1, 2, 3]) sub2 = self.graph.getnodes([3, 4, 5]) self.assertEqual(sub1 + sub2, self.graph) sub1 = self.graph.getnodes([1, 2, 3]) sub2 = self.graph.getnodes([4, 5]) self.assertNotEqual(sub1 + sub2, self.graph) # Sub graphs are still views on the origin combined = sub1 + sub2 self.assertTrue(combined.nodes.is_view) self.assertTrue(combined.edges.is_view) self.assertEqual(id(combined.origin), id(self.graph.origin)) # Adding graphs together that do not share a common origin sub1_copy = sub1.copy() combined = sub1_copy + sub2 self.assertFalse(combined.nodes.is_view) self.assertFalse(combined.edges.is_view) self.assertNotEqual(id(combined.origin), id(self.graph.origin)) def test_magic_method_iadd(self): """ Test Graph in place addition __iadd__ (+=) support """ # Adding sub graphs together to yield the full graph only works if # there is an overlap in the graphs connecting them together. Without # the overlap the connecting edges are lost sub1 = self.graph.getnodes([1, 2, 3]) sub2 = self.graph.getnodes([3, 4, 5]) sub1 += sub2 self.assertEqual(sub1, self.graph) sub1 = self.graph.getnodes([1, 2, 3]) sub2 = self.graph.getnodes([4, 5]) sub1 += sub2 self.assertNotEqual(sub1, self.graph) # Sub graphs are still views on the origin self.assertTrue(sub1.nodes.is_view) self.assertTrue(sub1.edges.is_view) self.assertEqual(id(sub1.origin), id(self.graph.origin)) # Adding graphs together that do not share a common origin sub1_copy = sub1.copy() sub1_copy += sub2 self.assertFalse(sub1_copy.nodes.is_view) self.assertFalse(sub1_copy.edges.is_view) self.assertNotEqual(id(sub1_copy.origin), id(self.graph.origin)) def test_magic_method_contains(self): """ Test Graph contains __contains__ test """ # Equal graphs also contain each other self.assertTrue(self.graph in self.graph) sub1 = self.graph.getnodes([1, 2, 3]) sub2 = self.graph.getnodes([4, 5]) self.assertTrue(sub1 in self.graph) self.assertFalse(sub2 in sub1) def test_magic_method_getitem(self): """ Test Graph dictionary style __getitem__ item lookup """ self.assertEqual(self.graph[2], self.graph.getnodes(2)) self.assertEqual(self.graph[(2, 3)], self.graph.getedges((2, 3))) # Support for slicing self.assertEqual(self.graph[2:], self.graph.getnodes([2, 3, 4, 5])) self.assertEqual(self.graph[2:4], self.graph.getnodes([2, 3])) self.assertEqual(self.graph[1:5:2], self.graph.getnodes([1, 3])) self.assertTrue(self.graph[1:-1].empty()) def test_magic_method_ge(self): """ Test Graph greater-then or equal __ge__ (>=) to support """ sub = self.graph.getnodes([2, 3, 4]) self.assertTrue(self.graph >= sub) self.assertFalse(sub >= self.graph) self.assertTrue(sub >= sub) def test_magic_method_gt(self): """ Test Graph greater-then __gt__ (>) support """ sub = self.graph.getnodes([2, 3, 4]) self.assertTrue(self.graph > sub) self.assertFalse(sub > self.graph) def test_magic_method_len(self): """ Test Graph length __len__ support """ self.assertEqual(len(self.graph), 5) def test_magic_method_le(self): """ Test Graph less-then or equal __le__ (<=) to support """ sub = self.graph.getnodes([2, 3, 4]) self.assertTrue(sub <= self.graph) self.assertFalse(self.graph <= sub) self.assertTrue(sub <= sub) def test_magic_method_lt(self): """ Test Graph greater-then __lt__ (<) support """ sub = self.graph.getnodes([2, 3, 4]) self.assertTrue(sub < self.graph) self.assertFalse(self.graph < sub) def test_magic_method_ne(self): """ Test Graph equality __ne__ (!=) test """ self.assertFalse(self.graph != self.graph) self.assertFalse(self.graph.getnodes([1, 3, 4]) != self.graph.getnodes([1, 3, 4])) self.assertFalse(self.graph != self.graph.copy(deep=True)) self.assertTrue(self.graph.getnodes([1, 2]) != self.graph.getnodes([2, 3])) self.assertTrue(self.graph.getedges([(1, 2), (2, 3)]) != self.graph.getnodes([2, 3])) def test_magic_method_sub(self): """ Test Graph subtract __sub__ (-) support """ sub = self.graph.getnodes([2, 3, 4]) self.assertEqual(self.graph - sub, self.graph.getnodes([1,5])) self.assertTrue(len(sub - self.graph) == 0) def test_magic_method_isub(self): """ Test Graph inplace subtract __isub__ (-=) support """ cp = self.graph.copy() self.graph -= self.graph.getnodes([2, 3, 4]) self.assertEqual(self.graph, cp.getnodes([1,5]))
class TestGraphNodeAttribute(UnittestPythonCompatibility): """ Test methods to get and set node attributes using a node storage driver. `DictStorage` is the default driver tested here. """ def setUp(self): """ Build default Graph with node and edge attributes """ self.graph = Graph() self.graph.add_nodes([('g', {'weight': 1.0, 'value': 'gr'}), ('r', {'weight': 1.5, 'value': 'ra'}), ('a', {'weight': 2.0, 'value': 'ap'}), ('p', {'weight': 2.5, 'value': 'ph'}), ('h', {'weight': 3.0})]) self.graph.add_edges([(1, 2), (2, 3), (3, 4), (3, 5), (4, 5)], value=True, weight=43.2, key='edge') def test_graph_node_attr_storeget(self): """ Test getting node attributes directly from the `nodes` storage """ self.assertEqual(self.graph.nodes[1]['weight'], 1.0) self.assertEqual(self.graph.nodes[3]['value'], 'ap') def test_graph_node_attr_storeset(self): """ Test setting node attributes directly from the `nodes` storage """ self.graph.nodes[1]['weight'] = 5.0 self.graph.nodes[3]['value'] = 'dd' self.assertEqual(self.graph.nodes[1]['weight'], 5.0) self.assertEqual(self.graph.nodes[3]['value'], 'dd') def test_graph_node_attr_key_tag(self): """ Test get attributes based on `key_tag` """ self.assertEqual(self.graph.nodes[1][self.graph.data.key_tag], 'g') self.assertEqual(self.graph.nodes[3][self.graph.data.key_tag], 'a') self.assertEqual(self.graph.get(1), 'g') # uses default node data tag def test_graph_node_attr_value_tag(self): """ Test get attributes based on `value_tag` """ self.assertEqual(self.graph.nodes[1][self.graph.data.value_tag], 'gr') self.assertEqual(self.graph.nodes[3][self.graph.data.value_tag], 'ap') def test_graph_node_attr_dict(self): """ Test if the returned full attribute dictionary is of expected format """ self.assertDictEqual(self.graph.nodes[1], {'_id': 1, 'key': 'g', 'weight': 1.0, 'value': 'gr'}) self.assertDictEqual(self.graph.nodes[3], {'_id': 3, 'key': 'a', 'weight': 2.0, 'value': 'ap'}) def test_graph_node_attr_exception(self): """ Test `nodes` exception if node not present """ self.assertRaises(GraphitException, self.graph.__getitem__, 10) self.assertIsNone(self.graph.nodes.get(10)) def test_graph_node_attr_graphget(self): """ Test access node attributes by nid using the (sub)graph 'get' method """ self.assertEqual(self.graph.get(4), 'p') self.assertEqual(self.graph.get(4, 'weight'), 2.5) # Key does not exist self.assertIsNone(self.graph.get(4, key='no_key')) # Key does not exist return defaultkey self.assertEqual(self.graph.get(4, key='no_key', defaultattr='weight'), 2.5) def test_graph_node_attr_singlenode_get(self): """ Test getting node attribute values directly using the single node Graph API which has the required methods (node_tools) added to it. """ node = self.graph.getnodes(5) self.assertEqual(node['key'], 'h') self.assertEqual(node.key, 'h') self.assertEqual(node.get('key'), 'h') self.assertEqual(node['weight'], 3.0) self.assertEqual(node.weight, 3.0) self.assertEqual(node.get('weight'), 3.0) def test_graph_node_attr_singlenode_set(self): """ Test setting node attribute values directly using the single node Graph API which has the required methods (node_tools) added to it. """ node = self.graph.getnodes(5) node.weight = 5.0 node['key'] = 'z' node.set('value', True) self.assertEqual(node.nodes[5]['weight'], 5.0) self.assertEqual(node.nodes[5]['key'], 'z') self.assertEqual(node.nodes[5]['value'], True) def test_graph_node_attr_singlenode_exception(self): """ Test exceptions in direct access to node attributes in a single graph class """ node = self.graph.getnodes(5) self.assertEqual(node.get(), None) # Default get returns value, not set self.assertRaises(KeyError, node.__getitem__, 'no_key') self.assertRaises(AttributeError, node.__getattr__, 'no_key') def test_graph_nodes_dict_keys(self): """ Test graph dict-like 'keys' support. """ self.assertListEqual(self.graph.keys(), ['g', 'r', 'a', 'p', 'h']) self.assertListEqual(self.graph.keys('weight'), [1.0, 1.5, 2.0, 2.5, 3.0]) def test_graph_nodes_dict_values(self): """ Test graph dict-like 'values' support. """ self.assertListEqual(self.graph.values(), ['gr', 'ra', 'ap', 'ph', None]) self.assertItemsEqual(self.graph.values('no_value'), [None, None, None, None, None]) def test_graph_nodes_dict_items(self): """ Test graph dict-like 'items' support. """ self.assertItemsEqual(self.graph.items(), [('g', 'gr'), ('r', 'ra'), ('a', 'ap'), ('p', 'ph'), ('h', None)]) self.assertItemsEqual(self.graph.items(valuestring='_id'), [('g', 1), ('r', 2), ('a', 3), ('p', 4), ('h', 5)]) self.assertItemsEqual(self.graph.items(keystring='_id', valuestring='weight'), [(1, 1.0), (2, 1.5), (3, 2.0), (4, 2.5), (5, 3.0)])
class TestGraphAddNodeConnected(UnittestPythonCompatibility): """ Test add_connect method for direct addition and edge connection of a new node to an existing node using a single node object """ currpath = os.path.dirname(__file__) def setUp(self): """ Build empty graph to add a node to and test default state """ self.graph = Graph(auto_nid=False) # Add two nodes self.graph.add_nodes(('one', 'two')) self.graph.add_edge('one', 'two') # Two nodes and one edge self.assertTrue(len(self.graph) == 2) self.assertTrue(len(self.graph.nodes) == 2) self.assertTrue(len(self.graph.edges) == 2) self.assertTrue(len(self.graph.adjacency) == 2) # auto_nid self.assertFalse(self.graph.data.auto_nid) def tearDown(self): """ Test state after node addition """ nids = sorted(self.graph.nodes) # The nid should equal the node self.assertTrue(self.node in nids) # The _id is still set self.assertEqual(self.graph.nodes[self.node]['_id'], 3) self.assertEqual(self.graph.data.nodeid, 4) # filled after addition self.assertTrue(len(self.graph) == 3) self.assertTrue(len(self.graph.nodes) == 3) self.assertTrue(len(self.graph.edges) == 4) self.assertTrue(len(self.graph.adjacency) == 3) # no adjacency self.assertTrue(len(self.graph.adjacency[nids[0]]) == 1) def test_add_connect_string(self): """ Test add connect a string node """ self.node = 'three' node = self.graph.getnodes('two') node.add_connect(self.node) self.assertTrue(('two', 'three') in self.graph.edges) self.assertTrue(('three', 'two') in self.graph.edges) def test_add_connect_attr(self): """ Test add connect a node with node and edge attributes """ self.node = 'three' node = self.graph.getnodes('two') node.add_connect(self.node, node_kwargs={ 'arg': True, 'n': 1.22 }, edge_kwargs={ 'arg': True, 'e': 5.44 }) # Node should contain keyword arguments self.assertTrue(self.graph.nodes[self.node]['arg']) self.assertTrue(self.graph.nodes[self.node]['n'] == 1.22) # Edges should contain keyword arguments edge = self.graph.getedges(('two', self.node)) self.assertTrue(all(e.get('arg', False) for e in edge.edges.values())) self.assertTrue(all(e.get('e') == 5.44 for e in edge.edges.values()))