Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

R 2.0.5 #21

Merged
merged 4 commits into from
Apr 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@

# Cog - Embedded Graph Database for Python
# ![ScreenShot](/cog-logo.png)
> [cogdb.io](https://cogdb.io)

> New release: 2.0.4,
> - Graph visualizations!
> - bug fixes
> New release: 2.0.5!

![ScreenShot](docs/ex2.png)

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

### Torque query examples

### Scan vertices
#### Scan vertices
```python
g.scan(3)
```

> {'result': [{'id': 'bob'}, {'id': 'emily'}, {'id': 'charlie'}]}

### Scan edges
#### Scan edges
```python
g.scan(3, 'e')
```
Expand All @@ -74,7 +73,20 @@ g.v("bob").out().all()
```
> {'result': [{'id': 'cool_person'}, {'id': 'fred'}]}

### starting from a vertex, follow all outgoing edges and count vertices
#### Everyone with status 'cool_person'
```python
g.v().has("status", 'cool_person').all()
```

> {'result': [{'id': 'bob'}, {'id': 'dani'}, {'id': 'greg'}]}

#### Include edges in the results
```python
g.v().has("follows", "fred").inc().all('e')
```
> {'result': [{'id': 'dani', 'edges': ['follows']}, {'id': 'charlie', 'edges': ['follows']}, {'id': 'alice', 'edges': ['follows']}]}

#### starting from a vertex, follow all outgoing edges and count vertices
```python
g.v("bob").out().count()
```
Expand All @@ -96,26 +108,26 @@ g.v().tag("from").out("follows").tag("to").view("follows").url
```
> file:///Path/to/your/cog_home/views/follows.html

### List all views
#### List all views
```
g.lsv()
```
> ['follows']

### Load existing visualization
#### Load existing visualization
```
g.getv('follows').render()
```

### starting from a vertex, follow all out going edges and tag them
#### starting from a vertex, follow all out going edges and tag them

```python
g.v("bob").out().tag("from").out().tag("to").all()
```
> {'result': [{'from': 'fred', 'id': 'greg', 'to': 'greg'}]}
>

### starting from a vertex, follow all incoming edges and list all vertices
#### starting from a vertex, follow all incoming edges and list all vertices
```python
g.v("bob").inc().all()
```
Expand All @@ -124,14 +136,14 @@ g.v("bob").inc().all()

## Loading data from a file

### Triples file
#### Triples file
```python
from cog.torque import Graph
g = Graph(graph_name="people")
g.load_triples("/path/to/triples.nt", "people")
```

### Edgelist file
#### Edgelist file
```python
from cog.torque import Graph
g = Graph(graph_name="people")
Expand Down
109 changes: 91 additions & 18 deletions cog/torque.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ class Vertex(object):
def __init__(self, _id):
self.id = _id
self.tags = {}
self.edge = None
self.edges = set()

def set_edge(self, edge):
self.edge = edge
self.edges.add(edge)
return self

def get_dict(self):
Expand All @@ -30,8 +30,7 @@ def __str__(self):

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

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

def load_csv(self, csv_path, id_column_name, graph_name=None):
"""
Loads CSV to a graph. One column must be designated as ID column
Loads CSV to a graph. One column must be designated as ID column.
:param csv_path:
:param id_column_name:
:param graph_name:
Expand All @@ -101,7 +100,6 @@ def put(self, vertex1, predicate, vertex2):
return self

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

def out(self, predicates=None):
'''
List of string predicates
:param predicates:
Traverse forward through edges.
:param predicates: A string or a List of strings.
:return:
'''
if predicates is not None:
if not isinstance(predicates, list):
predicates = [predicates]
predicates = map(hash_predicate, predicates)
predicates = list(map(hash_predicate, predicates))
else:
predicates = self.all_predicates

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

def inc(self, predicates=None):
'''
Traverse backward through edges.
:param predicates:
:return:
'''
if predicates is not None:
if not isinstance(predicates, list):
predicates = [predicates]
predicates = map(hash_predicate, predicates)
predicates = list(map(hash_predicate, predicates))
else:
predicates = self.all_predicates

self.__hop("in", predicates)
return self

def has(self, predicate, object):
pass
def __adjacent_vertices(self, vertex, predicates, direction='out'):
self.cog.use_namespace(self.graph_name)
adjacent_vertices = []
for predicate in predicates:
if direction == 'out':
out_record = self.cog.use_table(predicate).get(out_nodes(vertex.id))
if not out_record.is_empty():
for v_adj in out_record.value:
adjacent_vertices.append(Vertex(v_adj).set_edge(predicate))
elif direction == 'in':
in_record = self.cog.use_table(predicate).get(in_nodes(vertex.id))
if not in_record.is_empty():
for v_adj in in_record.value:
adjacent_vertices.append(Vertex(v_adj).set_edge(predicate))

return adjacent_vertices

def has(self, predicates, vertex):
"""
Filters all outgoing edges from a vertex that matches a list of predicates.
:param predicates:
:param vertex:
:return:
"""

if predicates is not None:
if not isinstance(predicates, list):
predicates = [predicates]
predicates = list(map(hash_predicate, predicates))

has_vertices = []
for lv in self.last_visited_vertices:
adj_vertices = self.__adjacent_vertices(lv, predicates)
# print(lv.id + " -> " + str([x.id for x in adj_vertices]))
for av in adj_vertices:
if av.id == vertex:
has_vertices.append(lv)

self.last_visited_vertices = has_vertices
return self

def hasr(self, predicates, vertex):
"""
'Has' in reverse. Filters all incoming edges from a vertex that matches a list of predicates.
:param predicates:
:param vertex:
:return:
"""

if predicates is not None:
if not isinstance(predicates, list):
predicates = [predicates]
predicates = list(map(hash_predicate, predicates))

has_vertices = []
for lv in self.last_visited_vertices:
adj_vertices = self.__adjacent_vertices(lv, predicates, 'in')
# print(lv.id + " -> " + str([x.id for x in adj_vertices]))
for av in adj_vertices:
if av.id == vertex:
has_vertices.append(lv)

self.last_visited_vertices = has_vertices
return self


def scan(self, limit=10, scan_type='v'):
'''
Scans vertices or edges in a graph.
:param limit:
:param scan_type:
:return:
'''
assert type(scan_type) is str, "Scan type must be either 'v' for vertices or 'e' for edges."
if scan_type == 'e':
self.cog.use_namespace(self.graph_name).use_table(self.config.GRAPH_EDGE_SET_TABLE_NAME)
Expand Down Expand Up @@ -183,8 +254,7 @@ def __hop(self, direction, predicates=None, tag=NOTAG):

def tag(self, tag_name):
'''
Saves nodes with a tag name and returned in the result set.
Primarily used to capture nodes while navigating the graph.
Saves vertices with a tag name. Used to capture vertices while traversing a graph.
:param tag_name:
:return:
'''
Expand All @@ -195,15 +265,18 @@ def tag(self, tag_name):
def count(self):
return len(self.last_visited_vertices)

def all(self):
def all(self, options=None):
"""
returns all the nodes in the result.
Returns all the vertices that are resultant of the graph query. Options 'e' would include the edges that were traversed.
https://github.com/cayleygraph/cayley/blob/master/docs/GizmoAPI.md
:return:
"""
result = []
show_edge = True if options is not None and 'e' in options else False
for v in self.last_visited_vertices:
item = {"id":v.id}
item = {"id": v.id}
if show_edge and v.edges:
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]
# item['edge'] = self.cog.use_namespace(self.graph_name).use_table(self.config.GRAPH_EDGE_SET_TABLE_NAME).get(item['edge']).value
item.update(v.tags)

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

def view(self, view_name, js_src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.js"):
"""
Returns html view of the graph
Returns html view of the resulting graph from a query.
:return:
"""
assert view_name is not None, "a view name is required to create a view, it can be any string."
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from setuptools import setup

setup(name='cogdb',
version='2.0.4',
version='2.0.5',
description='Persistent Embedded Graph Database',
url='http://github.com/arun1729/cog',
author='Arun Mahendra',
Expand Down
31 changes: 30 additions & 1 deletion test/test_torque.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ def test_torque_6(self):
def test_torque_7(self):
expected = {'result': [{'source': '<greg>', 'id': '"cool_person"', 'target': '"cool_person"'}]}
actual = TorqueTest.g.v("<fred>").out().tag("source").out().tag("target").all()
print(ordered(actual))
self.assertTrue(ordered(expected) == ordered(actual))

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

def test_torque_16(self):
expected = {'result': [{'id': '<bob>'}]}
actual = TorqueTest.g.v("<charlie>").out("<follows>").has("<follows>", "<fred>").all()
self.assertTrue(expected == actual)

def test_torque_17(self):
expected = {'result': [{'id': '<dani>'}, {'id': '<alice>'}, {'id': '<charlie>'}]}
actual = TorqueTest.g.v().has("<follows>", "<bob>").all()
self.assertTrue(expected == actual)

def test_torque_18(self):
expected = {'result': [{'id': '<bob>'}, {'id': '<dani>'}, {'id': '<greg>'}]}
actual = TorqueTest.g.v().has("<status>", '"cool_person"').all()
self.assertTrue(expected == actual)

def test_torque_19(self):
expected = {'result': [{'id': '<fred>', 'edges': ['<follows>']}, {'id': '"cool_person"', 'edges': ['<status>']}]}
actual = TorqueTest.g.v("<bob>").out().all('e')
self.assertTrue(ordered(expected) == ordered(actual))

def test_torque_20(self):
expected = {'result': [{'id': '<bob>'}, {'id': '<emily>'}]}
actual = TorqueTest.g.v().has("<follows>", "<fred>").all('e')
self.assertTrue(expected == actual)

def test_torque_21(self):
expected = {'result': [{'id': '<dani>', 'edges': ['<follows>']}, {'id': '<charlie>', 'edges': ['<follows>']}, {'id': '<alice>', 'edges': ['<follows>']}]}
actual = TorqueTest.g.v().has("<follows>", "<fred>").inc().all('e')
self.assertTrue(expected == actual)

@classmethod
def tearDownClass(cls):
TorqueTest.g.close()
Expand Down