Skip to content

Commit 242dbc9

Browse files
authoredApr 6, 2021
R 2.0.5 (#21)
* has and rhas * test and read me update * read me update * read me update
1 parent 6bd3ed7 commit 242dbc9

File tree

4 files changed

+146
-32
lines changed

4 files changed

+146
-32
lines changed
 

‎README.md

+24-12
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33

44
# Cog - Embedded Graph Database for Python
55
# ![ScreenShot](/cog-logo.png)
6+
> [cogdb.io](https://cogdb.io)
67
7-
> New release: 2.0.4,
8-
> - Graph visualizations!
9-
> - bug fixes
8+
> New release: 2.0.5!
109
1110
![ScreenShot](docs/ex2.png)
1211

@@ -55,14 +54,14 @@ g.load_csv('test/test-data/books.csv', "isbn")
5554

5655
### Torque query examples
5756

58-
### Scan vertices
57+
#### Scan vertices
5958
```python
6059
g.scan(3)
6160
```
6261

6362
> {'result': [{'id': 'bob'}, {'id': 'emily'}, {'id': 'charlie'}]}
6463
65-
### Scan edges
64+
#### Scan edges
6665
```python
6766
g.scan(3, 'e')
6867
```
@@ -74,7 +73,20 @@ g.v("bob").out().all()
7473
```
7574
> {'result': [{'id': 'cool_person'}, {'id': 'fred'}]}
7675
77-
### starting from a vertex, follow all outgoing edges and count vertices
76+
#### Everyone with status 'cool_person'
77+
```python
78+
g.v().has("status", 'cool_person').all()
79+
```
80+
81+
> {'result': [{'id': 'bob'}, {'id': 'dani'}, {'id': 'greg'}]}
82+
83+
#### Include edges in the results
84+
```python
85+
g.v().has("follows", "fred").inc().all('e')
86+
```
87+
> {'result': [{'id': 'dani', 'edges': ['follows']}, {'id': 'charlie', 'edges': ['follows']}, {'id': 'alice', 'edges': ['follows']}]}
88+
89+
#### starting from a vertex, follow all outgoing edges and count vertices
7890
```python
7991
g.v("bob").out().count()
8092
```
@@ -96,26 +108,26 @@ g.v().tag("from").out("follows").tag("to").view("follows").url
96108
```
97109
> file:///Path/to/your/cog_home/views/follows.html
98110
99-
### List all views
111+
#### List all views
100112
```
101113
g.lsv()
102114
```
103115
> ['follows']
104116
105-
### Load existing visualization
117+
#### Load existing visualization
106118
```
107119
g.getv('follows').render()
108120
```
109121

110-
### starting from a vertex, follow all out going edges and tag them
122+
#### starting from a vertex, follow all out going edges and tag them
111123

112124
```python
113125
g.v("bob").out().tag("from").out().tag("to").all()
114126
```
115127
> {'result': [{'from': 'fred', 'id': 'greg', 'to': 'greg'}]}
116128
>
117129
118-
### starting from a vertex, follow all incoming edges and list all vertices
130+
#### starting from a vertex, follow all incoming edges and list all vertices
119131
```python
120132
g.v("bob").inc().all()
121133
```
@@ -124,14 +136,14 @@ g.v("bob").inc().all()
124136

125137
## Loading data from a file
126138

127-
### Triples file
139+
#### Triples file
128140
```python
129141
from cog.torque import Graph
130142
g = Graph(graph_name="people")
131143
g.load_triples("/path/to/triples.nt", "people")
132144
```
133145

134-
### Edgelist file
146+
#### Edgelist file
135147
```python
136148
from cog.torque import Graph
137149
g = Graph(graph_name="people")

‎cog/torque.py

+91-18
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ class Vertex(object):
1616
def __init__(self, _id):
1717
self.id = _id
1818
self.tags = {}
19-
self.edge = None
19+
self.edges = set()
2020

2121
def set_edge(self, edge):
22-
self.edge = edge
22+
self.edges.add(edge)
2323
return self
2424

2525
def get_dict(self):
@@ -30,8 +30,7 @@ def __str__(self):
3030

3131
class Graph:
3232
"""
33-
https://www.w3.org/TR/WD-rdf-syntax-971002/
34-
https://github.com/cayleygraph/cayley/blob/master/docs/GizmoAPI.md
33+
Creates a graph object.
3534
"""
3635

3736
def __init__(self, graph_name, cog_home="cog_home", cog_path_prefix=None):
@@ -78,7 +77,7 @@ def load_triples(self, graph_data_path, graph_name=None):
7877

7978
def load_csv(self, csv_path, id_column_name, graph_name=None):
8079
"""
81-
Loads CSV to a graph. One column must be designated as ID column
80+
Loads CSV to a graph. One column must be designated as ID column.
8281
:param csv_path:
8382
:param id_column_name:
8483
:param graph_name:
@@ -101,7 +100,6 @@ def put(self, vertex1, predicate, vertex2):
101100
return self
102101

103102
def v(self, vertex=None):
104-
#TODO: need to check if node exists
105103
if vertex is not None:
106104
self.last_visited_vertices = [Vertex(vertex)]
107105
else:
@@ -113,14 +111,14 @@ def v(self, vertex=None):
113111

114112
def out(self, predicates=None):
115113
'''
116-
List of string predicates
117-
:param predicates:
114+
Traverse forward through edges.
115+
:param predicates: A string or a List of strings.
118116
:return:
119117
'''
120118
if predicates is not None:
121119
if not isinstance(predicates, list):
122120
predicates = [predicates]
123-
predicates = map(hash_predicate, predicates)
121+
predicates = list(map(hash_predicate, predicates))
124122
else:
125123
predicates = self.all_predicates
126124

@@ -129,21 +127,94 @@ def out(self, predicates=None):
129127
return self
130128

131129
def inc(self, predicates=None):
130+
'''
131+
Traverse backward through edges.
132+
:param predicates:
133+
:return:
134+
'''
132135
if predicates is not None:
133136
if not isinstance(predicates, list):
134137
predicates = [predicates]
135-
predicates = map(hash_predicate, predicates)
138+
predicates = list(map(hash_predicate, predicates))
136139
else:
137140
predicates = self.all_predicates
138141

139142
self.__hop("in", predicates)
140143
return self
141144

142-
def has(self, predicate, object):
143-
pass
145+
def __adjacent_vertices(self, vertex, predicates, direction='out'):
146+
self.cog.use_namespace(self.graph_name)
147+
adjacent_vertices = []
148+
for predicate in predicates:
149+
if direction == 'out':
150+
out_record = self.cog.use_table(predicate).get(out_nodes(vertex.id))
151+
if not out_record.is_empty():
152+
for v_adj in out_record.value:
153+
adjacent_vertices.append(Vertex(v_adj).set_edge(predicate))
154+
elif direction == 'in':
155+
in_record = self.cog.use_table(predicate).get(in_nodes(vertex.id))
156+
if not in_record.is_empty():
157+
for v_adj in in_record.value:
158+
adjacent_vertices.append(Vertex(v_adj).set_edge(predicate))
159+
160+
return adjacent_vertices
161+
162+
def has(self, predicates, vertex):
163+
"""
164+
Filters all outgoing edges from a vertex that matches a list of predicates.
165+
:param predicates:
166+
:param vertex:
167+
:return:
168+
"""
169+
170+
if predicates is not None:
171+
if not isinstance(predicates, list):
172+
predicates = [predicates]
173+
predicates = list(map(hash_predicate, predicates))
174+
175+
has_vertices = []
176+
for lv in self.last_visited_vertices:
177+
adj_vertices = self.__adjacent_vertices(lv, predicates)
178+
# print(lv.id + " -> " + str([x.id for x in adj_vertices]))
179+
for av in adj_vertices:
180+
if av.id == vertex:
181+
has_vertices.append(lv)
182+
183+
self.last_visited_vertices = has_vertices
184+
return self
185+
186+
def hasr(self, predicates, vertex):
187+
"""
188+
'Has' in reverse. Filters all incoming edges from a vertex that matches a list of predicates.
189+
:param predicates:
190+
:param vertex:
191+
:return:
192+
"""
193+
194+
if predicates is not None:
195+
if not isinstance(predicates, list):
196+
predicates = [predicates]
197+
predicates = list(map(hash_predicate, predicates))
198+
199+
has_vertices = []
200+
for lv in self.last_visited_vertices:
201+
adj_vertices = self.__adjacent_vertices(lv, predicates, 'in')
202+
# print(lv.id + " -> " + str([x.id for x in adj_vertices]))
203+
for av in adj_vertices:
204+
if av.id == vertex:
205+
has_vertices.append(lv)
206+
207+
self.last_visited_vertices = has_vertices
208+
return self
144209

145210

146211
def scan(self, limit=10, scan_type='v'):
212+
'''
213+
Scans vertices or edges in a graph.
214+
:param limit:
215+
:param scan_type:
216+
:return:
217+
'''
147218
assert type(scan_type) is str, "Scan type must be either 'v' for vertices or 'e' for edges."
148219
if scan_type == 'e':
149220
self.cog.use_namespace(self.graph_name).use_table(self.config.GRAPH_EDGE_SET_TABLE_NAME)
@@ -183,8 +254,7 @@ def __hop(self, direction, predicates=None, tag=NOTAG):
183254

184255
def tag(self, tag_name):
185256
'''
186-
Saves nodes with a tag name and returned in the result set.
187-
Primarily used to capture nodes while navigating the graph.
257+
Saves vertices with a tag name. Used to capture vertices while traversing a graph.
188258
:param tag_name:
189259
:return:
190260
'''
@@ -195,15 +265,18 @@ def tag(self, tag_name):
195265
def count(self):
196266
return len(self.last_visited_vertices)
197267

198-
def all(self):
268+
def all(self, options=None):
199269
"""
200-
returns all the nodes in the result.
270+
Returns all the vertices that are resultant of the graph query. Options 'e' would include the edges that were traversed.
201271
https://github.com/cayleygraph/cayley/blob/master/docs/GizmoAPI.md
202272
:return:
203273
"""
204274
result = []
275+
show_edge = True if options is not None and 'e' in options else False
205276
for v in self.last_visited_vertices:
206-
item = {"id":v.id}
277+
item = {"id": v.id}
278+
if show_edge and v.edges:
279+
item['edges'] = [self.cog.use_namespace(self.graph_name).use_table(self.config.GRAPH_EDGE_SET_TABLE_NAME).get(edge).value for edge in v.edges]
207280
# item['edge'] = self.cog.use_namespace(self.graph_name).use_table(self.config.GRAPH_EDGE_SET_TABLE_NAME).get(item['edge']).value
208281
item.update(v.tags)
209282

@@ -213,7 +286,7 @@ def all(self):
213286

214287
def view(self, view_name, js_src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.js"):
215288
"""
216-
Returns html view of the graph
289+
Returns html view of the resulting graph from a query.
217290
:return:
218291
"""
219292
assert view_name is not None, "a view name is required to create a view, it can be any string."

‎setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from setuptools import setup
22

33
setup(name='cogdb',
4-
version='2.0.4',
4+
version='2.0.5',
55
description='Persistent Embedded Graph Database',
66
url='http://github.com/arun1729/cog',
77
author='Arun Mahendra',

‎test/test_torque.py

+30-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ def test_torque_6(self):
8484
def test_torque_7(self):
8585
expected = {'result': [{'source': '<greg>', 'id': '"cool_person"', 'target': '"cool_person"'}]}
8686
actual = TorqueTest.g.v("<fred>").out().tag("source").out().tag("target").all()
87-
print(ordered(actual))
8887
self.assertTrue(ordered(expected) == ordered(actual))
8988

9089
def test_torque_8(self):
@@ -133,6 +132,36 @@ def test_torque_15(self):
133132
view = TorqueTest.g.v("<dani>").tag("from").out().tag("to").view("dani_view")
134133
self.assertEqual(['bob_view', 'dani_view'], sorted(TorqueTest.g.lsv()))
135134

135+
def test_torque_16(self):
136+
expected = {'result': [{'id': '<bob>'}]}
137+
actual = TorqueTest.g.v("<charlie>").out("<follows>").has("<follows>", "<fred>").all()
138+
self.assertTrue(expected == actual)
139+
140+
def test_torque_17(self):
141+
expected = {'result': [{'id': '<dani>'}, {'id': '<alice>'}, {'id': '<charlie>'}]}
142+
actual = TorqueTest.g.v().has("<follows>", "<bob>").all()
143+
self.assertTrue(expected == actual)
144+
145+
def test_torque_18(self):
146+
expected = {'result': [{'id': '<bob>'}, {'id': '<dani>'}, {'id': '<greg>'}]}
147+
actual = TorqueTest.g.v().has("<status>", '"cool_person"').all()
148+
self.assertTrue(expected == actual)
149+
150+
def test_torque_19(self):
151+
expected = {'result': [{'id': '<fred>', 'edges': ['<follows>']}, {'id': '"cool_person"', 'edges': ['<status>']}]}
152+
actual = TorqueTest.g.v("<bob>").out().all('e')
153+
self.assertTrue(ordered(expected) == ordered(actual))
154+
155+
def test_torque_20(self):
156+
expected = {'result': [{'id': '<bob>'}, {'id': '<emily>'}]}
157+
actual = TorqueTest.g.v().has("<follows>", "<fred>").all('e')
158+
self.assertTrue(expected == actual)
159+
160+
def test_torque_21(self):
161+
expected = {'result': [{'id': '<dani>', 'edges': ['<follows>']}, {'id': '<charlie>', 'edges': ['<follows>']}, {'id': '<alice>', 'edges': ['<follows>']}]}
162+
actual = TorqueTest.g.v().has("<follows>", "<fred>").inc().all('e')
163+
self.assertTrue(expected == actual)
164+
136165
@classmethod
137166
def tearDownClass(cls):
138167
TorqueTest.g.close()

0 commit comments

Comments
 (0)
Please sign in to comment.