From 0415dc7ff6b66029030df530c911e919046eb058 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Fri, 16 Aug 2024 22:27:05 -0700 Subject: [PATCH 01/17] Adding mem based key-value driver and testing a python lib --- gripql/python/gripql/graph.py | 6 ++ kvi/leveldb/memdb.go | 168 ++++++++++++++++++++++++++++++++++ pygrip_wrapper/new.go | 15 +++ 3 files changed, 189 insertions(+) create mode 100644 kvi/leveldb/memdb.go create mode 100644 pygrip_wrapper/new.go diff --git a/gripql/python/gripql/graph.py b/gripql/python/gripql/graph.py index 2b64d02b0..ccfd20ca5 100644 --- a/gripql/python/gripql/graph.py +++ b/gripql/python/gripql/graph.py @@ -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. diff --git a/kvi/leveldb/memdb.go b/kvi/leveldb/memdb.go new file mode 100644 index 000000000..66e8c2752 --- /dev/null +++ b/kvi/leveldb/memdb.go @@ -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 +} diff --git a/pygrip_wrapper/new.go b/pygrip_wrapper/new.go new file mode 100644 index 000000000..d8ddda4b0 --- /dev/null +++ b/pygrip_wrapper/new.go @@ -0,0 +1,15 @@ +package pygrip_wrapper + +import ( + "fmt" + + "github.com/bmeg/grip/gdbi" +) + +func NewMemServer() gdbi.GraphDB { + fmt.Printf("I got this far\n") + //db, _ := leveldb.NewMemKVInterface("", kvi.Options{}) + //graphdb := kvgraph.NewKVGraph(db) + //return graphdb + return nil +} From d352fa8bb1fca71fb6d1b035a2a54fee4b4fb604 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Tue, 20 Aug 2024 21:17:49 -0700 Subject: [PATCH 02/17] Working python wrapper library --- gripql/python/gripql/query.py | 25 ++++-- pygrip/Makefile | 4 + pygrip/__init__.py | 71 +++++++++++++++ pygrip/pygrip.h | 86 ++++++++++++++++++ pygrip/wrapper.go | 158 ++++++++++++++++++++++++++++++++++ pygrip_wrapper/new.go | 15 ---- test/pygrip/pygrip_test.py | 19 ++++ 7 files changed, 356 insertions(+), 22 deletions(-) create mode 100644 pygrip/Makefile create mode 100644 pygrip/__init__.py create mode 100644 pygrip/pygrip.h create mode 100644 pygrip/wrapper.go delete mode 100644 pygrip_wrapper/new.go create mode 100644 test/pygrip/pygrip_test.py diff --git a/gripql/python/gripql/query.py b/gripql/python/gripql/query.py index b9e8f1bc2..738c73f24 100644 --- a/gripql/python/gripql/query.py +++ b/gripql/python/gripql/query.py @@ -36,16 +36,13 @@ 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 + print("QueryBuilder_init_") 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 @@ -343,6 +340,20 @@ def to_dict(self): """ return {"query": self.query} + + +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) + super(QueryBuilder, self).__init__(self.__new) + self.url = self.base_url + "/v1/graph/" + graph + "/query" + self.graph = graph + self.query = [] + self.resume = resume + + def __new(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() diff --git a/pygrip/Makefile b/pygrip/Makefile new file mode 100644 index 000000000..e3d2cad19 --- /dev/null +++ b/pygrip/Makefile @@ -0,0 +1,4 @@ + + +pygrip.so: wrapper.go + go build -o pygrip.so -buildmode=c-shared wrapper.go diff --git a/pygrip/__init__.py b/pygrip/__init__.py new file mode 100644 index 000000000..a691c8ff4 --- /dev/null +++ b/pygrip/__init__.py @@ -0,0 +1,71 @@ + + +from __future__ import print_function +from ctypes import * +import os, inspect +import random, string +import json +from gripql.query import QueryBuilder + +cwd = os.getcwd() +currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) +os.chdir(currentdir) +_lib = cdll.LoadLibrary("./pygrip.so") +os.chdir(cwd) + +_lib.ReaderNext.restype = c_char_p + +class GoString(Structure): + _fields_ = [("p", c_char_p), ("n", c_longlong)] + +def NewMemServer(): + print(dir(_lib)) + 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) \ No newline at end of file diff --git a/pygrip/pygrip.h b/pygrip/pygrip.h new file mode 100644 index 000000000..db6abcf1c --- /dev/null +++ b/pygrip/pygrip.h @@ -0,0 +1,86 @@ +/* Code generated by cmd/cgo; DO NOT EDIT. */ + +/* package command-line-arguments */ + + +#line 1 "cgo-builtin-export-prolog" + +#include + +#ifndef GO_CGO_EXPORT_PROLOGUE_H +#define GO_CGO_EXPORT_PROLOGUE_H + +#ifndef GO_CGO_GOSTRING_TYPEDEF +typedef struct { const char *p; ptrdiff_t n; } _GoString_; +#endif + +#endif + +/* Start of preamble from import "C" comments. */ + + + + +/* End of preamble from import "C" comments. */ + + +/* Start of boilerplate cgo prologue. */ +#line 1 "cgo-gcc-export-header-prolog" + +#ifndef GO_CGO_PROLOGUE_H +#define GO_CGO_PROLOGUE_H + +typedef signed char GoInt8; +typedef unsigned char GoUint8; +typedef short GoInt16; +typedef unsigned short GoUint16; +typedef int GoInt32; +typedef unsigned int GoUint32; +typedef long long GoInt64; +typedef unsigned long long GoUint64; +typedef GoInt64 GoInt; +typedef GoUint64 GoUint; +typedef size_t GoUintptr; +typedef float GoFloat32; +typedef double GoFloat64; +#ifdef _MSC_VER +#include +typedef _Fcomplex GoComplex64; +typedef _Dcomplex GoComplex128; +#else +typedef float _Complex GoComplex64; +typedef double _Complex GoComplex128; +#endif + +/* + static assertion to make sure the file is being used on architecture + at least with matching size of GoInt. +*/ +typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1]; + +#ifndef GO_CGO_GOSTRING_TYPEDEF +typedef _GoString_ GoString; +#endif +typedef void *GoMap; +typedef void *GoChan; +typedef struct { void *t; void *v; } GoInterface; +typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; + +#endif + +/* End of boilerplate cgo prologue. */ + +#ifdef __cplusplus +extern "C" { +#endif + +extern GoUintptr NewMemServer(); +extern void AddVertex(GoUintptr graph, GoString gid, GoString label, GoString jdata); +extern void AddEdge(GoUintptr graph, GoString gid, GoString src, GoString dst, GoString label, GoString jdata); +extern GoUintptr Query(GoUintptr graph, GoString jquery); +extern GoUint8 ReaderDone(GoUintptr reader); +extern char* ReaderNext(GoUintptr reader); + +#ifdef __cplusplus +} +#endif diff --git a/pygrip/wrapper.go b/pygrip/wrapper.go new file mode 100644 index 000000000..033b947b1 --- /dev/null +++ b/pygrip/wrapper.go @@ -0,0 +1,158 @@ +package main + +/* +#include // for uintptr_t +*/ + +import "C" + +import ( + "context" + "encoding/json" + "fmt" + + "runtime/cgo" + + "github.com/bmeg/grip/engine" + "github.com/bmeg/grip/engine/core" + "github.com/bmeg/grip/engine/pipeline" + "github.com/bmeg/grip/gdbi" + "github.com/bmeg/grip/gripql" + "github.com/bmeg/grip/kvgraph" + "github.com/bmeg/grip/kvi" + "github.com/bmeg/grip/kvi/leveldb" + "google.golang.org/protobuf/encoding/protojson" +) + +var graphDB gdbi.GraphDB + +type GraphHandle uintptr +type QueryReaderHandle uintptr + +type Reader interface { + Done() bool + Next() string +} + +//export NewMemServer +func NewMemServer() GraphHandle { + db, _ := leveldb.NewMemKVInterface("", kvi.Options{}) + graphDB = kvgraph.NewKVGraph(db) + err := graphDB.AddGraph("default") + if err != nil { + fmt.Printf("Graph init error: %s\n", err) + } + g, err := graphDB.Graph("default") + if err != nil { + fmt.Printf("Graph init error: %s\n", err) + } + return GraphHandle(cgo.NewHandle(g)) +} + +//export AddVertex +func AddVertex(graph GraphHandle, gid, label, jdata string) { + data := map[string]any{} + err := json.Unmarshal([]byte(jdata), &data) + if err != nil { + fmt.Printf("Data error: %s : %s\n", err, jdata) + } + + g := cgo.Handle(graph).Value().(gdbi.GraphInterface) + + g.AddVertex([]*gdbi.Vertex{ + {ID: gid, Label: label, Data: data}, + }) +} + +//export AddEdge +func AddEdge(graph GraphHandle, gid, src, dst, label, jdata string) { + data := map[string]any{} + err := json.Unmarshal([]byte(jdata), &data) + if err != nil { + fmt.Printf("Data error: %s : %s\n", err, jdata) + } + + g := cgo.Handle(graph).Value().(gdbi.GraphInterface) + + g.AddEdge([]*gdbi.Edge{ + {ID: gid, To: dst, From: src, Label: label, Data: data}, + }) +} + +type QueryReader struct { + pipe gdbi.Pipeline + results chan *gripql.QueryResult + current *gripql.QueryResult +} + +//export Query +func Query(graph GraphHandle, jquery string) QueryReaderHandle { + query := gripql.GraphQuery{} + err := protojson.Unmarshal([]byte(jquery), &query) + if err != nil { + fmt.Printf("Query error: %s : %s\n", err) + } + + g := cgo.Handle(graph).Value().(gdbi.GraphInterface) + compiler := core.NewCompiler(g) + pipe, err := compiler.Compile(query.Query, nil) + if err != nil { + fmt.Printf("Compile error: %s : %s\n", err) + } + + ctx := context.Background() + + bufsize := 5000 + resch := make(chan *gripql.QueryResult, bufsize) + go func() { + defer close(resch) + graph := pipe.Graph() + dataType := pipe.DataType() + markTypes := pipe.MarkTypes() + man := engine.NewManager("./") //TODO: in memory option + rPipe := pipeline.Start(ctx, pipe, man, bufsize, nil, nil) + for t := range rPipe.Outputs { + if !t.IsSignal() { + resch <- pipeline.Convert(graph, dataType, markTypes, t) + } + } + man.Cleanup() + }() + var o = &QueryReader{ + pipe: pipe, + results: resch, + current: nil, + } + return QueryReaderHandle(cgo.NewHandle(o)) +} + +//export ReaderDone +func ReaderDone(reader QueryReaderHandle) bool { + r := cgo.Handle(reader).Value().(*QueryReader) + return r.Done() +} + +//export ReaderNext +func ReaderNext(reader QueryReaderHandle) *C.char { + r := cgo.Handle(reader).Value().(*QueryReader) + o := r.Next() + return C.CString(o) +} + +func (r *QueryReader) Next() string { + out, _ := protojson.Marshal(r.current) + return string(out) +} + +func (r *QueryReader) Done() bool { + select { + case i, ok := <-r.results: + if ok { + r.current = i + return false + } + return true + } +} + +func main() {} diff --git a/pygrip_wrapper/new.go b/pygrip_wrapper/new.go deleted file mode 100644 index d8ddda4b0..000000000 --- a/pygrip_wrapper/new.go +++ /dev/null @@ -1,15 +0,0 @@ -package pygrip_wrapper - -import ( - "fmt" - - "github.com/bmeg/grip/gdbi" -) - -func NewMemServer() gdbi.GraphDB { - fmt.Printf("I got this far\n") - //db, _ := leveldb.NewMemKVInterface("", kvi.Options{}) - //graphdb := kvgraph.NewKVGraph(db) - //return graphdb - return nil -} diff --git a/test/pygrip/pygrip_test.py b/test/pygrip/pygrip_test.py new file mode 100644 index 000000000..8eb6e2e71 --- /dev/null +++ b/test/pygrip/pygrip_test.py @@ -0,0 +1,19 @@ +import pygrip + +w = pygrip.NewMemServer() + + +w.addVertex("1", "Person", {"age":30, "eyes":"brown"}) +w.addVertex("2", "Person", {"age":40, "eyes":"blue"}) + +w.addEdge("1", "2", "knows") + +for row in w.V().hasLabel("Person"): + print("hasLabel", row) + +for row in w.V().out("knows"): + print("out", row) + +for row in w.V().count(): + print("count", row) + From 80b165211a65c53107135fa9d75b6b5553711ffd Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Tue, 20 Aug 2024 22:07:53 -0700 Subject: [PATCH 03/17] Fixing client --- gripql/python/gripql/query.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gripql/python/gripql/query.py b/gripql/python/gripql/query.py index 738c73f24..728658d84 100644 --- a/gripql/python/gripql/query.py +++ b/gripql/python/gripql/query.py @@ -39,7 +39,6 @@ def _wrap_dict_value(value): class QueryBuilder: def __init__(self): self.query = [] - print("QueryBuilder_init_") def __append(self, part): q = self._builder() @@ -342,16 +341,16 @@ def to_dict(self): -class Query(BaseConnection): +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.__new) + super(QueryBuilder, self).__init__() self.url = self.base_url + "/v1/graph/" + graph + "/query" self.graph = graph self.query = [] self.resume = resume - def __new(self): + 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): From 4e79dd7aae24b5d5aa88c060a17a3a05ad08a17c Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Thu, 22 Aug 2024 15:02:04 -0700 Subject: [PATCH 04/17] Adding setup.py for pygrip --- gripql/python/gripql/__init__.py | 2 +- pygrip/__init__.py | 5 +++-- setup.py | 33 ++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 setup.py diff --git a/gripql/python/gripql/__init__.py b/gripql/python/gripql/__init__.py index 8cc78e544..bbacd739d 100644 --- a/gripql/python/gripql/__init__.py +++ b/gripql/python/gripql/__init__.py @@ -36,4 +36,4 @@ count ] -__version__ = "0.7.1" +__version__ = "0.8.0" diff --git a/pygrip/__init__.py b/pygrip/__init__.py index a691c8ff4..df19dd8cd 100644 --- a/pygrip/__init__.py +++ b/pygrip/__init__.py @@ -2,7 +2,8 @@ from __future__ import print_function from ctypes import * -import os, inspect +from ctypes.util import find_library +import os, inspect, sysconfig import random, string import json from gripql.query import QueryBuilder @@ -10,7 +11,7 @@ cwd = os.getcwd() currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) os.chdir(currentdir) -_lib = cdll.LoadLibrary("./pygrip.so") +_lib = cdll.LoadLibrary("./_pygrip" + sysconfig.get_config_vars()["EXT_SUFFIX"]) os.chdir(cwd) _lib.ReaderNext.restype = c_char_p diff --git a/setup.py b/setup.py new file mode 100644 index 000000000..4dba5c58c --- /dev/null +++ b/setup.py @@ -0,0 +1,33 @@ +"""Setup for checksig package""" +import sys +from distutils.errors import CompileError +from subprocess import call + +from setuptools import Extension, setup, find_packages +from setuptools.command.build_ext import build_ext + + +class build_go_ext(build_ext): + """Custom command to build extension from Go source files""" + def build_extension(self, ext): + ext_path = self.get_ext_fullpath(ext.name) + cmd = ['go', 'build', '-buildmode=c-shared', '-o', ext_path] + cmd += ext.sources + out = call(cmd) + if out != 0: + raise CompileError('Go build failed') + +setup( + name='pygrip', + version='0.8.0', + packages=find_packages(include=['pygrip']), + #py_modules=['pygrip'], + ext_modules=[ + Extension('pygrip/_pygrip', ['./pygrip/wrapper.go']) + ], + cmdclass={'build_ext': build_go_ext}, + install_requires=[ + "gripql>=0.8.0" + ], + zip_safe=False, +) \ No newline at end of file From ebc227afba7bd0555b720e60c0ced4205c184966 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Thu, 22 Aug 2024 15:34:56 -0700 Subject: [PATCH 05/17] Working on adding unit tests --- .github/workflows/tests.yml | 20 ++++++++++++++++++-- pygrip/__init__.py | 1 + test/pygrip/pygrip_test.py | 19 ------------------- 3 files changed, 19 insertions(+), 21 deletions(-) delete mode 100644 test/pygrip/pygrip_test.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 91ef26d2e..fb4bf88cc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -233,8 +233,24 @@ 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 + - name: install gripql + run: | + cd gripql/python + python setup.py install + - name: install gripql + run: | + python setup.py install + #gridsTest: diff --git a/pygrip/__init__.py b/pygrip/__init__.py index df19dd8cd..3aa41f97c 100644 --- a/pygrip/__init__.py +++ b/pygrip/__init__.py @@ -10,6 +10,7 @@ cwd = os.getcwd() currentdir = os.path.dirname(os.path.abspath(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) diff --git a/test/pygrip/pygrip_test.py b/test/pygrip/pygrip_test.py deleted file mode 100644 index 8eb6e2e71..000000000 --- a/test/pygrip/pygrip_test.py +++ /dev/null @@ -1,19 +0,0 @@ -import pygrip - -w = pygrip.NewMemServer() - - -w.addVertex("1", "Person", {"age":30, "eyes":"brown"}) -w.addVertex("2", "Person", {"age":40, "eyes":"blue"}) - -w.addEdge("1", "2", "knows") - -for row in w.V().hasLabel("Person"): - print("hasLabel", row) - -for row in w.V().out("knows"): - print("out", row) - -for row in w.V().count(): - print("count", row) - From da288a3f74a4e5ec528483f7145d76d39ba9d957 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Thu, 22 Aug 2024 15:44:09 -0700 Subject: [PATCH 06/17] Doing user install for python --- .github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fb4bf88cc..fa47bc2ea 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -246,10 +246,10 @@ jobs: - name: install gripql run: | cd gripql/python - python setup.py install - - name: install gripql + python setup.py install --user + - name: install pygrip run: | - python setup.py install + python setup.py install --user From cf3613cf30f71131ac5427f4ad206c6370953722 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Thu, 22 Aug 2024 15:51:33 -0700 Subject: [PATCH 07/17] add step to actually run the pygrip unit tests --- .github/workflows/tests.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fa47bc2ea..d5e51e998 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -250,9 +250,10 @@ jobs: - name: install pygrip run: | python setup.py install --user - - - + - name: unit tests + run: | + python -m unittest discover -s ./test/pygrip + #gridsTest: # needs: build # name: GRIDs Conformance From 1441f5dc39f79af5ad4e7aea35f3f799760bfc85 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Thu, 22 Aug 2024 15:55:47 -0700 Subject: [PATCH 08/17] Adding missing file --- test/pygrip/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/pygrip/__init__.py diff --git a/test/pygrip/__init__.py b/test/pygrip/__init__.py new file mode 100644 index 000000000..e69de29bb From 2200d28413c56b57b0bd037f067ae8148c913c22 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Thu, 22 Aug 2024 16:04:16 -0700 Subject: [PATCH 09/17] Debugging unit test running --- .github/workflows/tests.yml | 3 ++- pygrip/__init__.py | 4 ++-- test/{pygrip => pygrip_test}/__init__.py | 0 test/pygrip_test/test_pygrip.py | 29 ++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) rename test/{pygrip => pygrip_test}/__init__.py (100%) create mode 100644 test/pygrip_test/test_pygrip.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d5e51e998..5ea5226e7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -252,7 +252,8 @@ jobs: python setup.py install --user - name: unit tests run: | - python -m unittest discover -s ./test/pygrip + cd test + python -m unittest discover -s ./pygrip_test #gridsTest: # needs: build diff --git a/pygrip/__init__.py b/pygrip/__init__.py index 3aa41f97c..d04f14c13 100644 --- a/pygrip/__init__.py +++ b/pygrip/__init__.py @@ -10,7 +10,8 @@ cwd = os.getcwd() currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) -print("cd to %s" % (currentdir)) +#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) @@ -21,7 +22,6 @@ class GoString(Structure): _fields_ = [("p", c_char_p), ("n", c_longlong)] def NewMemServer(): - print(dir(_lib)) return GraphDBWrapper( _lib.NewMemServer() ) def getGoString(s): diff --git a/test/pygrip/__init__.py b/test/pygrip_test/__init__.py similarity index 100% rename from test/pygrip/__init__.py rename to test/pygrip_test/__init__.py diff --git a/test/pygrip_test/test_pygrip.py b/test/pygrip_test/test_pygrip.py new file mode 100644 index 000000000..a21f881d2 --- /dev/null +++ b/test/pygrip_test/test_pygrip.py @@ -0,0 +1,29 @@ + +import pygrip +import unittest + +class TestPyGRIP(unittest.TestCase): + + def test_query(self): + + w = pygrip.NewMemServer() + + w.addVertex("1", "Person", {"age":30, "eyes":"brown"}) + w.addVertex("2", "Person", {"age":40, "eyes":"blue"}) + w.addEdge("1", "2", "knows") + + count = 0 + for row in w.V().hasLabel("Person"): + count += 1 + self.assertEqual(count, 2) + + count = 0 + for row in w.V().out("knows"): + count += 1 + self.assertEqual(count, 1) + + for row in w.V().count(): + self.assertEqual(row["count"], 2) + +if __name__ == '__main__': + unittest.main() From 7421fe8260e2d8ecbd70680250d62ec1cc087088 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Wed, 28 Aug 2024 11:13:31 -0700 Subject: [PATCH 10/17] Removing stdout error statements --- pygrip/__init__.py | 3 ++- pygrip/wrapper.go | 18 +++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pygrip/__init__.py b/pygrip/__init__.py index d04f14c13..33964bf1a 100644 --- a/pygrip/__init__.py +++ b/pygrip/__init__.py @@ -54,7 +54,8 @@ def addVertex(self, gid, label, data={}): """ Add vertex to a graph. """ - _lib.AddVertex(self._handle, getGoString(gid), getGoString(label), getGoString(json.dumps(data))) + _lib.AddVertex(self._handle, getGoString(gid), getGoString(label), + getGoString(json.dumps(data))) def addEdge(self, src, dst, label, data={}, gid=None): """ diff --git a/pygrip/wrapper.go b/pygrip/wrapper.go index 033b947b1..0657f2c81 100644 --- a/pygrip/wrapper.go +++ b/pygrip/wrapper.go @@ -9,7 +9,6 @@ import "C" import ( "context" "encoding/json" - "fmt" "runtime/cgo" @@ -21,6 +20,7 @@ import ( "github.com/bmeg/grip/kvgraph" "github.com/bmeg/grip/kvi" "github.com/bmeg/grip/kvi/leveldb" + "github.com/bmeg/grip/log" "google.golang.org/protobuf/encoding/protojson" ) @@ -40,21 +40,25 @@ func NewMemServer() GraphHandle { graphDB = kvgraph.NewKVGraph(db) err := graphDB.AddGraph("default") if err != nil { - fmt.Printf("Graph init error: %s\n", err) + log.Errorf("Graph init error: %s\n", err) } g, err := graphDB.Graph("default") if err != nil { - fmt.Printf("Graph init error: %s\n", err) + log.Errorf("Graph init error: %s\n", err) } return GraphHandle(cgo.NewHandle(g)) } +func CloseServer(graph GraphHandle) { + cgo.Handle(graph).Delete() +} + //export AddVertex func AddVertex(graph GraphHandle, gid, label, jdata string) { data := map[string]any{} err := json.Unmarshal([]byte(jdata), &data) if err != nil { - fmt.Printf("Data error: %s : %s\n", err, jdata) + log.Errorf("Data error: %s : %s\n", err, jdata) } g := cgo.Handle(graph).Value().(gdbi.GraphInterface) @@ -69,7 +73,7 @@ func AddEdge(graph GraphHandle, gid, src, dst, label, jdata string) { data := map[string]any{} err := json.Unmarshal([]byte(jdata), &data) if err != nil { - fmt.Printf("Data error: %s : %s\n", err, jdata) + log.Errorf("Data error: %s : %s\n", err, jdata) } g := cgo.Handle(graph).Value().(gdbi.GraphInterface) @@ -90,14 +94,14 @@ func Query(graph GraphHandle, jquery string) QueryReaderHandle { query := gripql.GraphQuery{} err := protojson.Unmarshal([]byte(jquery), &query) if err != nil { - fmt.Printf("Query error: %s : %s\n", err) + log.Errorf("Query error: %s : %s\n", err) } g := cgo.Handle(graph).Value().(gdbi.GraphInterface) compiler := core.NewCompiler(g) pipe, err := compiler.Compile(query.Query, nil) if err != nil { - fmt.Printf("Compile error: %s : %s\n", err) + log.Errorf("Compile error: %s : %s\n", err) } ctx := context.Background() From 0467c4e79bdc379b8a32acee9c207ff46b7e244f Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Wed, 28 Aug 2024 16:56:46 -0700 Subject: [PATCH 11/17] adds pythonpath for pytest --- pytest.ini | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..fdfcb6508 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +pythonpath = . gripql/python grip From 0b16fbe1f09527963066868963c0199728ec0393 Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Wed, 28 Aug 2024 16:57:30 -0700 Subject: [PATCH 12/17] make ./test a package --- test/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/__init__.py diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 000000000..e69de29bb From 951532272beb3a1b7d393f22aa476e2445da8f35 Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Wed, 28 Aug 2024 16:59:22 -0700 Subject: [PATCH 13/17] test FHIR load, dataframe traversals test FHIR load adds jsonpath-ng for reference searches improves traversal tests --- test/pygrip_test/fhir/README.md | 5 + test/pygrip_test/fhir/__init__.py | 0 .../META/DocumentReference.ndjson | 1 + .../META/Observation.ndjson | 3 + .../META/Organization.ndjson | 1 + .../fhir-compbio-examples/META/Patient.ndjson | 1 + .../META/ResearchStudy.ndjson | 1 + .../META/ResearchSubject.ndjson | 1 + .../META/Specimen.ndjson | 1 + .../fixtures/fhir-compbio-examples/README.md | 11 + test/pygrip_test/fhir/test_load.py | 264 ++++++++++++++++++ 11 files changed, 289 insertions(+) create mode 100644 test/pygrip_test/fhir/README.md create mode 100644 test/pygrip_test/fhir/__init__.py create mode 100644 test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/DocumentReference.ndjson create mode 100644 test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Observation.ndjson create mode 100644 test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Organization.ndjson create mode 100644 test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Patient.ndjson create mode 100644 test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/ResearchStudy.ndjson create mode 100644 test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/ResearchSubject.ndjson create mode 100644 test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Specimen.ndjson create mode 100644 test/pygrip_test/fhir/fixtures/fhir-compbio-examples/README.md create mode 100644 test/pygrip_test/fhir/test_load.py diff --git a/test/pygrip_test/fhir/README.md b/test/pygrip_test/fhir/README.md new file mode 100644 index 000000000..c41134976 --- /dev/null +++ b/test/pygrip_test/fhir/README.md @@ -0,0 +1,5 @@ +This test has a dependency +```commandline +pip install jsonpath-ng + +``` \ No newline at end of file diff --git a/test/pygrip_test/fhir/__init__.py b/test/pygrip_test/fhir/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/DocumentReference.ndjson b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/DocumentReference.ndjson new file mode 100644 index 000000000..8da14c2b2 --- /dev/null +++ b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/DocumentReference.ndjson @@ -0,0 +1 @@ +{"resourceType":"DocumentReference","id":"9ae7e542-767f-4b03-a854-7ceed17152cb","identifier":[{"use":"official","system":"https://my_demo.org/labA","value":"9ae7e542-767f-4b03-a854-7ceed17152cb"}],"status":"current","docStatus":"final","subject":{"reference":"Specimen/60c67a06-ea2d-4d24-9249-418dc77a16a9"},"date":"2024-08-21T10:53:00+00:00","content":[{"attachment":{"extension":[{"url":"http://aced-idp.org/fhir/StructureDefinition/md5","valueString":"227f0a5379362d42eaa1814cfc0101b8"},{"url":"http://aced-idp.org/fhir/StructureDefinition/source_path","valueUrl":"file:///home/LabA/specimen_1234_labA.fq.gz"}],"contentType":"text/fastq","url":"file:///home/LabA/specimen_1234_labA.fq.gz","size":5595609484,"title":"specimen_1234_labA.fq.gz","creation":"2024-08-21T10:53:00+00:00"}}]} \ No newline at end of file diff --git a/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Observation.ndjson b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Observation.ndjson new file mode 100644 index 000000000..774b7051d --- /dev/null +++ b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Observation.ndjson @@ -0,0 +1,3 @@ +{"resourceType":"Observation","id":"cec32723-9ede-5f24-ba63-63cb8c6a02cf","identifier":[{"use":"official","system":"https://my_demo.org/labA","value":"patientX_1234-9ae7e542-767f-4b03-a854-7ceed17152cb-sequencer"}], "status":"final","category":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/observation-category","code":"laboratory","display":"Laboratory"}]}],"code":{"coding":[{"system":"https://my_demo.org/labA","code":"Gen3 Sequencing Metadata","display":"Gen3 Sequencing Metadata"}]},"subject":{"reference":"Patient/bc4e1aa6-cb52-40e9-8f20-594d9c84f920"},"focus":[{"reference":"DocumentReference/9ae7e542-767f-4b03-a854-7ceed17152cb"}],"specimen":{"reference":"Specimen/60c67a06-ea2d-4d24-9249-418dc77a16a9"},"component":[{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"sequencer","display":"sequencer"}],"text":"sequencer"},"valueString":"Illumina Seq 1000"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"index","display":"index"}],"text":"index"},"valueString":"100bp Single index"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"type","display":"type"}],"text":"type"},"valueString":"Exome"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"project_id","display":"project_id"}],"text":"project_id"},"valueString":"labA_projectXYZ"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"read_length","display":"read_length"}],"text":"read_length"},"valueString":"100"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"instrument_run_id","display":"instrument_run_id"}],"text":"instrument_run_id"},"valueString":"234_ABC_1_8899"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"capture_bait_set","display":"capture_bait_set"}],"text":"capture_bait_set"},"valueString":"Human Exom 2X"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"end_type","display":"end_type"}],"text":"end_type"},"valueString":"Paired-End"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"capture","display":"capture"}],"text":"capture"},"valueString":"emitter XT"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"sequencing_site","display":"sequencing_site"}],"text":"sequencing_site"},"valueString":"AdvancedGeneExom"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"construction","display":"construction"}],"text":"construction"},"valueString":"library_construction"}]} +{"resourceType":"Observation","id":"4e3c6b59-b1fd-5c26-a611-da4cde9fd061","identifier":[{"use":"official","system":"https://my_demo.org/labA","value":"patientX_1234-specimen_1234_labA-sample_type"}],"status":"final","category":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/observation-category","code":"laboratory","display":"Laboratory"}],"text":"Laboratory"}],"code":{"coding":[{"system":"https://my_demo.org/labA","code":"labA specimen metadata","display":"labA specimen metadata"}],"text":"sample type abc"},"subject":{"reference":"Patient/bc4e1aa6-cb52-40e9-8f20-594d9c84f920"},"focus":[{"reference":"Specimen/60c67a06-ea2d-4d24-9249-418dc77a16a9"}],"component":[{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"sample_type","display":"sample_type"}],"text":"sample_type"},"valueString":"Primary Solid Tumor"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"library_id","display":"library_id"}],"text":"library_id"},"valueString":"12345"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"tissue_type","display":"tissue_type"}],"text":"tissue_type"},"valueString":"Tumor"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"treatments","display":"treatments"}],"text":"treatments"},"valueString":"Trastuzumab"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"allocated_for_site","display":"allocated_for_site"}],"text":"allocated_for_site"},"valueString":"TEST Clinical Research"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"pathology_data","display":"pathology_data"}],"text":"pathology_data"}},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"clinical_event","display":"clinical_event"}],"text":"clinical_event"}},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"indexed_collection_date","display":"indexed_collection_date"}],"text":"indexed_collection_date"},"valueInteger":365},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"biopsy_specimens_bems_id","display":"biopsy_specimens_bems_id"}],"text":"biopsy_specimens"},"valueString":"specimenA, specimenB, specimenC"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"biopsy_procedure_type","display":"biopsy_procedure_type"}],"text":"biopsy_procedure_type"},"valueString":"Biopsy - Core"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"biopsy_anatomical_location","display":"biopsy_anatomical_location"}],"text":"biopsy_anatomical_location"},"valueString":"top axillary lymph node"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"percent_tumor","display":"percent_tumor"}],"text":"percent_tumor"},"valueString":"30"}]} +{"resourceType":"Observation","id":"21f3411d-89a4-4bcc-9ce7-b76edb1c745f","identifier":[{"use":"official","system":"https://my_demo.org/labA","value":"patientX_1234-9ae7e542-767f-4b03-a854-7ceed17152cb-Gene"}], "status":"final","category":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/observation-category","code":"laboratory","display":"Laboratory"}]}],"code":{"coding":[{"system":"https://loinc.org","code":"81247-9","display":"Genomic structural variant copy number"}]},"subject":{"reference":"Patient/bc4e1aa6-cb52-40e9-8f20-594d9c84f920"},"focus":[{"reference":"DocumentReference/9ae7e542-767f-4b03-a854-7ceed17152cb"}],"specimen":{"reference":"Specimen/60c67a06-ea2d-4d24-9249-418dc77a16a9"},"component":[{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"Gene","display":"Gene"}],"text":"Gene"},"valueString":"TP53"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"Chromosome","display":"Chromosome"}],"text":"Chromosome"},"valueString":"chr17"},{"code":{"coding":[{"system":"https://my_demo.org/labA","code":"result","display":"result"}],"text":"result"},"valueString":"gain of function (GOF)"}]} \ No newline at end of file diff --git a/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Organization.ndjson b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Organization.ndjson new file mode 100644 index 000000000..967445ae7 --- /dev/null +++ b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Organization.ndjson @@ -0,0 +1 @@ +{"resourceType":"Organization","id":"89c8dc4c-2d9c-48c7-8862-241a49a78f14","identifier":[{"use":"official","system":"https://my_demo.org/labA","value":"LabA_ORGANIZATION"}],"type":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/organization-type","code":"prov","display":"Healthcare Provider"}],"text":"An organization that provides healthcare services."},{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/organization-type","code":"edu","display":"Educational Institute"}],"text":"An educational institution that provides education or research facilities."}]} \ No newline at end of file diff --git a/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Patient.ndjson b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Patient.ndjson new file mode 100644 index 000000000..107bf78e5 --- /dev/null +++ b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Patient.ndjson @@ -0,0 +1 @@ +{"resourceType":"Patient","id":"bc4e1aa6-cb52-40e9-8f20-594d9c84f920","identifier":[{"use":"official","system":"https://my_demo.org/labA","value":"patientX_1234"}],"active":true} \ No newline at end of file diff --git a/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/ResearchStudy.ndjson b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/ResearchStudy.ndjson new file mode 100644 index 000000000..74cc40029 --- /dev/null +++ b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/ResearchStudy.ndjson @@ -0,0 +1 @@ +{"resourceType":"ResearchStudy","id":"7dacd4d0-3c8e-470b-bf61-103891627d45","identifier":[{"use":"official","system":"https://my_demo.org/labA","value":"labA"}],"name":"LabA","status":"active","description":"LabA Clinical Trial Study: FHIR Schema Chorot Integration"} \ No newline at end of file diff --git a/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/ResearchSubject.ndjson b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/ResearchSubject.ndjson new file mode 100644 index 000000000..6aee6d082 --- /dev/null +++ b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/ResearchSubject.ndjson @@ -0,0 +1 @@ +{"resourceType":"ResearchSubject","id":"2fc448d6-a23b-4b94-974b-c66110164851","identifier":[{"use":"official","system":"https://my_demo.org/labA","value":"subjectX_1234"}],"status":"active","study":{"reference":"ResearchStudy/7dacd4d0-3c8e-470b-bf61-103891627d45"},"subject":{"reference":"Patient/bc4e1aa6-cb52-40e9-8f20-594d9c84f920"}} \ No newline at end of file diff --git a/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Specimen.ndjson b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Specimen.ndjson new file mode 100644 index 000000000..b79c72cbf --- /dev/null +++ b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/META/Specimen.ndjson @@ -0,0 +1 @@ +{"resourceType":"Specimen","id":"60c67a06-ea2d-4d24-9249-418dc77a16a9","identifier":[{"use":"official","system":"https://my_demo.org/labA","value":"specimen_1234_labA"}],"subject":{"reference":"Patient/bc4e1aa6-cb52-40e9-8f20-594d9c84f920"},"collection":{"collector":{"reference":"Organization/89c8dc4c-2d9c-48c7-8862-241a49a78f14"},"bodySite":{"concept":{"coding":[{"system":"http://snomed.info/sct","code":"76752008","display":"Breast"}],"text":"Breast"}}},"processing":[{"method":{"coding":[{"system":"http://snomed.info/sct","code":"117032008","display":"Spun specimen (procedure)"},{"system":"https://my_demo.org/labA","code":"Double-Spun","display":"Double-Spun"}],"text":"Spun specimen (procedure)"}}]} \ No newline at end of file diff --git a/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/README.md b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/README.md new file mode 100644 index 000000000..bcad0e36e --- /dev/null +++ b/test/pygrip_test/fhir/fixtures/fhir-compbio-examples/README.md @@ -0,0 +1,11 @@ +##### META folder test-data: + +``` +>>>> resources={'summary': {'DocumentReference': 1, 'Specimen': 1, 'Observation': 3, 'ResearchStudy': 1, 'ResearchSubject': 1, 'Organization': 1, 'Patient': 1}} +``` + +There are three Observations with user-defined metadata component. +1. Focus - reference -> Specimen +2. Focus - reference -> DocumentReference + 1. The first Observation contains metadata on the file's sequencing metadata. + 2. The second Observation includes a simple summary of a CNV analysis result computed from this file. diff --git a/test/pygrip_test/fhir/test_load.py b/test/pygrip_test/fhir/test_load.py new file mode 100644 index 000000000..6446749b6 --- /dev/null +++ b/test/pygrip_test/fhir/test_load.py @@ -0,0 +1,264 @@ +import json +import pathlib +import types +from collections import defaultdict + +import pytest + +import pygrip +from jsonpath_ng import jsonpath, parse + +from typing import Generator, Dict, Any + + +def resources() -> Generator[Dict[str, Any], None, None]: + """Read a directory of ndjson files, return dictionary for each line.""" + base = pathlib.Path(__file__).parent.absolute() + fixture_path = pathlib.Path(base / 'fixtures' / 'fhir-compbio-examples' / 'META') + assert fixture_path.exists(), f"Fixture path {fixture_path.absolute()} does not exist." + for file in fixture_path.glob('*.ndjson'): + with open(str(file)) as fp: + for l_ in fp.readlines(): + yield json.loads(l_) + + +@pytest.fixture +def expected_edges() -> list[tuple]: + """Return the expected edges for the resources.""" + return [('21f3411d-89a4-4bcc-9ce7-b76edb1c745f', '60c67a06-ea2d-4d24-9249-418dc77a16a9', 'specimen'), + ('21f3411d-89a4-4bcc-9ce7-b76edb1c745f', '9ae7e542-767f-4b03-a854-7ceed17152cb', 'focus'), + ('21f3411d-89a4-4bcc-9ce7-b76edb1c745f', 'bc4e1aa6-cb52-40e9-8f20-594d9c84f920', 'subject'), + ('2fc448d6-a23b-4b94-974b-c66110164851', '7dacd4d0-3c8e-470b-bf61-103891627d45', 'study'), + ('2fc448d6-a23b-4b94-974b-c66110164851', 'bc4e1aa6-cb52-40e9-8f20-594d9c84f920', 'subject'), + ('4e3c6b59-b1fd-5c26-a611-da4cde9fd061', '60c67a06-ea2d-4d24-9249-418dc77a16a9', 'focus'), + ('4e3c6b59-b1fd-5c26-a611-da4cde9fd061', 'bc4e1aa6-cb52-40e9-8f20-594d9c84f920', 'subject'), + ('60c67a06-ea2d-4d24-9249-418dc77a16a9', '89c8dc4c-2d9c-48c7-8862-241a49a78f14', 'collection_collector'), + ('60c67a06-ea2d-4d24-9249-418dc77a16a9', 'bc4e1aa6-cb52-40e9-8f20-594d9c84f920', 'subject'), + ('9ae7e542-767f-4b03-a854-7ceed17152cb', '60c67a06-ea2d-4d24-9249-418dc77a16a9', 'subject'), + ('cec32723-9ede-5f24-ba63-63cb8c6a02cf', '60c67a06-ea2d-4d24-9249-418dc77a16a9', 'specimen'), + ('cec32723-9ede-5f24-ba63-63cb8c6a02cf', '9ae7e542-767f-4b03-a854-7ceed17152cb', 'focus'), + ('cec32723-9ede-5f24-ba63-63cb8c6a02cf', 'bc4e1aa6-cb52-40e9-8f20-594d9c84f920', 'subject')] + + +@pytest.fixture +def expected_vertices() -> list[tuple]: + """Return the expected vertices (only id, label) for the resources.""" + return [('21f3411d-89a4-4bcc-9ce7-b76edb1c745f', 'Observation'), + ('2fc448d6-a23b-4b94-974b-c66110164851', 'ResearchSubject'), + ('4e3c6b59-b1fd-5c26-a611-da4cde9fd061', 'Observation'), + ('60c67a06-ea2d-4d24-9249-418dc77a16a9', 'Specimen'), + ('7dacd4d0-3c8e-470b-bf61-103891627d45', 'ResearchStudy'), + ('89c8dc4c-2d9c-48c7-8862-241a49a78f14', 'Organization'), + ('9ae7e542-767f-4b03-a854-7ceed17152cb', 'DocumentReference'), + ('bc4e1aa6-cb52-40e9-8f20-594d9c84f920', 'Patient'), + ('cec32723-9ede-5f24-ba63-63cb8c6a02cf', 'Observation')] + + +@pytest.fixture +def expected_dataframe_associations(): + return { + ('ResearchSubject', '2fc448d6-a23b-4b94-974b-c66110164851'): [ + ('ResearchStudy', '7dacd4d0-3c8e-470b-bf61-103891627d45'), + ('Patient', 'bc4e1aa6-cb52-40e9-8f20-594d9c84f920'), + ('Specimen', '60c67a06-ea2d-4d24-9249-418dc77a16a9')], + ('Specimen', '60c67a06-ea2d-4d24-9249-418dc77a16a9'): [ + ('ResearchStudy', '7dacd4d0-3c8e-470b-bf61-103891627d45'), + ('ResearchSubject', '2fc448d6-a23b-4b94-974b-c66110164851'), + ('Patient', 'bc4e1aa6-cb52-40e9-8f20-594d9c84f920'), + ('Observation', '4e3c6b59-b1fd-5c26-a611-da4cde9fd061')], + ('ResearchStudy', '7dacd4d0-3c8e-470b-bf61-103891627d45'): [ + ('ResearchSubject', '2fc448d6-a23b-4b94-974b-c66110164851')], + ('Organization', '89c8dc4c-2d9c-48c7-8862-241a49a78f14'): [ + ('ResearchStudy', '7dacd4d0-3c8e-470b-bf61-103891627d45'), + ('ResearchSubject', '2fc448d6-a23b-4b94-974b-c66110164851'), + ('Patient', 'bc4e1aa6-cb52-40e9-8f20-594d9c84f920'), + ('Specimen', '60c67a06-ea2d-4d24-9249-418dc77a16a9'), + ('DocumentReference', '9ae7e542-767f-4b03-a854-7ceed17152cb')], + ('DocumentReference', '9ae7e542-767f-4b03-a854-7ceed17152cb'): [ + ('ResearchStudy', '7dacd4d0-3c8e-470b-bf61-103891627d45'), + ('ResearchSubject', '2fc448d6-a23b-4b94-974b-c66110164851'), + ('Patient', 'bc4e1aa6-cb52-40e9-8f20-594d9c84f920'), + ('Specimen', '60c67a06-ea2d-4d24-9249-418dc77a16a9'), + ('Observation', '21f3411d-89a4-4bcc-9ce7-b76edb1c745f'), + ('Observation', 'cec32723-9ede-5f24-ba63-63cb8c6a02cf')], + ('Patient', 'bc4e1aa6-cb52-40e9-8f20-594d9c84f920'): [ + ('ResearchStudy', '7dacd4d0-3c8e-470b-bf61-103891627d45'), + ('ResearchSubject', '2fc448d6-a23b-4b94-974b-c66110164851'), + ('Specimen', '60c67a06-ea2d-4d24-9249-418dc77a16a9'), + ('Observation', '21f3411d-89a4-4bcc-9ce7-b76edb1c745f'), + ('Observation', '4e3c6b59-b1fd-5c26-a611-da4cde9fd061'), + ('Observation', 'cec32723-9ede-5f24-ba63-63cb8c6a02cf')] + } + + +def match_label(self, vertex_gid, label, seen_already=None) -> dict: + """Recursively find the first vertex of a given label, starting traversals from vertex_gid.""" + + # check params + assert vertex_gid is not None, "Expected vertex_gid to be not None." + assert label is not None, "Expected label to be not None." + # mutable default arguments are evil + # See https://florimond.dev/en/posts/2018/08/python-mutable-defaults-are-the-source-of-all-evil + if seen_already is None: + seen_already = [] + + # get all edges for vertex + q = self.V(vertex_gid).both() + + # get all vertices for edges + # TODO - consider if this should be a vertices_of_label() -> generator[dict] instead + for _ in q: + if _['vertex']['label'] == label: + return _ + else: + if _['vertex']['gid'] in seen_already: + continue + seen_already.append(_['vertex']['gid']) + return self.match_label(_['vertex']['gid'], label, seen_already=seen_already) + + +def dataframe_associations(self, vertex_gid, vertex_label, labels=('ResearchStudy', 'ResearchSubject', 'Patient', 'Specimen', 'DocumentReference', 'Observation')) -> list[dict]: + """Return all objects associated with vertex_gid.""" + associations = [] + for label in labels: + if label == 'Observation': + continue + if vertex_label == label: + continue + _ = self.match_label(vertex_gid, label) + if _ is not None: + associations.append(_['vertex']['data']) + if 'Observation' in labels: + q = self.V(vertex_gid).in_(["focus", "subject"]).hasLabel("Observation") + for _ in q: + associations.append(_['vertex']['data']) + return associations + + +@pytest.fixture +def graph() -> pygrip.GraphDBWrapper: + """Load the resources into the graph.""" + graph = pygrip.NewMemServer() + jsonpath_expr = parse('*..reference') + for _ in resources(): + graph.addVertex(_['id'], _['resourceType'], _) + for match in jsonpath_expr.find(_): + # value will be something like "Specimen/60c67a06-ea2d-4d24-9249-418dc77a16a9" + # full_path will be something like "specimen.reference" or "focus.[0].reference" + type_, dst_id = match.value.split('/') + path_parts = str(match.full_path).split('.') + path_parts = [part for part in path_parts if '[' not in part and part != 'reference'] + label = '_'.join(path_parts) + graph.addEdge(_['id'], dst_id, label) + + # monkey patch the graph object with our methods + graph.match_label = types.MethodType(match_label, graph) + graph.dataframe_associations = types.MethodType(dataframe_associations, graph) + + yield graph + + +def test_graph_vertices(graph, expected_vertices): + """Test the graph vertices.""" + + actual_vertices = [] + for _ in graph.V(): + assert 'vertex' in _, f"Expected 'vertex' in {_}" + vertex = _['vertex'] + assert 'data' in vertex, f"Expected 'data' in {vertex}" + assert 'gid' in vertex, f"Expected 'gid' in {vertex}" + assert 'label' in vertex, f"Expected 'label' in {vertex}" + assert 'data' in vertex, f"Expected 'data' in {vertex}" + resource = _['vertex']['data'] + actual_vertices.append((resource['id'], resource['resourceType'])) + + print(actual_vertices) + assert actual_vertices == expected_vertices, f"Expected {expected_vertices} but got {actual_vertices}." + + +def test_graph_edges(graph, expected_edges): + """Test the graph vertices.""" + + # check edges all edges + actual_edges = [] + for _ in graph.V().outE(): + assert 'edge' in _, f"Expected 'edge' in {_}" + edge = _['edge'] + assert 'gid' in edge, f"Expected 'gid' in {edge}" + assert 'label' in edge, f"Expected 'label' in {edge}" + assert 'from' in edge, f"Expected 'from' in {edge}" + assert 'to' in edge, f"Expected 'to' in {edge}" + assert 'data' in edge, f"Expected 'data' in {edge}" + + actual_edges.append((edge['from'], edge['to'], edge['label'])) + + print(actual_edges) + assert actual_edges == expected_edges, f"Expected {expected_edges} but got {actual_edges}." + + +def test_graph_methods(graph): + """Test the methods we expect in a graph object.""" + assert 'V' in dir(graph), f"Expected 'V' in {type(graph)}" + assert 'match_label' in dir(graph), f"Expected 'match_label' in {type(graph)}" + assert 'dataframe_associations' in dir(graph), f"Expected 'dataframe_associations' in {type(graph)}" + + +def test_traversals(graph): + """Test basic traversals""" + + # specimen -> patient + q = graph.V().hasLabel("Specimen").out("subject") + actual_specimen_patient_count = len(list(q)) + assert actual_specimen_patient_count == 1, f"Expected 1 but got {actual_specimen_patient_count}." + assert list(q)[0]['vertex']['data']['resourceType'] == 'Patient' + + q = graph.V().hasLabel("DocumentReference").outV().hasLabel("Specimen").outV().hasLabel("Patient") + assert len(list(q)) == 1, f"Expected 1 but got {len(list(q))}." + actual_document_reference_patient_count = len(list(q)) + assert actual_document_reference_patient_count == 1, f"Expected 1 but got {actual_document_reference_patient_count}." + assert list(q)[0]['vertex']['data']['resourceType'] == 'Patient' + + # follow edges by edge label + q = graph.V().hasLabel("DocumentReference").out("subject") + assert len(list(q)) == 1, f"Expected 1 but got {len(list(q))}." + for subject in q: + subject = subject['vertex']['data'] + assert subject['resourceType'] == 'Specimen', f"Expected Specimen but got {subject['resourceType']}." + + # follow all out all edges recursively to a vertex of type X + + q = graph.V().hasLabel("DocumentReference") + assert len(list(q)) == 1, f"Expected 1 but got {len(list(q))}." + document_reference_gid = list(q)[0]['vertex']['gid'] + + # 1 hop + specimen = graph.match_label(document_reference_gid, 'Specimen') + assert specimen is not None, "Expected Specimen" + assert specimen['vertex']['gid'] == '60c67a06-ea2d-4d24-9249-418dc77a16a9', f"Expected 60c67a06-ea2d-4d24-9249-418dc77a16a9 but got {specimen}." + + # 2 hops + patient = graph.match_label(document_reference_gid, 'Patient') + assert patient is not None, "Expected Patient" + assert patient['vertex']['gid'] == 'bc4e1aa6-cb52-40e9-8f20-594d9c84f920', f"Expected bc4e1aa6-cb52-40e9-8f20-594d9c84f920 but got {patient}." + + # 4 hops + research_study = graph.match_label(document_reference_gid, 'ResearchStudy') + assert research_study is not None, "Expected ResearchStudy" + assert research_study['vertex']['gid'] == '7dacd4d0-3c8e-470b-bf61-103891627d45', f"Expected 7dacd4d0-3c8e-470b-bf61-103891627d45 but got {research_study}." + + # Observations + q = graph.V(document_reference_gid).in_(["focus", "subject"]).hasLabel("Observation") + assert len(list(q)) == 2, f"Expected 2 but got {len(list(q))} for {document_reference_gid}." + + +def test_dataframe_associations(graph, expected_vertices, expected_dataframe_associations): + """Test the dataframe associations.""" + + actual_dataframe_associations = defaultdict(list) + # for all objects in the graph except Observations, retrieve the associated objects useful for a dataframe + for vertex_gid, vertex_label in expected_vertices: + if vertex_label == 'Observation': + continue + df = graph.dataframe_associations(vertex_gid, vertex_label) + actual_dataframe_associations[(vertex_label, vertex_gid)] = [(_['resourceType'], _['id']) for _ in df] + assert actual_dataframe_associations == expected_dataframe_associations, f"Expected {expected_dataframe_associations} but got {actual_dataframe_associations}." From 9770579dc9116a329c22b66665f1d684adb7d60d Mon Sep 17 00:00:00 2001 From: Brian Walsh Date: Thu, 5 Sep 2024 13:02:31 -0700 Subject: [PATCH 14/17] adds comments and TODO --- test/pygrip_test/fhir/test_load.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/pygrip_test/fhir/test_load.py b/test/pygrip_test/fhir/test_load.py index 6446749b6..4b32d0b84 100644 --- a/test/pygrip_test/fhir/test_load.py +++ b/test/pygrip_test/fhir/test_load.py @@ -24,7 +24,7 @@ def resources() -> Generator[Dict[str, Any], None, None]: @pytest.fixture def expected_edges() -> list[tuple]: - """Return the expected edges for the resources.""" + """Return the expected edges for the resources [(src, dst, label)].""" return [('21f3411d-89a4-4bcc-9ce7-b76edb1c745f', '60c67a06-ea2d-4d24-9249-418dc77a16a9', 'specimen'), ('21f3411d-89a4-4bcc-9ce7-b76edb1c745f', '9ae7e542-767f-4b03-a854-7ceed17152cb', 'focus'), ('21f3411d-89a4-4bcc-9ce7-b76edb1c745f', 'bc4e1aa6-cb52-40e9-8f20-594d9c84f920', 'subject'), @@ -42,7 +42,7 @@ def expected_edges() -> list[tuple]: @pytest.fixture def expected_vertices() -> list[tuple]: - """Return the expected vertices (only id, label) for the resources.""" + """Return the expected vertices [(id, label)] for the resources.""" return [('21f3411d-89a4-4bcc-9ce7-b76edb1c745f', 'Observation'), ('2fc448d6-a23b-4b94-974b-c66110164851', 'ResearchSubject'), ('4e3c6b59-b1fd-5c26-a611-da4cde9fd061', 'Observation'), @@ -56,6 +56,7 @@ def expected_vertices() -> list[tuple]: @pytest.fixture def expected_dataframe_associations(): + """Return the expected dataframe associations for the resources. { (resource_type, resource_id): [(association_resource_type, association_resource_id)].""" return { ('ResearchSubject', '2fc448d6-a23b-4b94-974b-c66110164851'): [ ('ResearchStudy', '7dacd4d0-3c8e-470b-bf61-103891627d45'), @@ -137,8 +138,10 @@ def dataframe_associations(self, vertex_gid, vertex_label, labels=('ResearchStud @pytest.fixture def graph() -> pygrip.GraphDBWrapper: - """Load the resources into the graph.""" + """Load the resources into the graph. Note: this does _not_ consider iceberg schema.""" + # TODO - add parameter or test environment variable to switch between in-memory and remote graph graph = pygrip.NewMemServer() + # use jsonpath to find all references with a resource jsonpath_expr = parse('*..reference') for _ in resources(): graph.addVertex(_['id'], _['resourceType'], _) @@ -146,12 +149,16 @@ def graph() -> pygrip.GraphDBWrapper: # value will be something like "Specimen/60c67a06-ea2d-4d24-9249-418dc77a16a9" # full_path will be something like "specimen.reference" or "focus.[0].reference" type_, dst_id = match.value.split('/') + # determine label from full path path_parts = str(match.full_path).split('.') + # strip out array indices and reference path_parts = [part for part in path_parts if '[' not in part and part != 'reference'] + # make it a label label = '_'.join(path_parts) graph.addEdge(_['id'], dst_id, label) # monkey patch the graph object with our methods + # TODO - consider a more formal subclass of pygrip.GraphDBWrapper graph.match_label = types.MethodType(match_label, graph) graph.dataframe_associations = types.MethodType(dataframe_associations, graph) From 24f21102f54b04dec20dcd618c258a44b9591549 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Tue, 4 Feb 2025 13:43:23 -0800 Subject: [PATCH 15/17] Adding python dependencies to unit tests --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 910dedc48..8d4787049 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -238,7 +238,7 @@ jobs: - name: Check out code uses: actions/checkout@v2 - name: Python Dependencies for Conformance - run: pip install requests numpy PyYAML + run: pip install requests numpy PyYAML pytest jsonpath-ng - name: install gripql run: | cd gripql/python From 6f0c8a12928cba40a66662022f43ca3f1fe29b20 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Fri, 4 Apr 2025 08:54:24 -0700 Subject: [PATCH 16/17] Eliminating debugging Printf statements --- accounts/basic.go | 6 ++- accounts/casbin.go | 9 ++-- accounts/util.go | 10 ++-- engine/core/util.go | 8 ---- grids/new.go | 5 +- gripper/server.go | 8 ++-- gripql/marshal_flattened.go | 4 +- main.go | 3 +- mongo/compile.go | 6 +-- mongo/has_evaluator.go | 4 +- pygrip/__init__.py | 74 ------------------------------ pygrip/wrapper.go | 13 ++++++ server/api.go | 2 +- server/endpoints.go | 2 +- server/server.go | 2 +- test/pygrip_test/fhir/test_load.py | 2 +- 16 files changed, 45 insertions(+), 113 deletions(-) delete mode 100644 pygrip/__init__.py diff --git a/accounts/basic.go b/accounts/basic.go index ffbf6b119..01c74761a 100644 --- a/accounts/basic.go +++ b/accounts/basic.go @@ -4,6 +4,8 @@ import ( "encoding/base64" "fmt" "strings" + + "github.com/bmeg/grip/log" ) // BasicCredential describes a username and password for use with Funnel's basic auth. @@ -18,7 +20,7 @@ func (ba BasicAuth) Validate(md MetaData) (string, error) { var auth []string var ok bool - fmt.Printf("Running BasicAuth: %#v\n", md) + log.Infof("Running BasicAuth: %#v\n", md) if auth, ok = md["Authorization"]; !ok { if auth, ok = md["authorization"]; !ok { @@ -28,7 +30,7 @@ func (ba BasicAuth) Validate(md MetaData) (string, error) { if len(auth) > 0 { user, password, ok := parseBasicAuth(auth[0]) - fmt.Printf("User: %s Password: %s OK: %s\n", user, password, ok) + log.Debugf("User: %s Password: %s OK: %#v\n", user, password, ok) for _, c := range ba { if c.User == user && c.Password == password { return user, nil diff --git a/accounts/casbin.go b/accounts/casbin.go index ce4a576bd..d953857a1 100644 --- a/accounts/casbin.go +++ b/accounts/casbin.go @@ -3,6 +3,7 @@ package accounts import ( "fmt" + "github.com/bmeg/grip/log" "github.com/casbin/casbin/v2" ) @@ -17,19 +18,19 @@ func (ce *CasbinAccess) init() { if e, err := casbin.NewEnforcer(ce.Model, ce.Policy); err == nil { ce.encforcer = e } else { - fmt.Printf("Casbin Error: %s", err) + log.Errorf("Casbin Error: %s", err) } } } func (ce *CasbinAccess) Enforce(user string, graph string, operation Operation) error { ce.init() - fmt.Printf("Casbin request '%s' '%s' '%s'\n", user, graph, operation) + log.Infof("Casbin request '%s' '%s' '%s'\n", user, graph, operation) if res, err := ce.encforcer.Enforce(user, graph, string(operation)); res { return nil } else if err != nil { - fmt.Printf("casbin error: %s\n", err) + log.Errorf("casbin error: %s\n", err) } - fmt.Printf("Not allowed: '%s' '%s' '%s'\n", user, graph, operation) + log.Errorf("Not allowed: '%s' '%s' '%s'\n", user, graph, operation) return fmt.Errorf("action restricted") } diff --git a/accounts/util.go b/accounts/util.go index c70a5afd7..b93377264 100644 --- a/accounts/util.go +++ b/accounts/util.go @@ -54,11 +54,11 @@ func (c *Config) StreamInterceptor() grpc.StreamServerInterceptor { // using a password stored in the config. func unaryAuthInterceptor(auth Authenticate, access Access) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - //fmt.Printf("AuthInt: %#v\n", ctx) + //log.Infof("AuthInt: %#v\n", ctx) md, _ := metadata.FromIncomingContext(ctx) - //fmt.Printf("Metadata: %#v\n", md) + //log.Infof("Metadata: %#v\n", md) //omd, _ := metadata.FromOutgoingContext(ctx) - //fmt.Printf("Raw: %#v\n", omd) + //log.Infof("Raw: %#v\n", omd) metaData := MetaData{} for i := range md { @@ -89,10 +89,10 @@ func unaryAuthInterceptor(auth Authenticate, access Access) grpc.UnaryServerInte // using a password stored in the config. func streamAuthInterceptor(auth Authenticate, access Access) grpc.StreamServerInterceptor { return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - //fmt.Printf("Streaming query: %#v\n", info) + //log.Infof("Streaming query: %#v\n", info) md, _ := metadata.FromIncomingContext(ss.Context()) - //fmt.Printf("Metadata: %#v\n", md) + //log.Infof("Metadata: %#v\n", md) metaData := MetaData{} for i := range md { metaData[i] = md[i] diff --git a/engine/core/util.go b/engine/core/util.go index 2cc616ef3..03a6ff036 100644 --- a/engine/core/util.go +++ b/engine/core/util.go @@ -1,13 +1,5 @@ package core -import ( - "github.com/kr/pretty" -) - -func debug(i ...interface{}) { - pretty.Println(i...) -} - func dedupStringSlice(s []string) []string { seen := make(map[string]struct{}, len(s)) j := 0 diff --git a/grids/new.go b/grids/new.go index 42c52441b..a5f8e6e93 100644 --- a/grids/new.go +++ b/grids/new.go @@ -9,6 +9,7 @@ import ( "github.com/bmeg/benchtop/bsontable" "github.com/bmeg/grip/gripql" + "github.com/bmeg/grip/log" "github.com/bmeg/grip/timestamp" ) @@ -42,7 +43,7 @@ func (kgraph *GDB) AddGraph(graph string) error { } func newGraph(baseDir, name string) (*Graph, error) { dbPath := filepath.Join(baseDir, name) - fmt.Printf("Creating new GRIDS graph %s\n", name) + log.Infof("Creating new GRIDS graph %s\n", name) // Create directory if it doesn't exist if _, err := os.Stat(dbPath); os.IsNotExist(err) { @@ -78,7 +79,7 @@ func newGraph(baseDir, name string) (*Graph, error) { func getGraph(baseDir, name string) (*Graph, error) { dbPath := filepath.Join(baseDir, name) - fmt.Printf("fetching GRIDS graph %s\n", name) + log.Infof("fetching GRIDS graph %s\n", name) versionPath := filepath.Join(dbPath, "VERSION") file, err := os.Open(versionPath) diff --git a/gripper/server.go b/gripper/server.go index 6feaa2cd4..0f28ff23a 100644 --- a/gripper/server.go +++ b/gripper/server.go @@ -3,9 +3,9 @@ package gripper import ( "context" "fmt" - "log" "net" + "github.com/bmeg/grip/log" "google.golang.org/grpc" "google.golang.org/protobuf/types/known/structpb" ) @@ -36,13 +36,13 @@ func NewSimpleTableServer(dr map[string]Driver) *SimpleTableServicer { func StartServer(port int, serv GRIPSourceServer) { lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port)) if err != nil { - log.Fatalf("failed to listen: %v", err) + log.Errorf("failed to listen: %v", err) } var opts []grpc.ServerOption grpcServer := grpc.NewServer(opts...) RegisterGRIPSourceServer(grpcServer, serv) - fmt.Printf("Starting: %d\n", port) + log.Infof("Starting: %d\n", port) grpcServer.Serve(lis) } @@ -97,7 +97,7 @@ func (st *SimpleTableServicer) GetRowsByID(srv GRIPSource_GetRowsByIDServer) err if err != nil { break } - log.Printf("Request: %s %s", err, req) + log.Debugf("Request: %s %s", err, req) if dr, ok := st.drivers[req.Collection]; ok { if row, err := dr.FetchRow(req.Id); err == nil { data, _ := structpb.NewStruct(row.Value) diff --git a/gripql/marshal_flattened.go b/gripql/marshal_flattened.go index da2ab6bc0..6a285a3ea 100644 --- a/gripql/marshal_flattened.go +++ b/gripql/marshal_flattened.go @@ -2,8 +2,8 @@ package gripql import ( "encoding/json" - "fmt" + "github.com/bmeg/grip/log" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" @@ -80,7 +80,7 @@ func (mflat *MarshalFlatten) Unmarshal(data []byte, v interface{}) error { } s, err := structpb.NewStruct(data) if err != nil { - fmt.Printf("NewStruct error: %s", err) + log.Errorf("NewStruct error: %s", err) } if err == nil { y.Data = s diff --git a/main.go b/main.go index df320e840..53fa06cea 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "os" "github.com/bmeg/grip/cmd" @@ -11,7 +10,7 @@ import ( func main() { log.ConfigureLogger(log.DefaultLoggerConfig()) if err := cmd.RootCmd.Execute(); err != nil { - fmt.Println("Error:", err.Error()) + log.Errorf("Error:", err.Error()) os.Exit(1) } } diff --git a/mongo/compile.go b/mongo/compile.go index 2d2eb382a..29391fe17 100644 --- a/mongo/compile.go +++ b/mongo/compile.go @@ -528,10 +528,10 @@ func (comp *Compiler) Compile(stmts []*gripql.GraphStatement, opts *gdbi.Compile keys := protoutil.AsStringList(stmt.HasKey) for _, key := range keys { lKey := ToPipelinePath(key) - fmt.Printf("Key: %s -> %s\n", key, lKey) + //log.Debugf("Key: %s -> %s\n", key, lKey) hasKeys[lKey] = bson.M{"$exists": true} } - fmt.Printf("hasKey: %#v\n", hasKeys) + //log.Debugf("hasKey: %#v\n", hasKeys) query = append(query, bson.D{primitive.E{Key: "$match", Value: hasKeys}}) case *gripql.GraphStatement_Limit: @@ -581,7 +581,7 @@ func (comp *Compiler) Compile(stmts []*gripql.GraphStatement, opts *gdbi.Compile }, }, }) - fmt.Printf("Distinct: %s\n", query) + //log.Debugf("Distinct: %s\n", query) switch lastType { case gdbi.VertexData: query = append(query, bson.D{primitive.E{Key: "$project", Value: bson.M{ diff --git a/mongo/has_evaluator.go b/mongo/has_evaluator.go index 5e08af15f..c4f644c57 100644 --- a/mongo/has_evaluator.go +++ b/mongo/has_evaluator.go @@ -1,8 +1,6 @@ package mongo import ( - "fmt" - "github.com/bmeg/grip/gripql" "github.com/bmeg/grip/log" "go.mongodb.org/mongo-driver/bson" @@ -22,7 +20,7 @@ func convertHasExpression(stmt *gripql.HasExpression, not bool) bson.M { } else { key := cond.Key output = convertHasExpression(gripql.And(gripql.Gt(key, lims[0]), gripql.Lt(key, lims[1])), not) - fmt.Printf("inside: %#v\n", output) + //log.Debugf("inside: %#v\n", output) } case gripql.Condition_OUTSIDE: diff --git a/pygrip/__init__.py b/pygrip/__init__.py deleted file mode 100644 index 33964bf1a..000000000 --- a/pygrip/__init__.py +++ /dev/null @@ -1,74 +0,0 @@ - - -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) \ No newline at end of file diff --git a/pygrip/wrapper.go b/pygrip/wrapper.go index 0657f2c81..de9f7bbaa 100644 --- a/pygrip/wrapper.go +++ b/pygrip/wrapper.go @@ -16,6 +16,7 @@ import ( "github.com/bmeg/grip/engine/core" "github.com/bmeg/grip/engine/pipeline" "github.com/bmeg/grip/gdbi" + "github.com/bmeg/grip/grids" "github.com/bmeg/grip/gripql" "github.com/bmeg/grip/kvgraph" "github.com/bmeg/grip/kvi" @@ -49,6 +50,18 @@ func NewMemServer() GraphHandle { return GraphHandle(cgo.NewHandle(g)) } +func NewGRIDServer(path string) GraphHandle { + graphDB, err := grids.NewGraphDB(path) + if err != nil { + log.Errorf("Graph init error: %s\n", err) + } + g, err := graphDB.Graph("default") + if err != nil { + log.Errorf("Graph init error: %s\n", err) + } + return GraphHandle(cgo.NewHandle(g)) +} + func CloseServer(graph GraphHandle) { cgo.Handle(graph).Delete() } diff --git a/server/api.go b/server/api.go index 44d548523..4c7a9cff2 100644 --- a/server/api.go +++ b/server/api.go @@ -663,7 +663,7 @@ func (server *GripServer) AddSchema(ctx context.Context, req *gripql.Graph) (*gr func (server *GripServer) AddJsonSchema(ctx context.Context, rawjson *gripql.RawJson) (*gripql.EditResult, error) { bytes, err := protojson.Marshal(rawjson.Data) if err != nil { - fmt.Printf("Failed to marshal data to bytes: %v\n", err) + log.Errorf("Failed to marshal data to bytes: %v\n", err) return nil, err } req, err := schema.ParseJSchema(bytes, rawjson.Graph) diff --git a/server/endpoints.go b/server/endpoints.go index 4adff8840..490e3d5ee 100644 --- a/server/endpoints.go +++ b/server/endpoints.go @@ -25,7 +25,7 @@ func (server *GripServer) AddEndpoint(name string, path string, config map[strin if err != nil { return err } - fmt.Printf("Method: %#v\n", gen) + log.Infof("Method: %#v\n", gen) if x, ok := (gen).(func(client gripql.Client, config map[string]string) (http.Handler, error)); ok { log.Infof("Plugin %s loaded", path) endpointMap[name] = x diff --git a/server/server.go b/server/server.go index 02a191655..fa2c61309 100644 --- a/server/server.go +++ b/server/server.go @@ -122,7 +122,7 @@ func NewGripServer(conf *config.Config, baseDir string, drivers map[string]gdbi. if _, ok := gdbs[conf.Default]; !ok { return nil, fmt.Errorf("default driver '%s' does not exist", conf.Default) } - fmt.Printf("Default graph driver: %s\n", conf.Default) + log.Infof("Default graph driver: %s\n", conf.Default) return server, nil } diff --git a/test/pygrip_test/fhir/test_load.py b/test/pygrip_test/fhir/test_load.py index 4b32d0b84..8ce6e087c 100644 --- a/test/pygrip_test/fhir/test_load.py +++ b/test/pygrip_test/fhir/test_load.py @@ -5,7 +5,7 @@ import pytest -import pygrip +import pygrip.pygrip as pygrip from jsonpath_ng import jsonpath, parse from typing import Generator, Dict, Any From e99e76f752f60fabed97ae935822dec6ab2850be Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Wed, 9 Apr 2025 14:39:08 -0700 Subject: [PATCH 17/17] Trying to put togeather a grpc based embedded grip --- cmd/root.go | 2 ++ pygrip/Makefile | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index b2a85c8d4..d2cacc389 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -10,6 +10,7 @@ import ( "github.com/bmeg/grip/cmd/delete" "github.com/bmeg/grip/cmd/drop" "github.com/bmeg/grip/cmd/dump" + "github.com/bmeg/grip/cmd/embedded" "github.com/bmeg/grip/cmd/erclient" "github.com/bmeg/grip/cmd/info" "github.com/bmeg/grip/cmd/job" @@ -73,6 +74,7 @@ func init() { RootCmd.AddCommand(version.Cmd) RootCmd.AddCommand(kvload.Cmd) RootCmd.AddCommand(delete.Cmd) + RootCmd.AddCommand(embedded.Cmd) } diff --git a/pygrip/Makefile b/pygrip/Makefile index e3d2cad19..dbe3ccf27 100644 --- a/pygrip/Makefile +++ b/pygrip/Makefile @@ -1,4 +1,4 @@ -pygrip.so: wrapper.go - go build -o pygrip.so -buildmode=c-shared wrapper.go +pygrip/gripql_pb2.py: ../gripql/gripql.proto + protoc --proto_path=../gripql/ --python_out=./pygrip -I ../googleapis/ ../gripql/gripql.proto \ No newline at end of file