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

[WIP] GRIP engine embedded in a python library #316

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
24 changes: 21 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,28 @@ jobs:
fi
# run specialized role based tests
make test-authorization ARGS="--grip_config_file_path test/badger-auth.yml"




pygripTest:
needs: build
name: PyGrip UnitTest
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Python Dependencies for Conformance
run: pip install requests numpy PyYAML
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May need to include setuptools

- name: install gripql
run: |
cd gripql/python
python setup.py install --user
- name: install pygrip
run: |
python setup.py install --user
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

python setup.py install --user produces:

...
Installed /Users/walsbr/.local/lib/python3.9/site-packages/pygrip-0.8.0-py3.9-macosx-12.4-arm64.egg
Processing dependencies for pygrip==0.8.0
Searching for pygrip==0.8.0
Reading https://pypi.org/simple/pygrip/
No local packages or working download links found for pygrip==0.8.0
error: Could not find suitable distribution for Requirement.parse('pygrip==0.8.0')```

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to 3.12, works now

Using /Users/walsbr/bmeg/grip/venv/lib/python3.12/site-packages
Finished processing dependencies for pygrip==0.8.0```

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, it doesn't look like it installed?

$ pip freeze | grep pygrip
>>>  (no hits)
python -c "import pygrip"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/Users/walsbr/bmeg/grip/pygrip/__init__.py", line 9, in <module>
    from gripql.query import QueryBuilder
ModuleNotFoundError: No module named 'gripql.query'```

$ pytest test/pygrip_test/test_pygrip.py
========================================================================================== test session starts ==========================================================================================
platform darwin -- Python 3.12.1, pytest-8.1.1, pluggy-1.4.0
rootdir: /Users/walsbr/bmeg/grip
collected 0 items / 1 error

================================================================================================ ERRORS =================================================================================================
___________________________________________________________________________ ERROR collecting test/pygrip_test/test_pygrip.py ____________________________________________________________________________
ImportError while importing test module '/Users/walsbr/bmeg/grip/test/pygrip_test/test_pygrip.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
../../.pyenv/versions/3.12.1/lib/python3.12/importlib/init.py:90: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
test/pygrip_test/test_pygrip.py:2: in
import pygrip
E ModuleNotFoundError: No module named 'pygrip'
======================================================================================== short test summary info ========================================================================================
ERROR test/pygrip_test/test_pygrip.py```

- name: unit tests
run: |
cd test
python -m unittest discover -s ./pygrip_test

#gridsTest:
# needs: build
# name: GRIDs Conformance
Expand Down
2 changes: 1 addition & 1 deletion gripql/python/gripql/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@
count
]

__version__ = "0.7.1"
__version__ = "0.8.0"
6 changes: 6 additions & 0 deletions gripql/python/gripql/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ def query(self):
"""
return Query(self.base_url, self.graph, self.user, self.password, self.token, self.credential_file)

def V(self, *args):
"""
Create a vertex query handle.
"""
return Query(self.base_url, self.graph, self.user, self.password, self.token, self.credential_file).V(*args)

def resume(self, job_id):
"""
Create a query handle.
Expand Down
24 changes: 17 additions & 7 deletions gripql/python/gripql/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,12 @@ def _wrap_dict_value(value):
return _wrap_value(value, dict)


class Query(BaseConnection):
def __init__(self, url, graph, user=None, password=None, token=None, credential_file=None, resume=None):
super(Query, self).__init__(url, user, password, token, credential_file)
self.url = self.base_url + "/v1/graph/" + graph + "/query"
self.graph = graph
class QueryBuilder:
def __init__(self):
self.query = []
self.resume = resume

def __append(self, part):
q = self.__class__(self.base_url, self.graph, self.user, self.password, self.token, self.credential_file, self.resume)
q = self._builder()
q.query = self.query[:]
q.query.append(part)
return q
Expand Down Expand Up @@ -343,6 +339,20 @@ def to_dict(self):
"""
return {"query": self.query}



class Query(BaseConnection, QueryBuilder):
def __init__(self, url, graph, user=None, password=None, token=None, credential_file=None, resume=None):
super(Query, self).__init__(url, user, password, token, credential_file)
super(QueryBuilder, self).__init__()
self.url = self.base_url + "/v1/graph/" + graph + "/query"
self.graph = graph
self.query = []
self.resume = resume

def _builder(self):
return self.__class__(self.base_url, self.graph, self.user, self.password, self.token, self.credential_file, self.resume)

def __iter__(self):
return self.__stream()

Expand Down
168 changes: 168 additions & 0 deletions kvi/leveldb/memdb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package leveldb

import (
"bytes"
"fmt"

"github.com/bmeg/grip/kvi"
"github.com/bmeg/grip/log"
"github.com/syndtr/goleveldb/leveldb/comparer"
"github.com/syndtr/goleveldb/leveldb/iterator"
"github.com/syndtr/goleveldb/leveldb/memdb"
)

var mem_loaded = kvi.AddKVDriver("memdb", NewMemKVInterface)

type LevelMemKV struct {
db *memdb.DB
}

// NewKVInterface creates new LevelDB backed KVInterface at `path`
func NewMemKVInterface(path string, opts kvi.Options) (kvi.KVInterface, error) {
log.Info("Starting In-Memory LevelDB")
db := memdb.New(comparer.DefaultComparer, 1000)
return &LevelMemKV{db: db}, nil
}

// BulkWrite implements kvi.KVInterface.
func (l *LevelMemKV) BulkWrite(u func(bl kvi.KVBulkWrite) error) error {
ktx := &memIterator{l.db, nil, true, nil, nil}
return u(ktx)
}

// Close implements kvi.KVInterface.
func (l *LevelMemKV) Close() error {
return nil
}

// Delete implements kvi.KVInterface.
func (l *LevelMemKV) Delete(key []byte) error {
return l.db.Delete(key)
}

// DeletePrefix implements kvi.KVInterface.
func (l *LevelMemKV) DeletePrefix(prefix []byte) error {
deleteBlockSize := 10000
for found := true; found; {
found = false
wb := make([][]byte, 0, deleteBlockSize)
it := l.db.NewIterator(nil)
for it.Seek(prefix); it.Valid() && bytes.HasPrefix(it.Key(), prefix) && len(wb) < deleteBlockSize-1; it.Next() {
wb = append(wb, copyBytes(it.Key()))
}
it.Release()
for _, i := range wb {
l.db.Delete(i)
found = true
}
}
return nil

}

// Get implements kvi.KVInterface.
func (l *LevelMemKV) Get(key []byte) ([]byte, error) {
return l.db.Get(key)
}

// HasKey implements kvi.KVInterface.
func (l *LevelMemKV) HasKey(key []byte) bool {
_, err := l.db.Get(key)
return err == nil
}

// Set implements kvi.KVInterface.
func (l *LevelMemKV) Set(key []byte, value []byte) error {
return l.db.Put(key, value)
}

// Update implements kvi.KVInterface.
func (l *LevelMemKV) Update(func(tx kvi.KVTransaction) error) error {
panic("unimplemented")
}

// View implements kvi.KVInterface.
func (l *LevelMemKV) View(u func(it kvi.KVIterator) error) error {
it := l.db.NewIterator(nil)
defer it.Release()
lit := memIterator{l.db, it, true, nil, nil}
return u(&lit)
}

type memIterator struct {
db *memdb.DB
it iterator.Iterator
forward bool
key []byte
value []byte
}

// Get retrieves the value of key `id`
func (lit *memIterator) Get(id []byte) ([]byte, error) {
return lit.db.Get(id)
}

func (lit *memIterator) Set(key, val []byte) error {
return lit.db.Put(key, val)
}

// Key returns the key the iterator is currently pointed at
func (lit *memIterator) Key() []byte {
return lit.key
}

// Value returns the valud of the iterator is currently pointed at
func (lit *memIterator) Value() ([]byte, error) {
return lit.value, nil
}

// Next move the iterator to the next key
func (lit *memIterator) Next() error {
var more bool
if lit.forward {
more = lit.it.Next()
} else {
more = lit.it.Prev()
}
if !more {
lit.key = nil
lit.value = nil
return fmt.Errorf("Invalid")
}
lit.key = copyBytes(lit.it.Key())
lit.value = copyBytes(lit.it.Value())
return nil
}

func (lit *memIterator) Seek(id []byte) error {
lit.forward = true
if lit.it.Seek(id) {
lit.key = copyBytes(lit.it.Key())
lit.value = copyBytes(lit.it.Value())
return nil
}
return fmt.Errorf("Invalid")
}

func (lit *memIterator) SeekReverse(id []byte) error {
lit.forward = false
if lit.it.Seek(id) {
//Level iterator will land on the first value above the request
//if we're there, move once to get below start request
if bytes.Compare(id, lit.it.Key()) < 0 {
lit.it.Prev()
}
lit.key = copyBytes(lit.it.Key())
lit.value = copyBytes(lit.it.Value())
return nil
}
return fmt.Errorf("Invalid")
}

// Valid returns true if iterator is still in valid location
func (lit *memIterator) Valid() bool {
if lit.key == nil || lit.value == nil {
return false
}
return true
}
4 changes: 4 additions & 0 deletions pygrip/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@


pygrip.so: wrapper.go
go build -o pygrip.so -buildmode=c-shared wrapper.go
74 changes: 74 additions & 0 deletions pygrip/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@


from __future__ import print_function
from ctypes import *
from ctypes.util import find_library
import os, inspect, sysconfig
import random, string
import json
from gripql.query import QueryBuilder

cwd = os.getcwd()
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
#print("frame: %s" % (inspect.getfile(inspect.currentframe())))
#print("cd to %s" % (currentdir))
os.chdir(currentdir)
_lib = cdll.LoadLibrary("./_pygrip" + sysconfig.get_config_vars()["EXT_SUFFIX"])
os.chdir(cwd)

_lib.ReaderNext.restype = c_char_p

class GoString(Structure):
_fields_ = [("p", c_char_p), ("n", c_longlong)]

def NewMemServer():
return GraphDBWrapper( _lib.NewMemServer() )

def getGoString(s):
return GoString(bytes(s, encoding="raw_unicode_escape"), len(s))

def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))

class QueryWrapper(QueryBuilder):
def __init__(self, wrapper):
super(QueryBuilder, self).__init__()
self.query = []
self.wrapper = wrapper

def __iter__(self):
jquery = json.dumps({ "graph" : "default", "query" : self.query })
reader = _lib.Query( self.wrapper._handle, getGoString(jquery) )
while not _lib.ReaderDone(reader):
j = _lib.ReaderNext(reader)
yield json.loads(j)

def _builder(self):
return QueryWrapper(self.wrapper)

class GraphDBWrapper:
def __init__(self, handle) -> None:
self._handle = handle

def addVertex(self, gid, label, data={}):
"""
Add vertex to a graph.
"""
_lib.AddVertex(self._handle, getGoString(gid), getGoString(label),
getGoString(json.dumps(data)))

def addEdge(self, src, dst, label, data={}, gid=None):
"""
Add edge to a graph.
"""
if gid is None:
gid = id_generator(10)

_lib.AddEdge(self._handle, getGoString(gid),
getGoString(src), getGoString(dst), getGoString(label),
getGoString(json.dumps(data)))



def V(self, *ids):
return QueryWrapper(self).V(*ids)
Loading
Loading