From 68b2334cd8484ac72814060ced5703a2f94b57d7 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 23 Sep 2014 16:28:16 +0300 Subject: initial implementation --- src-py/db_controller.py | 179 ++++++++++++++++++++++++++++++++++++++++++++++++ src-py/neo4j_util.py | 49 +++++++++++++ src-py/rhizi_webapp.py | 70 +++++++++++++++++++ 3 files changed, 298 insertions(+) create mode 100644 src-py/db_controller.py create mode 100644 src-py/neo4j_util.py create mode 100644 src-py/rhizi_webapp.py (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py new file mode 100644 index 00000000..6ad77b01 --- /dev/null +++ b/src-py/db_controller.py @@ -0,0 +1,179 @@ +#!/usr/bin/python + +import os +import json +import re +import logging +import traceback + +import urllib2 + +import neo4j_util as dbu + +log = logging.getLogger('rhizi') + +class DB_op(object): + """ + tx wrapped DB operation possibly composing multiple DB queries + """ + + def __init__(self): + self.s_id = 0 # statement id counter + self.id_to_statement_map = {} # zero based id to statement map + self.tx_id = None + self.tx_commit_url = None # cached from response to tx begin + + def begin(self, tx_open_url): + try: + # + # [!] neo4j seems picky about receiving an additional empty statement list + # + data = data = dbu.statement_set_to_REST_form([]) + ret = dbu.post_neo4j(tx_open_url, data) + tx_commit_url = ret['commit'] + self.parse_tx_id(tx_commit_url) + self.tx_commit_url = tx_commit_url + + log.debug('tx-open: id: {0}, commit-url: {1}'.format(self.tx_id, tx_commit_url)) + except Exception as e: + raise Exception('failed to open transaction:' + e.message) + + def parse_tx_id(self, tx_commit_url): + m = re.search('/(?P\d+)/commit$', tx_commit_url) + id_str = m.group('id') + self.tx_id = int(id_str) + + def commit(self): + try: + # + # [!] neo4j seems picky about receiving an additional empty statement list + # + data = dbu.statement_set_to_REST_form([]) + ret = dbu.post(self.tx_commit_url, data) + except Exception as e: + raise Exception('failed to commit transaction:' + e.message) + + log.debug('tx-commit: id: {0}, commit-url: {1}'.format(self.tx_id, self.tx_commit_url)) + + def add_statement(self, cypher_query, params={}): + """ + add a DB query language statement + @return: statement id + """ + ret = self.s_id + self.id_to_statement_map[self.s_id] = dbu.statement_to_REST_form(cypher_query, params) + self.s_id = self.s_id + 1 + return ret + + @property + def statement_set(self): + return self.id_to_statement_map.values() + + def on_success(self, data): + pass + + def on_error(self): + pass + +class DBO_add_node_set(DB_op): + """ + DB op: add node set + + @param node_map: type to node list map + """ + def __init__(self, node_map): + super(DBO_add_node_set, self).__init__() + self.node_map = node_map + + for type, n_set in self.node_map.items(): + q = "create (n:{0} {{prop_dict}}) return id(n)".format(type) + for n in n_set: + # + # any translation between how we accept node data + # and how we store them should go here + # + p = {'prop_dict' : { 'name' : n['name']}} + self.add_statement(q, p) + + def on_success(self, data): + # [!] fragile - parse results + # sample input: dict: {u'errors': [], u'results': [{u'data': [{u'row': [20]}], u'columns': [u'id(n)']}]} + id_set = [] + for r in data['results']: + columns = r['columns'] + for k in r['data']: + nid = k['row'][0] + id_set.append(nid) + + log.debug('node-set added: ids: ' + str(id_set)) + return id_set + +class DBO_load_node_id_set(DB_op): + """ + load node id set, filter by type / properties + """ + def __init__(self, filter_type, filter_prop=None): + super(DBO_load_node_id_set, self).__init__() + + # build where clause if necessary + filter_prop_str = "" + if filter_prop: + filter_prop_arr = [] + for k, v in filter_prop: + v_str = str(v) + if isinstance(v, str): + # quote string values + v_str = "'{0}'".format(v_str) + filter_prop_arr.append("n.{0} = {1} and ".format(k, v_str)) + filter_prop_str = " where " + " and ".join(filter_prop_arr) + + q = "match (n:{0}){1} return id(n)".format(filter_type, filter_prop_str) + self.add_statement(q) + + def on_success(self, data): + # [!] fragile - parse results + # sample input: dict: {u'errors': [], u'results': [{u'data': [{u'row': [20]}], u'columns': [u'id(n)']}]} + id_set = [] + for r in data['results']: + columns = r['columns'] + for k in r['data']: + nid = k['row'][0] + id_set.append(nid) + + log.debug('loaded node-set: ids: ' + str(id_set)) + return id_set + +class DB_Controller: + """ + neo4j DB controller + """ + def __init__(self, config): + self.config = config + + def exec_op(self, op): + """ + execute operation within a DB transaction + """ + tx_base_url = self.config.db_base_url + '/db/data/transaction' + data = dbu.statement_set_to_REST_form(op.statement_set) + + try: + op.begin(tx_base_url) + tx_url = "{0}/{1}".format(tx_base_url, op.tx_id) + ret_tx = dbu.post_neo4j(tx_url, data) + op.commit() + return op.on_success(ret_tx) + except Exception as e: + log.error(e.message) + log.error(traceback.print_exc()) + op.on_error() + + def create_db_op(self, f_work, f_cont): + ret = DB_op(f_work, f_cont) + return ret + + def exec_cypher_query(self, q): + """ + @deprecated: use transaction based api + """ + self.post_neo4j('/db/data/cypher', {"query" : q}) diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py new file mode 100644 index 00000000..009fbe5f --- /dev/null +++ b/src-py/neo4j_util.py @@ -0,0 +1,49 @@ +""" + Utility code in speaking the neo4j REST api +""" + +import json +import urllib2 + +def post_neo4j(url, data): + """ + return dict translation of the json string returned by neo4j, raise Exception if the 'errors' key is not empty + """ + ret = post(url, data) + ret_data = json.load(ret) + + if ret_data['errors']: + raise Exception('neo4j exception: ' + str(ret_data['errors'])) + + return ret_data + +def post(url, data): + assert(isinstance(data, dict)) # make sure we're not handed json strings + + post_data_json = json.dumps(data) + + req = urllib2.Request(url) + req.add_header('User-Agent', 'rhizi-server/0.1') + req.add_header('Accept', 'application/json; charset=UTF-8') + req.add_header('Content-Type', 'application/json') + + try: + ret = urllib2.urlopen(req, post_data_json) + except urllib2.HTTPError as e: + raise Exception('post request failed: code: {0}, reason: {1}'.format(e.code, e.reason)) + + return ret + +def statement_to_REST_form(query, parameters={}): + """ + turn cypher query to neo4j json API format + """ + assert isinstance(query, str) + assert isinstance(parameters, dict) + + return {'statement' : query, 'parameters': parameters} + +def statement_set_to_REST_form(statement_set): + assert isinstance(statement_set, list) + + return {'statements': statement_set} diff --git a/src-py/rhizi_webapp.py b/src-py/rhizi_webapp.py new file mode 100644 index 00000000..b63407d6 --- /dev/null +++ b/src-py/rhizi_webapp.py @@ -0,0 +1,70 @@ +""" +Rhizi webapp +""" +import os +import json +import logging + +from flask import Flask, request, url_for +import db_controller as dbc + +cwd = os.getcwd() +app = Flask(__name__) +app.debug = True + +class Config: + """ + rhizi-server configuration + """ + + @staticmethod + def init_from_file(file_path): + ret = Config() + + with open(file_path, 'r') as f: + cfg = json.loads(f.read()) + ret.db_base_url = cfg['neo4j_url'] + + return ret + + @property + def db_base_url(self): + return self.neo4j_url + + @property + def tx_api_path(self): + return '/db/data/transaction' + +@app.route("/add-node-set") +def foo(): + pass + +def init_logging(): + global log + + log = logging.getLogger('rhizi') + log.setLevel(logging.DEBUG) + log_handler_c = logging.StreamHandler() + log_handler_f = logging.FileHandler('/tmp/rhizi-backend.log') + + log.addHandler(log_handler_c) + log.addHandler(log_handler_f) + +def test_DB_controller_api(): + db_ctl = dbc.DB_Controller(cfg) + + n_map = { 'Skill': [{'name': 'kung fu' }, + {'name': 'judo' } + ], + 'Person': [{'name': 'Bob' }, {'name': 'Alice' }] + } + + db_ctl.exec_op(dbc.DBO_add_node_set(n_map)) + id_set = db_ctl.exec_op(dbc.DBO_load_node_id_set(filter_type='Skill')) + +if __name__ == "__main__": + cfg = Config.init_from_file('res/etc/rhizi-backend.conf') + init_logging() + + test_DB_controller_api() + # app.run(host='127.0.0.1', port=rhizi_backend_cfg['port'], ssl_context=ctx) -- cgit v1.3.1 From 391d02c6770fd9b7dea5014bfa974f675388c5c6 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 24 Sep 2014 02:00:18 +0300 Subject: db_controller.DBO_add_node_set.__init__ - add optional input_to_DB_property_map property mapping argument --- src-py/db_controller.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 6ad77b01..d7492e95 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -79,20 +79,18 @@ class DBO_add_node_set(DB_op): """ DB op: add node set - @param node_map: type to node list map + @param node_map: node-type to node list map + @input_to_DB_property_map: optional function which takes a map of input properties and returns a map of DB properties - use to map input schemas to DB schemas + """ - def __init__(self, node_map): + def __init__(self, node_map, input_to_DB_property_map=lambda _: _): super(DBO_add_node_set, self).__init__() self.node_map = node_map for type, n_set in self.node_map.items(): q = "create (n:{0} {{prop_dict}}) return id(n)".format(type) - for n in n_set: - # - # any translation between how we accept node data - # and how we store them should go here - # - p = {'prop_dict' : { 'name' : n['name']}} + for n_prop_dict in n_set: + p = {'prop_dict' : input_to_DB_property_map(n_prop_dict)} self.add_statement(q, p) def on_success(self, data): @@ -140,7 +138,7 @@ class DBO_load_node_id_set(DB_op): nid = k['row'][0] id_set.append(nid) - log.debug('loaded node-set: ids: ' + str(id_set)) + log.debug('loaded node id set: ' + str(id_set)) return id_set class DB_Controller: @@ -150,18 +148,23 @@ class DB_Controller: def __init__(self, config): self.config = config + def log_committed_queries(self, statement_set): + for sp_dict in statement_set['statements']: + log.debug('\tq: {0}'.format(sp_dict['statement'])) + def exec_op(self, op): """ execute operation within a DB transaction """ tx_base_url = self.config.db_base_url + '/db/data/transaction' - data = dbu.statement_set_to_REST_form(op.statement_set) + statement_set = dbu.statement_set_to_REST_form(op.statement_set) try: op.begin(tx_base_url) tx_url = "{0}/{1}".format(tx_base_url, op.tx_id) - ret_tx = dbu.post_neo4j(tx_url, data) + ret_tx = dbu.post_neo4j(tx_url, statement_set) op.commit() + self.log_committed_queries(statement_set) return op.on_success(ret_tx) except Exception as e: log.error(e.message) -- cgit v1.3.1 From 11d9e3696691d096ed9cd5b2c41810414d960f74 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 24 Sep 2014 02:01:37 +0300 Subject: add DBO_load_node_set_by_id DB op --- src-py/db_controller.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index d7492e95..de3133a4 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -141,6 +141,20 @@ class DBO_load_node_id_set(DB_op): log.debug('loaded node id set: ' + str(id_set)) return id_set +class DBO_load_node_set_by_id(DB_op): + """ + load a set of nodes by ids + """ + + def __init__(self, id_set): + super(DBO_load_node_set_by_id, self).__init__() + q = "match (n) where n.id in {id_list} return n" + self.add_statement(q, { 'id_list': id_set}) + + def on_success(self, data): + log.debug('loaded node set: ' + str(data)) + return data + class DB_Controller: """ neo4j DB controller -- cgit v1.3.1 From c061b63a9bcc347854e1615b95eff85616b136b1 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 28 Sep 2014 13:21:26 +0300 Subject: distinguish between loading by DB id and by id attribute: - DBO_load_node_set_by_attribute - DBO_load_node_set_by_id_attribute (convenience op) --- src-py/db_controller.py | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index de3133a4..3ccc8bfd 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -76,14 +76,14 @@ class DB_op(object): pass class DBO_add_node_set(DB_op): - """ - DB op: add node set - - @param node_map: node-type to node list map - @input_to_DB_property_map: optional function which takes a map of input properties and returns a map of DB properties - use to map input schemas to DB schemas - - """ def __init__(self, node_map, input_to_DB_property_map=lambda _: _): + """ + DB op: add node set + + @param node_map: node-type to node list map + @input_to_DB_property_map: optional function which takes a map of input properties and returns a map of DB properties - use to map input schemas to DB schemas + + """ super(DBO_add_node_set, self).__init__() self.node_map = node_map @@ -107,10 +107,10 @@ class DBO_add_node_set(DB_op): return id_set class DBO_load_node_id_set(DB_op): - """ - load node id set, filter by type / properties - """ def __init__(self, filter_type, filter_prop=None): + """ + load node DB id set, filter by type / properties + """ super(DBO_load_node_id_set, self).__init__() # build where clause if necessary @@ -141,20 +141,29 @@ class DBO_load_node_id_set(DB_op): log.debug('loaded node id set: ' + str(id_set)) return id_set -class DBO_load_node_set_by_id(DB_op): - """ - load a set of nodes by ids - """ +class DBO_load_node_set_by_attribute(DB_op): - def __init__(self, id_set): - super(DBO_load_node_set_by_id, self).__init__() - q = "match (n) where n.id in {id_list} return n" - self.add_statement(q, { 'id_list': id_set}) + def __init__(self, attr_name, attr_set): + """ + load a set of nodes whose attr_name is in attr_set + + @return: loaded node set or an empty set if no match was found + """ + super(DBO_load_node_set_by_attribute, self).__init__() + q = "match (n) where n.{0} in {{attr_list}} return n".format(attr_name) + self.add_statement(q, { 'attr_list': attr_set}) def on_success(self, data): log.debug('loaded node set: ' + str(data)) return data +class DBO_load_node_set_by_id_attribute(DBO_load_node_set_by_attribute): + def __init__(self, id_set): + """ + convenience op for load a set of nodes by their 'id' attribute != DB node id + """ + super(DBO_load_node_set_by_id_attribute, self).__init__('id', id_set) + class DB_Controller: """ neo4j DB controller -- cgit v1.3.1 From a170bda6cea7810ee4fea0738b2144ac00bba1f1 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 28 Sep 2014 13:36:38 +0300 Subject: parse_single_query_response_data: assist in single query result parsing --- src-py/db_controller.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 3ccc8bfd..c9305fb9 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -69,6 +69,17 @@ class DB_op(object): def statement_set(self): return self.id_to_statement_map.values() + def parse_single_query_response_data(self, q, data): + """ + DB op can issue complex sets of quries all at once - this helper method + assists in parsing response data from a single query. + """ + ret = [] + r_0 = data['results'][0] + for row in r_0['data']: + ret.append(row['row'][0]) + return ret + def on_success(self, data): pass @@ -155,7 +166,7 @@ class DBO_load_node_set_by_attribute(DB_op): def on_success(self, data): log.debug('loaded node set: ' + str(data)) - return data + return self.parse_single_query_response_data(self.statement_set[0], data) class DBO_load_node_set_by_id_attribute(DBO_load_node_set_by_attribute): def __init__(self, id_set): -- cgit v1.3.1 From bf219dfccb9fa786d4fd1e89013db2fd095190c0 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 28 Sep 2014 14:13:54 +0300 Subject: db_controller test class --- src-py/test_db_controller.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src-py/test_db_controller.py (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py new file mode 100644 index 00000000..57403e98 --- /dev/null +++ b/src-py/test_db_controller.py @@ -0,0 +1,41 @@ +import unittest +import db_controller +import logging +import db_controller as dbc + +from rhizi_server import Config + +class TestDBController(unittest.TestCase): + + db_ctl = None + log = None + + n_map = { 'Skill': [{'name': 'kung fu', 'id': 'skill_00' }, + {'name': 'judo', 'id': 'skill_01' } + ], + 'Person': [{'name': 'Bob', 'id': 'person_00' }, + {'name': 'Alice', 'id': 'person_01' }] + } + + @classmethod + def setUpClass(self): + cfg = Config.init_from_file('res/etc/rhizi-backend.conf') + self.log = logging.getLogger('rhizi') + self.db_ctl = dbc.DB_Controller(cfg) + + def setUp(self): + self.flush_db() + + def test_node_lifecycle(self): + """ + test node commit & load + """ + + self.db_ctl.exec_op(dbc.DBO_add_node_set(self.n_map)) + n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_id_attribute(['skill_00', 'person_01'])) + self.assertEqual(len(n_set), 2, 'incorrect result size') + + def tearDown(self): pass + +if __name__ == "__main__": + unittest.main() -- cgit v1.3.1 From dd1966972b41e7fd3492f8db07b286647f39b02d Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 28 Sep 2014 14:14:35 +0300 Subject: flush_db neo4j utility --- src-py/test_db_controller.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 57403e98..6e3a42ff 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -26,6 +26,13 @@ class TestDBController(unittest.TestCase): def setUp(self): self.flush_db() + def flush_db(self): + """ + complete DB flush: remove all nodes & links + """ + self.db_ctl.exec_cypher_query('match ()-[r]-() delete r') + self.db_ctl.exec_cypher_query('match (n) delete n') + def test_node_lifecycle(self): """ test node commit & load -- cgit v1.3.1 From f93e7846097e73d5f021594af16264fe7ebdf9d0 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 28 Sep 2014 14:31:25 +0300 Subject: DBO_load_node_set_by_DB_id & test --- src-py/db_controller.py | 24 ++++++++++++++++++++---- src-py/test_db_controller.py | 8 ++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index c9305fb9..a6f738bd 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -93,7 +93,6 @@ class DBO_add_node_set(DB_op): @param node_map: node-type to node list map @input_to_DB_property_map: optional function which takes a map of input properties and returns a map of DB properties - use to map input schemas to DB schemas - """ super(DBO_add_node_set, self).__init__() self.node_map = node_map @@ -152,6 +151,21 @@ class DBO_load_node_id_set(DB_op): log.debug('loaded node id set: ' + str(id_set)) return id_set +class DBO_load_node_set_by_DB_id(DB_op): + def __init__(self, id_set): + """ + load a set of nodes whose DB id is in id_set + + @return: loaded node set or an empty set if no match was found + """ + super(DBO_load_node_set_by_DB_id, self).__init__() + q = "match (n) where id(n) in {id_set} return n" + self.add_statement(q, { 'id_set': id_set}) + + def on_success(self, data): + log.debug('loaded node set: ' + str(data)) + return self.parse_single_query_response_data(self.statement_set[0], data) + class DBO_load_node_set_by_attribute(DB_op): def __init__(self, attr_name, attr_set): @@ -161,8 +175,8 @@ class DBO_load_node_set_by_attribute(DB_op): @return: loaded node set or an empty set if no match was found """ super(DBO_load_node_set_by_attribute, self).__init__() - q = "match (n) where n.{0} in {{attr_list}} return n".format(attr_name) - self.add_statement(q, { 'attr_list': attr_set}) + q = "match (n) where n.{0} in {{attr_set}} return n".format(attr_name) + self.add_statement(q, { 'attr_set': attr_set}) def on_success(self, data): log.debug('loaded node set: ' + str(data)) @@ -213,4 +227,6 @@ class DB_Controller: """ @deprecated: use transaction based api """ - self.post_neo4j('/db/data/cypher', {"query" : q}) + + # call post and not dbu.post_neo4j to avoid response key errors + dbu.post(self.config.db_base_url + '/db/data/cypher', {"query" : q}) diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 6e3a42ff..3f4db98f 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -33,6 +33,14 @@ class TestDBController(unittest.TestCase): self.db_ctl.exec_cypher_query('match ()-[r]-() delete r') self.db_ctl.exec_cypher_query('match (n) delete n') + def test_node_DB_id_lifecycle(self): + """ + test node DB id life cycle + """ + id_set = self.db_ctl.exec_op(dbc.DBO_add_node_set(self.n_map)) + n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_DB_id(id_set)) + self.assertEqual(len(n_set), len(id_set), 'incorrect result size') + def test_node_lifecycle(self): """ test node commit & load -- cgit v1.3.1 From 267b3d2e4dec5a25afd14634c2c494f302f5c80a Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 28 Sep 2014 15:29:14 +0300 Subject: rename conf file --- src-py/test_db_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 3f4db98f..1a73cb49 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -19,7 +19,7 @@ class TestDBController(unittest.TestCase): @classmethod def setUpClass(self): - cfg = Config.init_from_file('res/etc/rhizi-backend.conf') + cfg = Config.init_from_file('res/etc/rhizi-server.conf') self.log = logging.getLogger('rhizi') self.db_ctl = dbc.DB_Controller(cfg) -- cgit v1.3.1 From 3008d35eb298b4ddc0b85a3e0114e4bc0c87fe45 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 28 Sep 2014 18:24:14 +0300 Subject: Early Rhizi API prototype: - load_node_by_id_attr() - response_wrap() --- src-py/rhizi_api.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src-py/rhizi_api.py (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py new file mode 100644 index 00000000..246768aa --- /dev/null +++ b/src-py/rhizi_api.py @@ -0,0 +1,43 @@ +""" +Rhizi web API +""" +import os +import db_controller as dbc +import json +from flask import jsonify + +from flask import Flask +from flask import request + +webapp = Flask(__name__) +webapp.debug = True + +# injected: DB controller +db_ctl = None + +def __response_wrap(data=None, error=None): + """ + wrap response data/errors as dict - this should always be used when returning + data to allow easy return of list objects, assist in error case distinction, etc. + """ + return dict(data=data, error=error) + +@webapp.route("/load/node-single", methods=['POST']) +def load_node_by_id_attr(): + """ + @return: a list containing a single node whose id attribute matches 'id' or + an empty list if the requested node is not found + @raise exception: on error + """ + node_id = request.form['id'] + op = dbc.DBO_load_node_set_by_id_attribute([node_id]) + try: + n = db_ctl.exec_op(op) + ret = __response_wrap(n) + return jsonify(ret) + except Exception as e: + return jsonify('unable to load node with id: {0}'.format(node_id)) + +@webapp.route("/add/node-single") +def add_node(n): + pass -- cgit v1.3.1 From 3bfd0208c04660539cced9a23584a4a39757e526 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 28 Sep 2014 18:25:20 +0300 Subject: TestRhiziAPI --- src-py/test_rhizi_api.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src-py/test_rhizi_api.py (limited to 'src-py') diff --git a/src-py/test_rhizi_api.py b/src-py/test_rhizi_api.py new file mode 100644 index 00000000..fe03d627 --- /dev/null +++ b/src-py/test_rhizi_api.py @@ -0,0 +1,40 @@ +import unittest +import db_controller as dbc +import rhizi_api +import json + +from rhizi_server import Config + +class TestRhiziAPI(unittest.TestCase): + + def setUp(self): + cfg = Config.init_from_file('res/etc/rhizi-server.conf') + db_ctl = dbc.DB_Controller(cfg) + rhizi_api.db_ctl = db_ctl + self.webapp = rhizi_api.webapp.test_client() + + def test_load_node_non_existing(self): + """ + test loading a non existing node + """ + node_id = 'non_existing_id' + rv = self.webapp.post('/load/node-single', data=dict(id=node_id)) + rsp = json.loads(rv.data) + data = rsp['data'] + err = rsp['error'] + self.assertEqual(None, err) + self.assertEqual(0, len(data)) + + def test_load_node_existing(self): + """ + test loading an existing node + """ + node_id = 'skill_00' + rv = self.webapp.post('/load/node-single', data=dict(id=node_id)) + n_set = json.loads(rv.data)['data'] + + self.assertEqual(1, len(n_set)) + self.assertEqual(n_set[0]['id'], node_id) + +if __name__ == "__main__": + unittest.main() -- cgit v1.3.1 From 78149303d695475be310acf245323c058e84ca6d Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 28 Sep 2014 18:30:52 +0300 Subject: __response_wrap on errror --- src-py/rhizi_api.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 246768aa..7fb988e7 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -33,11 +33,12 @@ def load_node_by_id_attr(): op = dbc.DBO_load_node_set_by_id_attribute([node_id]) try: n = db_ctl.exec_op(op) - ret = __response_wrap(n) + ret = __response_wrap(data=n) return jsonify(ret) except Exception as e: - return jsonify('unable to load node with id: {0}'.format(node_id)) + return jsonify(__response_wrap(error='unable to load node with id: {0}'.format(node_id))) -@webapp.route("/add/node-single") +@webapp.route("/add/node-single", methods=['POST']) def add_node(n): + pass -- cgit v1.3.1 From 56f72b17743b5f065be241202214b5578971e8da Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 28 Sep 2014 18:34:40 +0300 Subject: __sanitize_input stub --- src-py/rhizi_api.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 7fb988e7..3bd11ea4 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -15,6 +15,9 @@ webapp.debug = True # injected: DB controller db_ctl = None +def __sanitize_input(): + pass + def __response_wrap(data=None, error=None): """ wrap response data/errors as dict - this should always be used when returning @@ -30,6 +33,9 @@ def load_node_by_id_attr(): @raise exception: on error """ node_id = request.form['id'] + + __sanitize_input(node_id) + op = dbc.DBO_load_node_set_by_id_attribute([node_id]) try: n = db_ctl.exec_op(op) @@ -40,5 +46,9 @@ def load_node_by_id_attr(): @webapp.route("/add/node-single", methods=['POST']) def add_node(n): - + pass + +@webapp.route("/add/node-set", methods=['POST']) +def add_node_set(n_set): + op = dbc.DBO_add_node_set() pass -- cgit v1.3.1 From e65ca88e90c0828d46e4b58580695ee14c897086 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 28 Sep 2014 22:22:59 +0300 Subject: rm 'self.test_client' in favor of 'with ...' statements --- src-py/test_rhizi_api.py | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) (limited to 'src-py') diff --git a/src-py/test_rhizi_api.py b/src-py/test_rhizi_api.py index fe03d627..bbea53bf 100644 --- a/src-py/test_rhizi_api.py +++ b/src-py/test_rhizi_api.py @@ -11,30 +11,41 @@ class TestRhiziAPI(unittest.TestCase): cfg = Config.init_from_file('res/etc/rhizi-server.conf') db_ctl = dbc.DB_Controller(cfg) rhizi_api.db_ctl = db_ctl - self.webapp = rhizi_api.webapp.test_client() + + def test_add_node_set(self): + """ + add node set test + """ + node_map = { 'Skill': { 'name': 'kung-fu' } } + + with rhizi_api.webapp.test_client() as c: + req = c.post('/add/node-set', data={'node_map':node_map}) + rz_data = json.loads(req.data)['data'] def test_load_node_non_existing(self): """ - test loading a non existing node + loading a non existing node test """ node_id = 'non_existing_id' - rv = self.webapp.post('/load/node-single', data=dict(id=node_id)) - rsp = json.loads(rv.data) - data = rsp['data'] - err = rsp['error'] - self.assertEqual(None, err) - self.assertEqual(0, len(data)) + with rhizi_api.webapp.test_client() as c: + req = c.post('/load/node-single', data=dict(id=node_id)) + req_data = json.loads(req.data) + rz_data = req_data['data'] + rz_err = req_data['error'] + self.assertEqual(None, rz_err) + self.assertEqual(0, len(rz_data)) def test_load_node_existing(self): """ - test loading an existing node + loading an existing node test """ node_id = 'skill_00' - rv = self.webapp.post('/load/node-single', data=dict(id=node_id)) - n_set = json.loads(rv.data)['data'] - - self.assertEqual(1, len(n_set)) - self.assertEqual(n_set[0]['id'], node_id) + with rhizi_api.webapp.test_client() as c: + req = c.post('/load/node-single', data=dict(id=node_id)) + n_set = json.loads(req.data)['data'] + + self.assertEqual(1, len(n_set)) + self.assertEqual(n_set[0]['id'], node_id) if __name__ == "__main__": unittest.main() -- cgit v1.3.1 From 25e6bbb5e0796cf9a382e252cc47834fa8415014 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 28 Sep 2014 23:34:33 +0300 Subject: DBO_add_node_set arg type sanity checking --- src-py/db_controller.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index a6f738bd..8adcc9eb 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -69,7 +69,7 @@ class DB_op(object): def statement_set(self): return self.id_to_statement_map.values() - def parse_single_query_response_data(self, q, data): + def extract_single_query_response_data(self, q, data): """ DB op can issue complex sets of quries all at once - this helper method assists in parsing response data from a single query. @@ -95,6 +95,11 @@ class DBO_add_node_set(DB_op): @input_to_DB_property_map: optional function which takes a map of input properties and returns a map of DB properties - use to map input schemas to DB schemas """ super(DBO_add_node_set, self).__init__() + + for k, v in node_map.iteritems(): # do some type sanity checking + assert isinstance(k, str) + assert isinstance(v, list) + self.node_map = node_map for type, n_set in self.node_map.items(): @@ -164,7 +169,7 @@ class DBO_load_node_set_by_DB_id(DB_op): def on_success(self, data): log.debug('loaded node set: ' + str(data)) - return self.parse_single_query_response_data(self.statement_set[0], data) + return self.extract_single_query_response_data(self.statement_set[0], data) class DBO_load_node_set_by_attribute(DB_op): @@ -174,19 +179,23 @@ class DBO_load_node_set_by_attribute(DB_op): @return: loaded node set or an empty set if no match was found """ + assert isinstance(attr_set, list) + super(DBO_load_node_set_by_attribute, self).__init__() q = "match (n) where n.{0} in {{attr_set}} return n".format(attr_name) self.add_statement(q, { 'attr_set': attr_set}) def on_success(self, data): log.debug('loaded node set: ' + str(data)) - return self.parse_single_query_response_data(self.statement_set[0], data) + return self.extract_single_query_response_data(self.statement_set[0], data) class DBO_load_node_set_by_id_attribute(DBO_load_node_set_by_attribute): def __init__(self, id_set): """ convenience op for load a set of nodes by their 'id' attribute != DB node id """ + assert isinstance(id_set, list) + super(DBO_load_node_set_by_id_attribute, self).__init__('id', id_set) class DB_Controller: -- cgit v1.3.1 From c4dab7675a438781a36fb70e769ed7b911f9ef25 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 28 Sep 2014 23:50:06 +0300 Subject: type check against 'basestring' rather than str, tagging with: [python2] --- src-py/neo4j_util.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 009fbe5f..4c166bfe 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -7,7 +7,8 @@ import urllib2 def post_neo4j(url, data): """ - return dict translation of the json string returned by neo4j, raise Exception if the 'errors' key is not empty + @return dict object from the neo4j json POST response + @raise exception: if the 'errors' key is not empty """ ret = post(url, data) ret_data = json.load(ret) @@ -38,7 +39,7 @@ def statement_to_REST_form(query, parameters={}): """ turn cypher query to neo4j json API format """ - assert isinstance(query, str) + assert isinstance(query, basestring) assert isinstance(parameters, dict) return {'statement' : query, 'parameters': parameters} -- cgit v1.3.1 From 98354ad1d1e3044b928f73b51b9408501adc1880 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 28 Sep 2014 23:55:52 +0300 Subject: cleanup test_add_node_set --- src-py/test_rhizi_api.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'src-py') diff --git a/src-py/test_rhizi_api.py b/src-py/test_rhizi_api.py index bbea53bf..102d0cc3 100644 --- a/src-py/test_rhizi_api.py +++ b/src-py/test_rhizi_api.py @@ -4,6 +4,8 @@ import rhizi_api import json from rhizi_server import Config +from werkzeug.test import EnvironBuilder +from werkzeug.test import Client class TestRhiziAPI(unittest.TestCase): @@ -16,11 +18,13 @@ class TestRhiziAPI(unittest.TestCase): """ add node set test """ - node_map = { 'Skill': { 'name': 'kung-fu' } } - + node_map = { 'Skill': [{ 'name': 'kung-fu' }, { 'name': 'judo' }] } with rhizi_api.webapp.test_client() as c: - req = c.post('/add/node-set', data={'node_map':node_map}) - rz_data = json.loads(req.data)['data'] + req = c.post('/add/node-set', content_type='application/json', + data=json.dumps(dict(node_map=node_map))) + id_set = json.loads(req.data)['data'] + self.assertEqual(2, len(id_set)) + self.assertTrue(isinstance(id_set[0], int)) def test_load_node_non_existing(self): """ -- cgit v1.3.1 From 064131c6254178446dd780f2dbc26af8836d65fc Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 29 Sep 2014 17:07:45 +0300 Subject: switch to app/json post argument content type --- src-py/db_controller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 8adcc9eb..67bb64d1 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -97,7 +97,7 @@ class DBO_add_node_set(DB_op): super(DBO_add_node_set, self).__init__() for k, v in node_map.iteritems(): # do some type sanity checking - assert isinstance(k, str) + assert isinstance(k, basestring) assert isinstance(v, list) self.node_map = node_map @@ -134,7 +134,7 @@ class DBO_load_node_id_set(DB_op): filter_prop_arr = [] for k, v in filter_prop: v_str = str(v) - if isinstance(v, str): + if isinstance(v, basestring): # quote string values v_str = "'{0}'".format(v_str) filter_prop_arr.append("n.{0} = {1} and ".format(k, v_str)) -- cgit v1.3.1 From 0c7aa6f772c2a1352a2b643885baa9085886bfec Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 29 Sep 2014 17:19:12 +0300 Subject: extend Flask class to allow customization of HTTP OPTIONS method response --- src-py/rhizi_api.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 3bd11ea4..c8a323df 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -8,8 +8,22 @@ from flask import jsonify from flask import Flask from flask import request +from flask import make_response -webapp = Flask(__name__) + +class FlaskExt(Flask): + """ + Flask server customization + """ + + def make_default_options_response(self): + # sup = super(Flask, self) + ret = Flask.make_default_options_response(self) + ret.headers['Access-Control-Allow-Origin'] = '*' + ret.headers['Access-Control-Allow-Headers'] = "Origin, Content-Type, Accept, Authorization" + return ret + +webapp = FlaskExt(__name__) webapp.debug = True # injected: DB controller -- cgit v1.3.1 From 734c6f39e94600b90b417bd894503e00231d85af Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 29 Sep 2014 17:28:49 +0300 Subject: more response handling with __common_resp_handle() --- src-py/rhizi_api.py | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index c8a323df..eb76212e 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -39,6 +39,17 @@ def __response_wrap(data=None, error=None): """ return dict(data=data, error=error) +def __common_resp_handle(data=None, error=None): + """ + provide common response handling + """ + ret_data = __response_wrap(data, error) + resp = jsonify(ret_data) + + # more response processing + + return resp + @webapp.route("/load/node-single", methods=['POST']) def load_node_by_id_attr(): """ -- cgit v1.3.1 From 783aad986c91bf936fb128818b171439b160596c Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 29 Sep 2014 17:29:44 +0300 Subject: load_node_by_id_attr -> load_node_single_by_id_attr (unimplemented currently), load_node_set_by_id_attr --- src-py/rhizi_api.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index eb76212e..5a1a4845 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -51,23 +51,30 @@ def __common_resp_handle(data=None, error=None): return resp @webapp.route("/load/node-single", methods=['POST']) -def load_node_by_id_attr(): +def load_node_single_by_id_attr(): + assert False + +@webapp.route("/load/node-set", methods=['POST']) +def load_node_set_by_id_attr(): """ + @param id_set: list of node ids to match id attribute against @return: a list containing a single node whose id attribute matches 'id' or an empty list if the requested node is not found @raise exception: on error """ - node_id = request.form['id'] - - __sanitize_input(node_id) - - op = dbc.DBO_load_node_set_by_id_attribute([node_id]) + id_set = request.get_json()['id_set'] + __sanitize_input(id_set) + + return __load_node_set_by_id_attr_common(id_set) + +def __load_node_set_by_id_attr_common(id_set): + op = dbc.DBO_load_node_set_by_id_attribute(id_set) try: - n = db_ctl.exec_op(op) - ret = __response_wrap(data=n) - return jsonify(ret) + n_set = db_ctl.exec_op(op) + return __common_resp_handle(data=n_set) except Exception as e: - return jsonify(__response_wrap(error='unable to load node with id: {0}'.format(node_id))) + log.exception(e) + return __common_resp_handle(error='unable to load node with ids: {0}'.format(id_set)) @webapp.route("/add/node-single", methods=['POST']) def add_node(n): -- cgit v1.3.1 From 84a41242f0256db723842fec9521c0f8d2f79103 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 29 Sep 2014 19:47:33 +0300 Subject: deffer support for single object API operations --- src-py/rhizi_api.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 5a1a4845..3a31ab11 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -52,6 +52,7 @@ def __common_resp_handle(data=None, error=None): @webapp.route("/load/node-single", methods=['POST']) def load_node_single_by_id_attr(): + # pending decision regarding support for single object operations assert False @webapp.route("/load/node-set", methods=['POST']) @@ -77,8 +78,9 @@ def __load_node_set_by_id_attr_common(id_set): return __common_resp_handle(error='unable to load node with ids: {0}'.format(id_set)) @webapp.route("/add/node-single", methods=['POST']) -def add_node(n): - pass +def add_node(): + # pending decision regarding support for single object operations + assert False @webapp.route("/add/node-set", methods=['POST']) def add_node_set(n_set): -- cgit v1.3.1 From 1a4607c092168bfc1333ad6048a62a58a116d908 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 29 Sep 2014 19:50:13 +0300 Subject: add_node_set --- src-py/rhizi_api.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 3a31ab11..7e001275 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -83,6 +83,17 @@ def add_node(): assert False @webapp.route("/add/node-set", methods=['POST']) -def add_node_set(n_set): - op = dbc.DBO_add_node_set() - pass +def add_node_set(): + """ + @param node_map: node type to node map, eg. { 'Skill': { 'name': 'kung-fu' } } + """ + node_map = request.get_json()['node_map'] + __sanitize_input(node_map) + + op = dbc.DBO_add_node_set(node_map) + try: + n_set = db_ctl.exec_op(op) + return __common_resp_handle(n_set) + except Exception as e: + return __common_resp_handle('exception raised: add_node_set') + -- cgit v1.3.1 From 97d9e83236f28b427ff45d0d6ffa8d3a5ce2333a Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 29 Sep 2014 19:50:49 +0300 Subject: test logging - will move to a super class later --- src-py/test_rhizi_api.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src-py') diff --git a/src-py/test_rhizi_api.py b/src-py/test_rhizi_api.py index 102d0cc3..266e0153 100644 --- a/src-py/test_rhizi_api.py +++ b/src-py/test_rhizi_api.py @@ -14,6 +14,14 @@ class TestRhiziAPI(unittest.TestCase): db_ctl = dbc.DB_Controller(cfg) rhizi_api.db_ctl = db_ctl + @classmethod + def setUpClass(self): + # TODO extract to superclass + log = logging.getLogger('rhizi') + log.setLevel(logging.DEBUG) + log_handler_c = logging.StreamHandler() + log.addHandler(log_handler_c) + def test_add_node_set(self): """ add node set test -- cgit v1.3.1 From e733285fcd4c68f7ba194848cc990362bb13bb49 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 29 Sep 2014 19:51:12 +0300 Subject: adapt tests to API changes --- src-py/test_rhizi_api.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'src-py') diff --git a/src-py/test_rhizi_api.py b/src-py/test_rhizi_api.py index 266e0153..6faab51c 100644 --- a/src-py/test_rhizi_api.py +++ b/src-py/test_rhizi_api.py @@ -2,6 +2,7 @@ import unittest import db_controller as dbc import rhizi_api import json +import logging from rhizi_server import Config from werkzeug.test import EnvironBuilder @@ -28,7 +29,8 @@ class TestRhiziAPI(unittest.TestCase): """ node_map = { 'Skill': [{ 'name': 'kung-fu' }, { 'name': 'judo' }] } with rhizi_api.webapp.test_client() as c: - req = c.post('/add/node-set', content_type='application/json', + req = c.post('/add/node-set', + content_type='application/json', data=json.dumps(dict(node_map=node_map))) id_set = json.loads(req.data)['data'] self.assertEqual(2, len(id_set)) @@ -38,9 +40,11 @@ class TestRhiziAPI(unittest.TestCase): """ loading a non existing node test """ - node_id = 'non_existing_id' + id_set = ['non_existing_id'] with rhizi_api.webapp.test_client() as c: - req = c.post('/load/node-single', data=dict(id=node_id)) + req = c.post('/load/node-set', + content_type='application/json', + data=json.dumps({ 'id_set': id_set})) req_data = json.loads(req.data) rz_data = req_data['data'] rz_err = req_data['error'] @@ -51,13 +55,15 @@ class TestRhiziAPI(unittest.TestCase): """ loading an existing node test """ - node_id = 'skill_00' + id_set = ['skill_00'] with rhizi_api.webapp.test_client() as c: - req = c.post('/load/node-single', data=dict(id=node_id)) + req = c.post('/load/node-set', + content_type='application/json', + data=json.dumps({ 'id_set': id_set})) n_set = json.loads(req.data)['data'] self.assertEqual(1, len(n_set)) - self.assertEqual(n_set[0]['id'], node_id) + self.assertEqual(n_set[0]['id'], id_set[0]) if __name__ == "__main__": unittest.main() -- cgit v1.3.1 From 410163f4c55208d2edf527e677eae532bf59e0aa Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 29 Sep 2014 19:51:40 +0300 Subject: misc --- src-py/rhizi_api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 7e001275..0adf6bc3 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -4,12 +4,14 @@ Rhizi web API import os import db_controller as dbc import json +import logging from flask import jsonify from flask import Flask from flask import request from flask import make_response +log = logging.getLogger('rhizi') class FlaskExt(Flask): """ @@ -29,7 +31,7 @@ webapp.debug = True # injected: DB controller db_ctl = None -def __sanitize_input(): +def __sanitize_input(*args, **kw_args): pass def __response_wrap(data=None, error=None): -- cgit v1.3.1 From 78bf830f0a48fa3dd9d2e956b94eebf1d0eac7b2 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 5 Oct 2014 20:50:58 +0200 Subject: DB_op refactor: mv begin(), commit() functions to db_controller bringing us closer towards op reusability with both REST/Embedded DB instances --- src-py/db_controller.py | 82 ++++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 35 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 67bb64d1..309ba823 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -23,38 +23,11 @@ class DB_op(object): self.tx_id = None self.tx_commit_url = None # cached from response to tx begin - def begin(self, tx_open_url): - try: - # - # [!] neo4j seems picky about receiving an additional empty statement list - # - data = data = dbu.statement_set_to_REST_form([]) - ret = dbu.post_neo4j(tx_open_url, data) - tx_commit_url = ret['commit'] - self.parse_tx_id(tx_commit_url) - self.tx_commit_url = tx_commit_url - - log.debug('tx-open: id: {0}, commit-url: {1}'.format(self.tx_id, tx_commit_url)) - except Exception as e: - raise Exception('failed to open transaction:' + e.message) - def parse_tx_id(self, tx_commit_url): m = re.search('/(?P\d+)/commit$', tx_commit_url) id_str = m.group('id') self.tx_id = int(id_str) - def commit(self): - try: - # - # [!] neo4j seems picky about receiving an additional empty statement list - # - data = dbu.statement_set_to_REST_form([]) - ret = dbu.post(self.tx_commit_url, data) - except Exception as e: - raise Exception('failed to commit transaction:' + e.message) - - log.debug('tx-commit: id: {0}, commit-url: {1}'.format(self.tx_id, self.tx_commit_url)) - def add_statement(self, cypher_query, params={}): """ add a DB query language statement @@ -204,24 +177,63 @@ class DB_Controller: """ def __init__(self, config): self.config = config + self.tx_base_url = self.config.db_base_url + '/db/data/transaction' def log_committed_queries(self, statement_set): for sp_dict in statement_set['statements']: log.debug('\tq: {0}'.format(sp_dict['statement'])) + def __begin_tx(self, op): + tx_open_url = self.tx_base_url + + try: + # + # [!] neo4j seems picky about receiving an additional empty statement list + # + data = data = dbu.statement_set_to_REST_form([]) + ret = dbu.post_neo4j(tx_open_url, data) + tx_commit_url = ret['commit'] + op.parse_tx_id(tx_commit_url) + + log.debug('tx-open: id: {0}, commit-url: {1}'.format(op.tx_id, tx_commit_url)) + except Exception as e: + raise Exception('failed to open transaction:' + e.message) + + def __exex_op_statements(self, op): + tx_url = "{0}/{1}".format(self.tx_base_url, op.tx_id) + statement_set = dbu.statement_set_to_REST_form(op.statement_set) + + try: + ret = dbu.post_neo4j(tx_url, statement_set) + self.log_committed_queries(statement_set) + return ret + except Exception as e: + raise Exception('failed exec op statements: err: {0}, url: {1}'.format(e.message, tx_url)) + + def __commit_tx(self, op): + tx_commit_url = "{0}/{1}/commit".format(self.tx_base_url, op.tx_id) + + try: + # + # [!] neo4j seems picky about receiving an additional empty statement list + # + data = dbu.statement_set_to_REST_form([]) + ret = dbu.post(tx_commit_url, data) + + log.debug('tx-commit: id: {0}, commit-url: {1}'.format(op.tx_id, tx_commit_url)) + + return ret + except Exception as e: + raise Exception('failed to commit transaction:' + e.message) + def exec_op(self, op): """ execute operation within a DB transaction """ - tx_base_url = self.config.db_base_url + '/db/data/transaction' - statement_set = dbu.statement_set_to_REST_form(op.statement_set) - try: - op.begin(tx_base_url) - tx_url = "{0}/{1}".format(tx_base_url, op.tx_id) - ret_tx = dbu.post_neo4j(tx_url, statement_set) - op.commit() - self.log_committed_queries(statement_set) + self.__begin_tx(op) + ret_tx = self.__exex_op_statements(op) + ret_commit = self.__commit_tx(op) return op.on_success(ret_tx) except Exception as e: log.error(e.message) -- cgit v1.3.1 From 82f927d4ca2ca671afccc3a5efd1e73e2e89e9ca Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 5 Oct 2014 20:51:57 +0200 Subject: fix DBO_load_node_set_by_attribute filter query part construction --- src-py/db_controller.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 309ba823..04312eaa 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -146,17 +146,32 @@ class DBO_load_node_set_by_DB_id(DB_op): class DBO_load_node_set_by_attribute(DB_op): - def __init__(self, attr_name, attr_set): + def __init__(self, filter_attr_map): """ - load a set of nodes whose attr_name is in attr_set + load a set of nodes according to filter_attr_map + @param filter_attr_map: is a filter_key to filter_value_set map of + attributes to match against, eg.: + { 'id':[0,1], 'color: ['red','blue'] } @return: loaded node set or an empty set if no match was found """ - assert isinstance(attr_set, list) - + + # type sanity checks + assert isinstance(filter_attr_map, dict) + assert len(filter_attr_map) > 0 + for k, v in filter_attr_map.items(): + assert isinstance(k, basestring) + assert isinstance(v, list) + + filter_arr = [] + for k, v in filter_attr_map.items(): + f_attr = "n.{0} in {1}".format(k, v) + filter_arr.append(f_attr) + filter_str = "where {0}".format(' and '.join(filter_arr)) + super(DBO_load_node_set_by_attribute, self).__init__() - q = "match (n) where n.{0} in {{attr_set}} return n".format(attr_name) - self.add_statement(q, { 'attr_set': attr_set}) + q = "match (n) {0} return n".format(filter_str) + self.add_statement(q, { 'attr_set': filter_str}) def on_success(self, data): log.debug('loaded node set: ' + str(data)) -- cgit v1.3.1 From c1ff46d12a3a9d43d84bb337fa1795d0795a7a15 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 5 Oct 2014 20:53:23 +0200 Subject: misc - switch to iteritems() --- src-py/db_controller.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 04312eaa..d72c7bc4 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -68,11 +68,11 @@ class DBO_add_node_set(DB_op): @input_to_DB_property_map: optional function which takes a map of input properties and returns a map of DB properties - use to map input schemas to DB schemas """ super(DBO_add_node_set, self).__init__() - - for k, v in node_map.iteritems(): # do some type sanity checking + + for k, v in node_map.iteritems(): # do some type sanity checking assert isinstance(k, basestring) assert isinstance(v, list) - + self.node_map = node_map for type, n_set in self.node_map.items(): -- cgit v1.3.1 From 4cce2e3737a78ffca41ada15b3f0d38968b60900 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 5 Oct 2014 20:54:00 +0200 Subject: adapt DBO_load_node_set_by_id_attribute to DBO_load_node_set_by_attribute --- src-py/db_controller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index d72c7bc4..2c3eb873 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -180,11 +180,11 @@ class DBO_load_node_set_by_attribute(DB_op): class DBO_load_node_set_by_id_attribute(DBO_load_node_set_by_attribute): def __init__(self, id_set): """ - convenience op for load a set of nodes by their 'id' attribute != DB node id + convenience op: load a set of nodes by their 'id' attribute != DB node id """ assert isinstance(id_set, list) - super(DBO_load_node_set_by_id_attribute, self).__init__('id', id_set) + super(DBO_load_node_set_by_id_attribute, self).__init__({'id': id_set}) class DB_Controller: """ -- cgit v1.3.1 From 4c1e833bf2ae393b6f47bb9a363538119691230c Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 5 Oct 2014 20:55:19 +0200 Subject: add int (non-string) node properties --- src-py/test_db_controller.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 1a73cb49..83818ee3 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -10,11 +10,13 @@ class TestDBController(unittest.TestCase): db_ctl = None log = None - n_map = { 'Skill': [{'name': 'kung fu', 'id': 'skill_00' }, - {'name': 'judo', 'id': 'skill_01' } - ], - 'Person': [{'name': 'Bob', 'id': 'person_00' }, - {'name': 'Alice', 'id': 'person_01' }] + n_map = { 'Skill': [{'name': 'Kung Fu', 'id': 'skill_00' }, + {'name': 'Judo', 'id': 'skill_01' } + ], + + 'Person': [{'name': 'Bob', 'id': 'person_00', 'age': 128 }, + {'name': 'Alice', 'id': 'person_01', 'age': 256 } + ] } @classmethod -- cgit v1.3.1 From 792d23d1670c8796ab2dad61922be7914e5349d0 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 5 Oct 2014 20:55:55 +0200 Subject: add node insertion to test setup --- src-py/test_db_controller.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 83818ee3..3c8f4aff 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -27,13 +27,14 @@ class TestDBController(unittest.TestCase): def setUp(self): self.flush_db() + self.db_ctl.exec_op(dbc.DBO_add_node_set(self.n_map)) def flush_db(self): """ complete DB flush: remove all nodes & links """ - self.db_ctl.exec_cypher_query('match ()-[r]-() delete r') - self.db_ctl.exec_cypher_query('match (n) delete n') + self.db_ctl.exec_cypher_query('match (n) optional match (n)-[r]-() delete n,r') + def test_node_DB_id_lifecycle(self): """ -- cgit v1.3.1 From 38d261158f5b0276d8db6df83c854bcb0340fe17 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 5 Oct 2014 20:56:09 +0200 Subject: fix db_controller tests --- src-py/test_db_controller.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 3c8f4aff..d268daf7 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -35,6 +35,15 @@ class TestDBController(unittest.TestCase): """ self.db_ctl.exec_cypher_query('match (n) optional match (n)-[r]-() delete n,r') + def test_load_node_set_by_attribute(self): + filter_map = { 'name': ['Bob', 'Judo'], + 'age': [128] } + n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_attribute(filter_map)) + self.assertEqual(len(n_set), 1) + + def test_load_node_set_by_id_attribute(self): + n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_id_attribute(['skill_00', 'person_01'])) + self.assertEqual(len(n_set), 2) def test_node_DB_id_lifecycle(self): """ @@ -44,14 +53,7 @@ class TestDBController(unittest.TestCase): n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_DB_id(id_set)) self.assertEqual(len(n_set), len(id_set), 'incorrect result size') - def test_node_lifecycle(self): - """ - test node commit & load - """ - - self.db_ctl.exec_op(dbc.DBO_add_node_set(self.n_map)) - n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_id_attribute(['skill_00', 'person_01'])) - self.assertEqual(len(n_set), 2, 'incorrect result size') + def test_load_node_set_by_DB_id(self): pass def tearDown(self): pass -- cgit v1.3.1 From 2b67aabc0c34d93e91b847731fb5b5435f560255 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 5 Oct 2014 20:58:39 +0200 Subject: harden test_load_node_set_by_attribute() --- src-py/test_db_controller.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index d268daf7..f770905e 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -40,6 +40,10 @@ class TestDBController(unittest.TestCase): 'age': [128] } n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_attribute(filter_map)) self.assertEqual(len(n_set), 1) + + filter_map = { 'age': [128, 256, 404] } + n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_attribute(filter_map)) + self.assertEqual(len(n_set), 2) def test_load_node_set_by_id_attribute(self): n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_id_attribute(['skill_00', 'person_01'])) -- cgit v1.3.1 From c52c99169826846bc73d53b189c753507e91a4c9 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 5 Oct 2014 21:13:52 +0200 Subject: refactor DB_Driver_REST, add DB_Driver_Embedded stub --- src-py/db_controller.py | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 2c3eb873..aaed2977 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -186,19 +186,11 @@ class DBO_load_node_set_by_id_attribute(DBO_load_node_set_by_attribute): super(DBO_load_node_set_by_id_attribute, self).__init__({'id': id_set}) -class DB_Controller: - """ - neo4j DB controller - """ - def __init__(self, config): - self.config = config - self.tx_base_url = self.config.db_base_url + '/db/data/transaction' +class DB_Driver_REST: + def __init__(self, db_base_url): + self.tx_base_url = db_base_url + '/db/data/transaction' - def log_committed_queries(self, statement_set): - for sp_dict in statement_set['statements']: - log.debug('\tq: {0}'.format(sp_dict['statement'])) - - def __begin_tx(self, op): + def begin_tx(self, op): tx_open_url = self.tx_base_url try: @@ -214,7 +206,7 @@ class DB_Controller: except Exception as e: raise Exception('failed to open transaction:' + e.message) - def __exex_op_statements(self, op): + def exex_op_statements(self, op): tx_url = "{0}/{1}".format(self.tx_base_url, op.tx_id) statement_set = dbu.statement_set_to_REST_form(op.statement_set) @@ -225,7 +217,7 @@ class DB_Controller: except Exception as e: raise Exception('failed exec op statements: err: {0}, url: {1}'.format(e.message, tx_url)) - def __commit_tx(self, op): + def commit_tx(self, op): tx_commit_url = "{0}/{1}/commit".format(self.tx_base_url, op.tx_id) try: @@ -241,14 +233,31 @@ class DB_Controller: except Exception as e: raise Exception('failed to commit transaction:' + e.message) + def log_committed_queries(self, statement_set): + for sp_dict in statement_set['statements']: + log.debug('\tq: {0}'.format(sp_dict['statement'])) + +class DB_Driver_Embedded: + pass + +class DB_Controller: + """ + neo4j DB controller + """ + def __init__(self, config, db_driver=None): + self.config = config + if not db_driver: + db_driver = DB_Driver_REST(self.config.db_base_url) + self.db_driver = db_driver + def exec_op(self, op): """ execute operation within a DB transaction """ try: - self.__begin_tx(op) - ret_tx = self.__exex_op_statements(op) - ret_commit = self.__commit_tx(op) + self.db_driver.begin_tx(op) + ret_tx = self.db_driver.exex_op_statements(op) + ret_commit = self.db_driver.commit_tx(op) return op.on_success(ret_tx) except Exception as e: log.error(e.message) -- cgit v1.3.1 From 8033e138410d3440ef0e76ed2baae515d4ec989f Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 6 Oct 2014 16:04:15 +0300 Subject: migrate towards testing via embedded DB, remove DB initialization --- src-py/test_db_controller.py | 7 ------- 1 file changed, 7 deletions(-) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index f770905e..65f0231f 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -26,15 +26,8 @@ class TestDBController(unittest.TestCase): self.db_ctl = dbc.DB_Controller(cfg) def setUp(self): - self.flush_db() self.db_ctl.exec_op(dbc.DBO_add_node_set(self.n_map)) - def flush_db(self): - """ - complete DB flush: remove all nodes & links - """ - self.db_ctl.exec_cypher_query('match (n) optional match (n)-[r]-() delete n,r') - def test_load_node_set_by_attribute(self): filter_map = { 'name': ['Bob', 'Judo'], 'age': [128] } -- cgit v1.3.1 From c176cbea614d7e4d93f16c1479b97fd2641db12b Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 6 Oct 2014 17:29:59 +0300 Subject: use cypher query parameter place-holders: - each attribute filter set in passed as a query parameter - avoid string encoding issues when attempting non-parameterized queries --- src-py/db_controller.py | 52 ++++++++++++++++++++++++++++++++++++-------- src-py/test_db_controller.py | 12 +++++----- 2 files changed, 50 insertions(+), 14 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index aaed2977..93e1c553 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -38,6 +38,12 @@ class DB_op(object): self.s_id = self.s_id + 1 return ret + def __iter__(self): + # TODO impl + pass + # for k, v in self.id_to_statement_map: + # yield {k, v, None} + @property def statement_set(self): return self.id_to_statement_map.values() @@ -165,13 +171,16 @@ class DBO_load_node_set_by_attribute(DB_op): filter_arr = [] for k, v in filter_attr_map.items(): - f_attr = "n.{0} in {1}".format(k, v) + # create a cypher query parameter place holder for each attr set + # eg. n.foo in {foo}, where foo is passed as a query parameter + f_attr = "n.{0} in {{{0}}}".format(k, v) filter_arr.append(f_attr) + filter_str = "where {0}".format(' and '.join(filter_arr)) super(DBO_load_node_set_by_attribute, self).__init__() q = "match (n) {0} return n".format(filter_str) - self.add_statement(q, { 'attr_set': filter_str}) + self.add_statement(q, params=filter_attr_map) def on_success(self, data): log.debug('loaded node set: ' + str(data)) @@ -186,7 +195,10 @@ class DBO_load_node_set_by_id_attribute(DBO_load_node_set_by_attribute): super(DBO_load_node_set_by_id_attribute, self).__init__({'id': id_set}) -class DB_Driver_REST: +class DB_Driver_Base(): + pass + +class DB_Driver_REST(DB_Driver_Base): def __init__(self, db_base_url): self.tx_base_url = db_base_url + '/db/data/transaction' @@ -237,18 +249,40 @@ class DB_Driver_REST: for sp_dict in statement_set['statements']: log.debug('\tq: {0}'.format(sp_dict['statement'])) -class DB_Driver_Embedded: - pass +class DB_Driver_Embedded(DB_Driver_Base): + def __init__(self, db_base_url): + self.tx_base_url = db_base_url + '/db/data/transaction' + + from org.rhizi.db.neo4j.util import EmbeddedNeo4j + self.edb = EmbeddedNeo4j.createDb() + self.edb.createDb() + + def begin_tx(self, op): + pass + + def exex_op_statements(self, op): + s_set = op.statement_set + self.edb.executeCypherQury() + + def commit_tx(self, op): + pass + + def log_committed_queries(self, statement_set): + for sp_dict in statement_set['statements']: + log.debug('\tq: {0}'.format(sp_dict['statement'])) + class DB_Controller: """ neo4j DB controller """ - def __init__(self, config, db_driver=None): + def __init__(self, config, db_driver_class=None): self.config = config - if not db_driver: - db_driver = DB_Driver_REST(self.config.db_base_url) - self.db_driver = db_driver + if not db_driver_class: + self.db_driver = DB_Driver_REST(self.config.db_base_url) + else: + self.db_driver = db_driver_class() + assert isinstance(self.db_driver, DB_Driver_Base) def exec_op(self, op): """ diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 65f0231f..f64c47ce 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -22,18 +22,19 @@ class TestDBController(unittest.TestCase): @classmethod def setUpClass(self): cfg = Config.init_from_file('res/etc/rhizi-server.conf') - self.log = logging.getLogger('rhizi') self.db_ctl = dbc.DB_Controller(cfg) + self.db_ctl.exec_op(dbc.DBO_add_node_set(self.n_map)) + self.log = logging.getLogger('rhizi') def setUp(self): - self.db_ctl.exec_op(dbc.DBO_add_node_set(self.n_map)) + pass def test_load_node_set_by_attribute(self): - filter_map = { 'name': ['Bob', 'Judo'], + filter_map = { 'name': ['Bob', u'Judo'], 'age': [128] } n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_attribute(filter_map)) self.assertEqual(len(n_set), 1) - + filter_map = { 'age': [128, 256, 404] } n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_attribute(filter_map)) self.assertEqual(len(n_set), 2) @@ -46,7 +47,8 @@ class TestDBController(unittest.TestCase): """ test node DB id life cycle """ - id_set = self.db_ctl.exec_op(dbc.DBO_add_node_set(self.n_map)) + id_set = self.db_ctl.exec_op(dbc.DBO_add_node_set({'Person': [{'name': 'John Doe', 'id': 'jdoe_00'}, + {'name': 'John Doe', 'id': 'jdoe_01'}]})) n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_DB_id(id_set)) self.assertEqual(len(n_set), len(id_set), 'incorrect result size') -- cgit v1.3.1 From 8fa7ddf12eb814d649f74232a36bea23b91ffa3f Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 6 Oct 2014 17:37:26 +0300 Subject: fix REST query path --- src-py/test_rhizi_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/test_rhizi_api.py b/src-py/test_rhizi_api.py index 6faab51c..9749dfe9 100644 --- a/src-py/test_rhizi_api.py +++ b/src-py/test_rhizi_api.py @@ -42,7 +42,7 @@ class TestRhiziAPI(unittest.TestCase): """ id_set = ['non_existing_id'] with rhizi_api.webapp.test_client() as c: - req = c.post('/load/node-set', + req = c.post('/load/node-set-by-id', content_type='application/json', data=json.dumps({ 'id_set': id_set})) req_data = json.loads(req.data) -- cgit v1.3.1 From 2e080bafa5b4d30361e73652c2b12f6d4cfb8705 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 6 Oct 2014 17:55:11 +0300 Subject: DBO_load_node_id_set: fix cypher query --- src-py/db_controller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 93e1c553..96b12079 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -102,6 +102,7 @@ class DBO_add_node_set(DB_op): class DBO_load_node_id_set(DB_op): def __init__(self, filter_type, filter_prop=None): + # TODO: mv type filter to DBO_load_node_set_by_attribute """ load node DB id set, filter by type / properties """ @@ -119,7 +120,7 @@ class DBO_load_node_id_set(DB_op): filter_prop_arr.append("n.{0} = {1} and ".format(k, v_str)) filter_prop_str = " where " + " and ".join(filter_prop_arr) - q = "match (n:{0}){1} return id(n)".format(filter_type, filter_prop_str) + q = "match (n:{0} {1}) return id(n)".format(filter_type, filter_prop_str) self.add_statement(q) def on_success(self, data): -- cgit v1.3.1 From b7ac56cbe38fbccbb782b42c07bb5b389e65a433 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 6 Oct 2014 18:15:10 +0300 Subject: common DB_op __type_check_filter_attr_map() --- src-py/db_controller.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 96b12079..b4ce0fc5 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -44,6 +44,16 @@ class DB_op(object): # for k, v in self.id_to_statement_map: # yield {k, v, None} + def __type_check_filter_attr_map(self, filter_attr_map): + """ + # type sanity check an attribute filter map + """ + assert isinstance(filter_attr_map, dict) + assert len(filter_attr_map) > 0 + for k, v in filter_attr_map.items(): + assert isinstance(k, basestring) + assert isinstance(v, list) + @property def statement_set(self): return self.id_to_statement_map.values() -- cgit v1.3.1 From eef0c89ed89293ca89ba8b5f42b7100c46682fd7 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 6 Oct 2014 18:16:12 +0300 Subject: filter_attr_map to cypher where clause conversion: where_clause_from_filter_attr_map() --- src-py/neo4j_util.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 4c166bfe..929324fb 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -48,3 +48,19 @@ def statement_set_to_REST_form(statement_set): assert isinstance(statement_set, list) return {'statements': statement_set} + +def where_clause_from_filter_attr_map(filter_attr_map, node_param_name="n"): + """ + convert a filter attribute map to a parameterized Cypher where clause, eg. + in: { 'att_foo': [ 'a', 'b' ], 'att_goo': [1,2] } + out: where n.att_foo in {att_foo} and n.att_goo in {att_goo} ... + """ + filter_arr = [] + for k in filter_attr_map.keys(): + # create a cypher query parameter place holder for each attr set + # eg. n.foo in {foo}, where foo is passed as a query parameter + f_attr = "{0}.{1} in {{{1}}}".format(node_param_name, k) + filter_arr.append(f_attr) + filter_str = "where {0}".format(' and '.join(filter_arr)) + return filter_str + -- cgit v1.3.1 From e4fbde3cea8a5febda828b12927a3759d430606d Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 6 Oct 2014 18:49:14 +0300 Subject: handle empty filter maps --- src-py/db_controller.py | 42 ++++++++++++++++++++++-------------------- src-py/neo4j_util.py | 5 +++++ 2 files changed, 27 insertions(+), 20 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index b4ce0fc5..dcc22f7c 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -112,7 +112,7 @@ class DBO_add_node_set(DB_op): class DBO_load_node_id_set(DB_op): def __init__(self, filter_type, filter_prop=None): - # TODO: mv type filter to DBO_load_node_set_by_attribute + # TODO: mv type filter to DBO_load_node_set """ load node DB id set, filter by type / properties """ @@ -161,35 +161,23 @@ class DBO_load_node_set_by_DB_id(DB_op): log.debug('loaded node set: ' + str(data)) return self.extract_single_query_response_data(self.statement_set[0], data) -class DBO_load_node_set_by_attribute(DB_op): +class DBO_load_node_set(DB_op): - def __init__(self, filter_attr_map): + def __init__(self, filter_type=None, filter_attr_map=None): """ load a set of nodes according to filter_attr_map @param filter_attr_map: is a filter_key to filter_value_set map of attributes to match against, eg.: { 'id':[0,1], 'color: ['red','blue'] } + @param filter_type: node type filter @return: loaded node set or an empty set if no match was found """ - # type sanity checks - assert isinstance(filter_attr_map, dict) - assert len(filter_attr_map) > 0 - for k, v in filter_attr_map.items(): - assert isinstance(k, basestring) - assert isinstance(v, list) - - filter_arr = [] - for k, v in filter_attr_map.items(): - # create a cypher query parameter place holder for each attr set - # eg. n.foo in {foo}, where foo is passed as a query parameter - f_attr = "n.{0} in {{{0}}}".format(k, v) - filter_arr.append(f_attr) + self.__type_check_filter_attr_map(filter_attr_map) + filter_str = dbu.where_clause_from_filter_attr_map() - filter_str = "where {0}".format(' and '.join(filter_arr)) - - super(DBO_load_node_set_by_attribute, self).__init__() + super(DBO_load_node_set, self).__init__() q = "match (n) {0} return n".format(filter_str) self.add_statement(q, params=filter_attr_map) @@ -197,7 +185,7 @@ class DBO_load_node_set_by_attribute(DB_op): log.debug('loaded node set: ' + str(data)) return self.extract_single_query_response_data(self.statement_set[0], data) -class DBO_load_node_set_by_id_attribute(DBO_load_node_set_by_attribute): +class DBO_load_node_set_by_id_attribute(DBO_load_node_set): def __init__(self, id_set): """ convenience op: load a set of nodes by their 'id' attribute != DB node id @@ -206,6 +194,20 @@ class DBO_load_node_set_by_id_attribute(DBO_load_node_set_by_attribute): super(DBO_load_node_set_by_id_attribute, self).__init__({'id': id_set}) +class DBO_load_link_id_set(DB_op): + def __init__(self, filter_type=None, filter_attr_map=None): + """ + load a set of link ids + + @param filter_type: link type filter + @param filter_attr_map: is a filter_key to filter_value_set map of + attributes to match link properties against + @return: a set of loaded link ids + """ + self.__type_check_filter_attr_map(filter_attr_map) + filter_str = dbu.where_clause_from_filter_attr_map() + + class DB_Driver_Base(): pass diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 929324fb..c340e15a 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -54,7 +54,12 @@ def where_clause_from_filter_attr_map(filter_attr_map, node_param_name="n"): convert a filter attribute map to a parameterized Cypher where clause, eg. in: { 'att_foo': [ 'a', 'b' ], 'att_goo': [1,2] } out: where n.att_foo in {att_foo} and n.att_goo in {att_goo} ... + + @param filter_attr_map: may be None or empty """ + if not filter_attr_map: + return "" + filter_arr = [] for k in filter_attr_map.keys(): # create a cypher query parameter place holder for each attr set -- cgit v1.3.1 From 4717f01ed33b26f6c0fbbf2cb83fbf681d14da41 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 7 Oct 2014 18:08:20 +0200 Subject: mv type sanity checks to neo4j_util --- src-py/db_controller.py | 12 ------------ src-py/neo4j_util.py | 12 ++++++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index dcc22f7c..b76de5fe 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -44,16 +44,6 @@ class DB_op(object): # for k, v in self.id_to_statement_map: # yield {k, v, None} - def __type_check_filter_attr_map(self, filter_attr_map): - """ - # type sanity check an attribute filter map - """ - assert isinstance(filter_attr_map, dict) - assert len(filter_attr_map) > 0 - for k, v in filter_attr_map.items(): - assert isinstance(k, basestring) - assert isinstance(v, list) - @property def statement_set(self): return self.id_to_statement_map.values() @@ -174,7 +164,6 @@ class DBO_load_node_set(DB_op): @return: loaded node set or an empty set if no match was found """ - self.__type_check_filter_attr_map(filter_attr_map) filter_str = dbu.where_clause_from_filter_attr_map() super(DBO_load_node_set, self).__init__() @@ -204,7 +193,6 @@ class DBO_load_link_id_set(DB_op): attributes to match link properties against @return: a set of loaded link ids """ - self.__type_check_filter_attr_map(filter_attr_map) filter_str = dbu.where_clause_from_filter_attr_map() diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index c340e15a..2d870fcc 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -69,3 +69,15 @@ def where_clause_from_filter_attr_map(filter_attr_map, node_param_name="n"): filter_str = "where {0}".format(' and '.join(filter_arr)) return filter_str +def __type_check_link(link): + assert link.has_key('__src') + assert link.has_key('__dst') + +def __type_check_filter_attr_map(filter_attr_map): + """ + # type sanity check an attribute filter map + """ + assert isinstance(filter_attr_map, dict) + for k, v in filter_attr_map.items(): + assert isinstance(k, basestring) + assert isinstance(v, list) -- cgit v1.3.1 From 7ca40693c02664c11f3304f5da4d18e137e7b423 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 7 Oct 2014 18:08:35 +0200 Subject: rm old DBO_load_node_id_set --- src-py/db_controller.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index b76de5fe..8ca3a642 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -100,41 +100,6 @@ class DBO_add_node_set(DB_op): log.debug('node-set added: ids: ' + str(id_set)) return id_set -class DBO_load_node_id_set(DB_op): - def __init__(self, filter_type, filter_prop=None): - # TODO: mv type filter to DBO_load_node_set - """ - load node DB id set, filter by type / properties - """ - super(DBO_load_node_id_set, self).__init__() - - # build where clause if necessary - filter_prop_str = "" - if filter_prop: - filter_prop_arr = [] - for k, v in filter_prop: - v_str = str(v) - if isinstance(v, basestring): - # quote string values - v_str = "'{0}'".format(v_str) - filter_prop_arr.append("n.{0} = {1} and ".format(k, v_str)) - filter_prop_str = " where " + " and ".join(filter_prop_arr) - - q = "match (n:{0} {1}) return id(n)".format(filter_type, filter_prop_str) - self.add_statement(q) - - def on_success(self, data): - # [!] fragile - parse results - # sample input: dict: {u'errors': [], u'results': [{u'data': [{u'row': [20]}], u'columns': [u'id(n)']}]} - id_set = [] - for r in data['results']: - columns = r['columns'] - for k in r['data']: - nid = k['row'][0] - id_set.append(nid) - - log.debug('loaded node id set: ' + str(id_set)) - return id_set class DBO_load_node_set_by_DB_id(DB_op): def __init__(self, id_set): -- cgit v1.3.1 From 2dd76779990f9a1af5401fb29a490d51c95c031b Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 7 Oct 2014 18:09:06 +0200 Subject: model.py stub, GDiff stub --- src-py/model.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src-py/model.py (limited to 'src-py') diff --git a/src-py/model.py b/src-py/model.py new file mode 100644 index 00000000..3c3ce629 --- /dev/null +++ b/src-py/model.py @@ -0,0 +1,36 @@ +class link(): + """ + documentation anchor - this class currently carries no implementation + and only acts as a documentation anchor + + link['__src'] - meta attribute for link source + link['__dst'] - meta attribute for link destination + """ + pass + +class GDiff(): + + def __init__(self, link_set_rm=[], + node_set_rm=[], + node_set_add=[], + link_set_add=[]): + self.link_set_rm = link_set_rm + self.node_set_rm = node_set_rm + self.node_set_add = node_set_add + self.link_set_add = link_set_add + + @property + def link_set_rm(self): + return self.link_set_rm + + @property + def node_set_rm(self): + return self.node_set_rm + + @property + def link_set_add(self): + return self.link_set_add + + @property + def node_set_add(self): + return self.node_set_add -- cgit v1.3.1 From e69fcf28ea7fe5af9c46b71dd03e86b75f885e27 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 7 Oct 2014 18:10:29 +0200 Subject: cypher query gen utility functions: create_query_from_node_map, create_query_from_link_map --- src-py/neo4j_util.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 2d870fcc..bbd2a176 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -69,6 +69,48 @@ def where_clause_from_filter_attr_map(filter_attr_map, node_param_name="n"): filter_str = "where {0}".format(' and '.join(filter_arr)) return filter_str +def create_query_from_node_map(node_map, input_to_DB_property_map=lambda _: _): + """ + generate a set of node create queries + + @param node_map: is a node-type to node map + @input_to_DB_property_map: optional function which takes a map of input properties and returns a map of DB properties - use to map input schemas to DB schemas + + @return: a (query, query_parameteres) set of create queries + """ + ret = [] + for n_type, n_set in node_map.items(): + q = "create (n:{0} {{prop_dict}}) return id(n)".format(n_type) + for n_prop_set in n_set: + q_params = {'prop_dict' : input_to_DB_property_map(n_prop_set)} + ret.append((q, q_params)) + return ret + +def create_query_from_link_map(link_map, input_to_DB_property_map=lambda _: _): + """ + generate a set of link create queries + + @param link_map: is a link-type to link map - see model.link + """ + ret = [] + for l_type, l_set in link_map.items(): + + for link in l_set: + __type_check_link(link) + + n_src = link['__src'] + n_dst = link['__dst'] + + # TODO: use object based link representation + prop_dict = link.copy() + del prop_dict['__dst'] + del prop_dict['__src'] + + q = "create ({ns})-[:{lt} {{prop_dict}}]-({nd})".format(ns=n_src, lt=l_type, nd=n_dst) + q_params = {'prop_dict' : input_to_DB_property_map(prop_dict)} + ret.append((q, q_params)) + + return ret def __type_check_link(link): assert link.has_key('__src') assert link.has_key('__dst') -- cgit v1.3.1 From 337e0d2ce4706e8ebd176b7a7e6e5f82d461e007 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 7 Oct 2014 21:19:36 +0200 Subject: DB_op statement __iter__ --- src-py/db_controller.py | 18 ++++++++++-------- src-py/test_db_controller.py | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 8ca3a642..52913ac5 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -39,14 +39,16 @@ class DB_op(object): return ret def __iter__(self): - # TODO impl - pass - # for k, v in self.id_to_statement_map: - # yield {k, v, None} - - @property - def statement_set(self): - return self.id_to_statement_map.values() + """ + iterate over (statement_index, statement, statement_result) + note: statement_index is zero based + + TODO: support statement_result + """ + i = 0 + for s in self.statement_set: + yield (i, s, None) + i = i + 1 def extract_single_query_response_data(self, q, data): """ diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index f64c47ce..f7092e6d 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -29,6 +29,20 @@ class TestDBController(unittest.TestCase): def setUp(self): pass + def test_db_op_statement_iter(self): + s_arr = ['match (n) return n', + 'create (b:Book {\'title\': \'foo\'}) return b'] + + db_op = dbc.DB_op() + db_op.add_statement(s_arr[0]) + db_op.add_statement(s_arr[1]) + + i = 0 + for s in db_op: + # access: second tuple item -> REST-form 'statement' key + self.assertEqual(s_arr[i], s[1]['statement']) + i = i + 1 + def test_load_node_set_by_attribute(self): filter_map = { 'name': ['Bob', u'Judo'], 'age': [128] } -- cgit v1.3.1 From 46063f7257ae06cb4a8c74f6069292aca2db2cea Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 7 Oct 2014 23:31:40 +0200 Subject: Cypher_String_Formatter - see class doc --- src-py/neo4j_util.py | 54 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 10 deletions(-) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index bbd2a176..78602fe1 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -4,6 +4,28 @@ import json import urllib2 +import model +import string + +class Cypher_String_Formatter(string.Formatter): + """ + Despite parameter support in Cypher, we sometimes do engage in query string building + - as both Cypher & Python use brackets to wrap parameters, escaping them in Python makes + queries less readable. This customized formatter will simply ignore unavailable keyworded + formatting arguments, allowing the use of non-escaped parameter designation, eg: + q = cfmt("match (a:{type} {cypher_param})", type='Book') + """ + + def get_field(self, field_name, args, kwargs): + # ignore key not found, return bracket wrapped key + try: + val=super(Cypher_String_Formatter, self).get_field(field_name, args, kwargs) + except (KeyError, AttributeError): + val="{" + field_name + "}", field_name + return val + +def cfmt(fmt_str, *args, **kwargs): + return Cypher_String_Formatter().format(fmt_str, *args, **kwargs) def post_neo4j(url, data): """ @@ -49,7 +71,7 @@ def statement_set_to_REST_form(statement_set): return {'statements': statement_set} -def where_clause_from_filter_attr_map(filter_attr_map, node_param_name="n"): +def gen_clause_where_from_filter_attr_map(filter_attr_map, node_label="n"): """ convert a filter attribute map to a parameterized Cypher where clause, eg. in: { 'att_foo': [ 'a', 'b' ], 'att_goo': [1,2] } @@ -59,17 +81,19 @@ def where_clause_from_filter_attr_map(filter_attr_map, node_param_name="n"): """ if not filter_attr_map: return "" - + + __type_check_filter_attr_map(filter_attr_map) + filter_arr = [] - for k in filter_attr_map.keys(): + for attr in filter_attr_map.keys(): # create a cypher query parameter place holder for each attr set # eg. n.foo in {foo}, where foo is passed as a query parameter - f_attr = "{0}.{1} in {{{1}}}".format(node_param_name, k) + f_attr = cfmt("{node_label}.{attr} in {{{attr}}}", node_label=node_label, attr=attr) filter_arr.append(f_attr) filter_str = "where {0}".format(' and '.join(filter_arr)) return filter_str -def create_query_from_node_map(node_map, input_to_DB_property_map=lambda _: _): +def gen_query_create_from_node_map(node_map, input_to_DB_property_map=lambda _: _): """ generate a set of node create queries @@ -78,15 +102,17 @@ def create_query_from_node_map(node_map, input_to_DB_property_map=lambda _: _): @return: a (query, query_parameteres) set of create queries """ + __type_check_link_or_node_map(node_map) + ret = [] for n_type, n_set in node_map.items(): - q = "create (n:{0} {{prop_dict}}) return id(n)".format(n_type) + q = cfmt("create (n:{n_type} {node_attr}) return id(n)", n_type=n_type) for n_prop_set in n_set: - q_params = {'prop_dict' : input_to_DB_property_map(n_prop_set)} + q_params = {'node_attr': input_to_DB_property_map(n_prop_set)} ret.append((q, q_params)) return ret -def create_query_from_link_map(link_map, input_to_DB_property_map=lambda _: _): +def gen_query_create_from_link_map(link_map, input_to_DB_property_map=lambda _: _): """ generate a set of link create queries @@ -106,15 +132,23 @@ def create_query_from_link_map(link_map, input_to_DB_property_map=lambda _: _): del prop_dict['__dst'] del prop_dict['__src'] - q = "create ({ns})-[:{lt} {{prop_dict}}]-({nd})".format(ns=n_src, lt=l_type, nd=n_dst) - q_params = {'prop_dict' : input_to_DB_property_map(prop_dict)} + q = cfmt("match (src {src_attr}),(dst {dst_attr}) create (src)-[:{l_type} {link_attr}]-(dst)", l_type=l_type) + q_params = {'src_attr': { 'id': n_src} , + 'dst_attr': { 'id': n_dst} , + 'link_attr' : input_to_DB_property_map(prop_dict)} ret.append((q, q_params)) return ret + def __type_check_link(link): assert link.has_key('__src') assert link.has_key('__dst') +def __type_check_link_or_node_map(x_map): + for k, v in x_map.iteritems(): # do some type sanity checking + assert isinstance(k, basestring) + assert isinstance(v, list) + def __type_check_filter_attr_map(filter_attr_map): """ # type sanity check an attribute filter map -- cgit v1.3.1 From 3f580af48a7738fe3ff539a63918f4a22da6e91f Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 8 Oct 2014 01:35:28 +0200 Subject: gen_query_create_from_node_map: reuse single query with a multiple parameter sets --- src-py/neo4j_util.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 78602fe1..61b40c06 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -103,13 +103,15 @@ def gen_query_create_from_node_map(node_map, input_to_DB_property_map=lambda _: @return: a (query, query_parameteres) set of create queries """ __type_check_link_or_node_map(node_map) - + ret = [] for n_type, n_set in node_map.items(): q = cfmt("create (n:{n_type} {node_attr}) return id(n)", n_type=n_type) + q_params_set = [] for n_prop_set in n_set: - q_params = {'node_attr': input_to_DB_property_map(n_prop_set)} - ret.append((q, q_params)) + q_params = input_to_DB_property_map(n_prop_set) + q_params_set.append(q_params) + ret.append((q, {'node_attr': q_params_set})) return ret def gen_query_create_from_link_map(link_map, input_to_DB_property_map=lambda _: _): @@ -118,7 +120,7 @@ def gen_query_create_from_link_map(link_map, input_to_DB_property_map=lambda _: @param link_map: is a link-type to link map - see model.link """ - ret = [] + q_params_set = [] for l_type, l_set in link_map.items(): for link in l_set: @@ -132,13 +134,13 @@ def gen_query_create_from_link_map(link_map, input_to_DB_property_map=lambda _: del prop_dict['__dst'] del prop_dict['__src'] - q = cfmt("match (src {src_attr}),(dst {dst_attr}) create (src)-[:{l_type} {link_attr}]-(dst)", l_type=l_type) - q_params = {'src_attr': { 'id': n_src} , - 'dst_attr': { 'id': n_dst} , + q_params = {'src': { 'id': n_src} , + 'dst': { 'id': n_dst} , 'link_attr' : input_to_DB_property_map(prop_dict)} - ret.append((q, q_params)) + q_params_set.append(q_params) - return ret + q = "match (src {id: {src}.id}),(dst {id: {dst}.id}) create (src)-[:%(l_type)s {link_attr}]->(dst)" % {'l_type':l_type} + return (q, q_params_set) def __type_check_link(link): assert link.has_key('__src') -- cgit v1.3.1 From db91e66762e1fdfc737788d8ba89bb1a0996fc5a Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 12 Oct 2014 12:43:54 +0200 Subject: allow query sets to partially succeed --- src-py/neo4j_util.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 61b40c06..6b78dc18 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -30,13 +30,12 @@ def cfmt(fmt_str, *args, **kwargs): def post_neo4j(url, data): """ @return dict object from the neo4j json POST response - @raise exception: if the 'errors' key is not empty """ ret = post(url, data) ret_data = json.load(ret) - if ret_data['errors']: - raise Exception('neo4j exception: ' + str(ret_data['errors'])) + # [!] do not raise exception if ret_data['errors'] is not empty - + # this allows query-sets to partially succeed return ret_data -- cgit v1.3.1 From abdd6499319a4857e2653697e8398fe13b141c39 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 12 Oct 2014 13:09:54 +0200 Subject: change DB_Controller op execution logic - allow query-sets to partially succeed --- src-py/db_controller.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 52913ac5..180a6b37 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -61,10 +61,13 @@ class DB_op(object): ret.append(row['row'][0]) return ret - def on_success(self, data): + def parse_multi_statement_response_data(self, data): pass - def on_error(self): + def on_completion(self, data): + self.result_set = data['results'] + self.error_set = data['errors'] + pass class DBO_add_node_set(DB_op): @@ -89,7 +92,7 @@ class DBO_add_node_set(DB_op): p = {'prop_dict' : input_to_DB_property_map(n_prop_dict)} self.add_statement(q, p) - def on_success(self, data): + def on_completion(self, data): # [!] fragile - parse results # sample input: dict: {u'errors': [], u'results': [{u'data': [{u'row': [20]}], u'columns': [u'id(n)']}]} id_set = [] @@ -114,9 +117,9 @@ class DBO_load_node_set_by_DB_id(DB_op): q = "match (n) where id(n) in {id_set} return n" self.add_statement(q, { 'id_set': id_set}) - def on_success(self, data): + def on_completion(self, data): log.debug('loaded node set: ' + str(data)) - return self.extract_single_query_response_data(self.statement_set[0], data) + return self.parse_single_query_response_data(data) class DBO_load_node_set(DB_op): @@ -137,9 +140,11 @@ class DBO_load_node_set(DB_op): q = "match (n) {0} return n".format(filter_str) self.add_statement(q, params=filter_attr_map) - def on_success(self, data): - log.debug('loaded node set: ' + str(data)) - return self.extract_single_query_response_data(self.statement_set[0], data) + self.add_statement(q, q_params) + + def on_completion(self, data): + log.debug('loaded id-set: ' + str(data)) + return self.parse_single_query_response_data(data) class DBO_load_node_set_by_id_attribute(DBO_load_node_set): def __init__(self, id_set): @@ -260,11 +265,12 @@ class DB_Controller: self.db_driver.begin_tx(op) ret_tx = self.db_driver.exex_op_statements(op) ret_commit = self.db_driver.commit_tx(op) - return op.on_success(ret_tx) + return op.on_completion(ret_tx) except Exception as e: + # here we watch for IOExecptions, etc - not db errors + # these are returned in the db response itself log.error(e.message) log.error(traceback.print_exc()) - op.on_error() def create_db_op(self, f_work, f_cont): ret = DB_op(f_work, f_cont) -- cgit v1.3.1 From f917586c635d099891708ae6e1ca4796b28a53e8 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 12 Oct 2014 13:46:49 +0200 Subject: test_partial_query_set_execution_success --- src-py/test_db_controller.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index f7092e6d..e09548bb 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -66,7 +66,33 @@ class TestDBController(unittest.TestCase): n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_DB_id(id_set)) self.assertEqual(len(n_set), len(id_set), 'incorrect result size') - def test_load_node_set_by_DB_id(self): pass + def test_load_node_set_by_DB_id(self): pass # TODO + + def test_partial_query_set_execution_success(self): + """ + test: + - statement execution stops at first invalid statement + - assert create statement with result data does not actually persist in DB + + From the REST API doc: 'If any errors occur while executing statements, + the server will roll back the transaction.' + """ + n_id = 'test_partial_query_set_execution_success' + + op = dbc.DB_op() + op.add_statement("create (n:Person {id: '%s'}) return n" % (n_id), {}) # valid statement + op.add_statement("match (n) return n", {}) # valid statement + op.add_statement("non-valid statement #1", {}) + op.add_statement("non-valid statement #2", {}) + + self.db_ctl.exec_op(op) + + self.assertEqual(len(op.result_set), 2) + self.assertEqual(len(op.error_set), 1) + + # assert node creation did not persist + n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_id_attribute([n_id])) + self.assertEqual(len(n_set), 0) def tearDown(self): pass -- cgit v1.3.1 From 6a5c1acbeca40a374736d657d48beb33f0d32b27 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 12 Oct 2014 14:40:12 +0200 Subject: support result_set iteration post DB_op execution --- src-py/db_controller.py | 17 ++++++++++++----- src-py/test_db_controller.py | 25 +++++++++++++++++-------- 2 files changed, 29 insertions(+), 13 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 180a6b37..3969a2bb 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -40,15 +40,22 @@ class DB_op(object): def __iter__(self): """ - iterate over (statement_index, statement, statement_result) + iterate over (statement_index, statement, result, error) + where result & error are mutually exclusive + note: statement_index is zero based - TODO: support statement_result + TODO: handle partial iteration due to error_set being non-empty """ i = 0 - for s in self.statement_set: - yield (i, s, None) - i = i + 1 + if self.result_set: + for s in self.statement_set: + yield (i, s, self.result_set[i]) + i = i + 1 + else: + for s in self.statement_set: + yield (i, s, None) + i = i + 1 def extract_single_query_response_data(self, q, data): """ diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index e09548bb..7d96954e 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -29,18 +29,27 @@ class TestDBController(unittest.TestCase): def setUp(self): pass - def test_db_op_statement_iter(self): - s_arr = ['match (n) return n', - 'create (b:Book {\'title\': \'foo\'}) return b'] + def test_db_op_statement_iteration(self): + s_arr = ['create (b:Book {title: \'foo\'}) return b', + 'match (n) return n',] - db_op = dbc.DB_op() - db_op.add_statement(s_arr[0]) - db_op.add_statement(s_arr[1]) + op = dbc.DB_op() + op.add_statement(s_arr[0]) + op.add_statement(s_arr[1]) + + i = 0 + for s_id, s, r in op: + # access: second tuple item -> REST-form 'statement' key + self.assertEqual(s_arr[i], s['statement']) + self.assertEqual(None, r) + i = i + 1 + + self.db_ctl.exec_op(op) i = 0 - for s in db_op: + for s_id, s, r in op: # access: second tuple item -> REST-form 'statement' key - self.assertEqual(s_arr[i], s[1]['statement']) + self.assertNotEqual(None, r) i = i + 1 def test_load_node_set_by_attribute(self): -- cgit v1.3.1 From 8e20a72f5cd0464f673a374e5a3741a58932e770 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 12 Oct 2014 15:17:17 +0200 Subject: return DB_result_set when iterating over DB_op results --- src-py/db_controller.py | 4 +++- src-py/neo4j_util.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 3969a2bb..32d5a436 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -9,6 +9,7 @@ import traceback import urllib2 import neo4j_util as dbu +from neo4j_util import DB_result_set log = logging.getLogger('rhizi') @@ -50,7 +51,8 @@ class DB_op(object): i = 0 if self.result_set: for s in self.statement_set: - yield (i, s, self.result_set[i]) + rs = DB_result_set(self.result_set[i]) + yield (i, s, rs) i = i + 1 else: for s in self.statement_set: diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 6b78dc18..7c228dea 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -7,6 +7,17 @@ import urllib2 import model import string +class DB_result_set(object): + def __init__(self, data): + self.data = data + + def __iter__(self): + for db_row_dict in self.data['data']: + # example: dict: {u'row': [{u'title': u'foo'}]} + assert None != db_row_dict['row'][0] + + yield db_row_dict['row'][0] + class Cypher_String_Formatter(string.Formatter): """ Despite parameter support in Cypher, we sometimes do engage in query string building -- cgit v1.3.1 From de70bef3e3be880b86e08aba810940d9481ce64b Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 12 Oct 2014 15:18:48 +0200 Subject: n=node:IndexName(Key={value}) DBO_add_link_set operation & test: - use DB_op.__iter__ to compile on_completion() return value - return id(link) values until we come up with a better link id scheme --- src-py/db_controller.py | 21 +++++++++++++++++++++ src-py/test_db_controller.py | 15 +++++++++++++++ 2 files changed, 36 insertions(+) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 32d5a436..bff3a45b 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -114,6 +114,27 @@ class DBO_add_node_set(DB_op): log.debug('node-set added: ids: ' + str(id_set)) return id_set +class DBO_add_link_set(DB_op): + def __init__(self, link_map): + """ + @param link_map: is a link-type to link-set map - see model.link + """ + super(DBO_add_link_set, self).__init__() + for q, q_params in db_util.gen_query_create_from_link_map(link_map): + self.add_statement(q, q_params) + + def on_completion(self, data): + super(DBO_add_link_set, self).on_completion(data) + + id_set = [] + for s_id, s, r_set in self: + for row in r_set: + # [!] fragile - parse results + lid = row + id_set.append(lid) + + log.debug('link-set added: ids: ' + str(id_set)) + return id_set class DBO_load_node_set_by_DB_id(DB_op): def __init__(self, id_set): diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 7d96954e..cfa8ee31 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -52,6 +52,21 @@ class TestDBController(unittest.TestCase): self.assertNotEqual(None, r) i = i + 1 + def test_add_link_set(self): + l_map = { 'Knows' : [{'__src': 'person_00', '__dst': 'skill_00'}, + {'__src': 'person_00', '__dst': 'skill_01'}] } + l_set = self.db_ctl.exec_op(dbc.DBO_add_link_set(l_map)) + self.assertEqual(len(l_set), 2) + + def test_load_node_set_by_type(self): + filter_type = 'Person' + n_set = self.db_ctl.exec_op(dbc.DBO_load_node_id_set(filter_type=filter_type)) + self.assertEqual(len(n_set), 2) + + filter_type = 'Nan_Type' + n_set = self.db_ctl.exec_op(dbc.DBO_load_node_id_set(filter_type=filter_type)) + self.assertEqual(len(n_set), 0) + def test_load_node_set_by_attribute(self): filter_map = { 'name': ['Bob', u'Judo'], 'age': [128] } -- cgit v1.3.1 From 749f4efb5cace86377606cf924378ed79d5071d2 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 12 Oct 2014 15:36:10 +0200 Subject: parse_single_query_response_data - use __iter__ --- src-py/db_controller.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index bff3a45b..726445a6 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -59,15 +59,15 @@ class DB_op(object): yield (i, s, None) i = i + 1 - def extract_single_query_response_data(self, q, data): + def parse_single_query_response_data(self, data): """ DB op can issue complex sets of quries all at once - this helper method assists in parsing response data from a single query. """ ret = [] - r_0 = data['results'][0] - for row in r_0['data']: - ret.append(row['row'][0]) + for _, _, r_set in self: + for row in r_set: + ret.append(row) return ret def parse_multi_statement_response_data(self, data): -- cgit v1.3.1 From 27acfa40abeeacecc8e46088ddb671e04284b7ef Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 12 Oct 2014 18:01:24 +0200 Subject: neo4j_test_util - rand_id(), flush_db() --- src-py/neo4j_test_util.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src-py/neo4j_test_util.py (limited to 'src-py') diff --git a/src-py/neo4j_test_util.py b/src-py/neo4j_test_util.py new file mode 100644 index 00000000..8edac1b2 --- /dev/null +++ b/src-py/neo4j_test_util.py @@ -0,0 +1,10 @@ +import uuid + +def rand_id(): + return str(uuid.uuid4()) + +def flush_db(db_ctl): + """ + complete DB flush: remove all nodes & links + """ + db_ctl.exec_cypher_query('match (n) optional match (n)-[r]-() delete n,r') -- cgit v1.3.1 From 0b06576fdbc76a5f208f0233dae47bb0707520d2 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 12 Oct 2014 18:03:14 +0200 Subject: DBO_load_link_set_by_src_or_dst_id_attributes & test --- src-py/db_controller.py | 30 ++++++++++++++++++++++++++++-- src-py/test_db_controller.py | 13 +++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 726445a6..64d33e8a 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -176,14 +176,40 @@ class DBO_load_node_set(DB_op): log.debug('loaded id-set: ' + str(data)) return self.parse_single_query_response_data(data) -class DBO_load_node_set_by_id_attribute(DBO_load_node_set): +class DBO_load_node_set_by_id_attribute(DBO_load_node_id_set): def __init__(self, id_set): """ convenience op: load a set of nodes by their 'id' attribute != DB node id """ assert isinstance(id_set, list) - super(DBO_load_node_set_by_id_attribute, self).__init__({'id': id_set}) + super(DBO_load_node_set_by_id_attribute, self).__init__(filter_attr_map={'id': id_set}) + + +class DBO_load_link_set_by_src_or_dst_id_attributes(DB_op): + def __init__(self, src_id=None, dst_id=None): + """ + load an id-set of links by source/target id attributes != DB node id + """ + assert None != src_id or None != dst_id + + super(DBO_load_link_set_by_src_or_dst_id_attributes, self).__init__() + + if not src_id: + q = "match ()-[r]->({id: {dst_id}}) return r" + q_params = {'dst_id': dst_id} + elif not dst_id: + q = "match ({id: {src_id}})-[r]->() return r" + q_params = {'src_id': src_id} + else: + q = "match ({id: {src_id}})-[r]->({id: {dst_id}}) return r" + q_params = {'src_id': src_id, 'dst_id': dst_id} + + self.add_statement(q, q_params) + + def on_completion(self, data): + log.debug('loaded id-set: ' + str(data)) + return self.parse_single_query_response_data(data) class DBO_load_link_id_set(DB_op): def __init__(self, filter_type=None, filter_attr_map=None): diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index cfa8ee31..258b91c8 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -81,6 +81,19 @@ class TestDBController(unittest.TestCase): n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_id_attribute(['skill_00', 'person_01'])) self.assertEqual(len(n_set), 2) + def test_load_link_set_by_src_or_dst_id_attributes(self): + op = dbc.DBO_load_link_set_by_src_or_dst_id_attributes(src_id='person_00', dst_id='skill_00') + n_set = self.db_ctl.exec_op(op) + self.assertEqual(len(n_set), 1) + + op = dbc.DBO_load_link_set_by_src_or_dst_id_attributes(src_id='person_00') + n_set = self.db_ctl.exec_op(op) + self.assertEqual(len(n_set), 2) + + op = dbc.DBO_load_link_set_by_src_or_dst_id_attributes(dst_id='skill_00') + n_set = self.db_ctl.exec_op(op) + self.assertEqual(len(n_set), 1) + def test_node_DB_id_lifecycle(self): """ test node DB id life cycle -- cgit v1.3.1 From 8c023ba62f8f8642ad8458d8cec61679e0ffad66 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 12 Oct 2014 18:05:25 +0200 Subject: switch to non-altering DB tests using random ids --- src-py/test_db_controller.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 258b91c8..446d9288 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -52,9 +52,22 @@ class TestDBController(unittest.TestCase): self.assertNotEqual(None, r) i = i + 1 + def test_add_node_set(self): + n_map = { 'T_test_add_node_set': [{'id': rand_id() }] } + id_set = self.db_ctl.exec_op(dbc.DBO_add_node_set(n_map)) + self.assertEqual(len(id_set), 1) + def test_add_link_set(self): - l_map = { 'Knows' : [{'__src': 'person_00', '__dst': 'skill_00'}, - {'__src': 'person_00', '__dst': 'skill_01'}] } + src_id = rand_id() + dst_id_0 = rand_id() + dst_id_1 = rand_id() + n_map = { 'T_test_add_node_set': [{'id': src_id }, + {'id': dst_id_0 }, + {'id': dst_id_1 }] } + self.db_ctl.exec_op(dbc.DBO_add_node_set(n_map)) + + l_map = { 'T_test_add_link_set' : [{'__src': src_id, '__dst': dst_id_0}, + {'__src': src_id, '__dst': dst_id_1}] } l_set = self.db_ctl.exec_op(dbc.DBO_add_link_set(l_map)) self.assertEqual(len(l_set), 2) -- cgit v1.3.1 From f5aaa150c41e07104913fc7e99015ada36e228ac Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 12 Oct 2014 18:06:05 +0200 Subject: DBO_add_node_set - sync with code changes --- src-py/db_controller.py | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 64d33e8a..936009b5 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -80,35 +80,21 @@ class DB_op(object): pass class DBO_add_node_set(DB_op): - def __init__(self, node_map, input_to_DB_property_map=lambda _: _): + def __init__(self, node_map): """ DB op: add node set - @param node_map: node-type to node list map - @input_to_DB_property_map: optional function which takes a map of input properties and returns a map of DB properties - use to map input schemas to DB schemas + @param node_map: node-type to node-set map """ super(DBO_add_node_set, self).__init__() - - for k, v in node_map.iteritems(): # do some type sanity checking - assert isinstance(k, basestring) - assert isinstance(v, list) - - self.node_map = node_map - - for type, n_set in self.node_map.items(): - q = "create (n:{0} {{prop_dict}}) return id(n)".format(type) - for n_prop_dict in n_set: - p = {'prop_dict' : input_to_DB_property_map(n_prop_dict)} - self.add_statement(q, p) + for q, q_param_set in db_util.gen_query_create_from_node_map(node_map): + self.add_statement(q, q_param_set) def on_completion(self, data): - # [!] fragile - parse results - # sample input: dict: {u'errors': [], u'results': [{u'data': [{u'row': [20]}], u'columns': [u'id(n)']}]} id_set = [] - for r in data['results']: - columns = r['columns'] - for k in r['data']: - nid = k['row'][0] + for _, _, r_set in self: + for row in r_set: + nid = row # [!] fragile id_set.append(nid) log.debug('node-set added: ids: ' + str(id_set)) -- cgit v1.3.1 From 1d8d5646897ca05f97bb33be5ced2b8643ae4a23 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 12 Oct 2014 18:07:21 +0200 Subject: DB_op - result_set, error_set --- src-py/db_controller.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 936009b5..96de855d 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -19,8 +19,9 @@ class DB_op(object): """ def __init__(self): - self.s_id = 0 # statement id counter - self.id_to_statement_map = {} # zero based id to statement map + self.statement_set = [] + self.result_set = None + self.error_set = None self.tx_id = None self.tx_commit_url = None # cached from response to tx begin @@ -29,15 +30,14 @@ class DB_op(object): id_str = m.group('id') self.tx_id = int(id_str) - def add_statement(self, cypher_query, params={}): + def add_statement(self, query, query_params={}): """ add a DB query language statement - @return: statement id + @return: statement index (zero based) """ - ret = self.s_id - self.id_to_statement_map[self.s_id] = dbu.statement_to_REST_form(cypher_query, params) - self.s_id = self.s_id + 1 - return ret + s = db_util.statement_to_REST_form(query, query_params) + self.statement_set.append(s) + return len(self.statement_set) def __iter__(self): """ -- cgit v1.3.1 From a9abe5a017b01c40615e23a279d46bb33c240e3f Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 12 Oct 2014 19:10:38 +0200 Subject: DBO_load_link_id_set & test --- src-py/db_controller.py | 18 ++++++++++++++---- src-py/test_db_controller.py | 9 +++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 96de855d..1e80ff84 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -198,17 +198,27 @@ class DBO_load_link_set_by_src_or_dst_id_attributes(DB_op): return self.parse_single_query_response_data(data) class DBO_load_link_id_set(DB_op): - def __init__(self, filter_type=None, filter_attr_map=None): + def __init__(self, filter_type=None, filter_attr_map={}): """ - load a set of link ids + load an id-set of links @param filter_type: link type filter @param filter_attr_map: is a filter_key to filter_value_set map of attributes to match link properties against @return: a set of loaded link ids """ - filter_str = dbu.where_clause_from_filter_attr_map() - + super(DBO_load_link_id_set, self).__init__() + + q = "match ()-[r{filter_type} {filter_attr}]->() return id(r)" + q = cfmt(q, filter_type="" if not filter_type else ":" + filter_type) + q = cfmt(q, filter_attr=db_util.gen_clause_attr_filter_from_filter_attr_map(filter_attr_map)) + q_params = {k: v[0] for (k, v) in filter_attr_map.items()} # pass on only first value from each value set + + self.add_statement(q, q_params) + + def on_completion(self, data): + log.debug('loaded id-set: ' + str(data)) + return self.parse_single_query_response_data(data) class DB_Driver_Base(): pass diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 446d9288..176fce4b 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -94,6 +94,15 @@ class TestDBController(unittest.TestCase): n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_id_attribute(['skill_00', 'person_01'])) self.assertEqual(len(n_set), 2) + def test_load_link_set_by_type(self): + op = dbc.DBO_load_link_id_set(filter_type='Knows') + id_set = self.db_ctl.exec_op(op) + self.assertEqual(len(id_set), 2) + + op = dbc.DBO_load_link_id_set(filter_type='Nan_Type') + id_set = self.db_ctl.exec_op(op) + self.assertEqual(len(id_set), 0) + def test_load_link_set_by_src_or_dst_id_attributes(self): op = dbc.DBO_load_link_set_by_src_or_dst_id_attributes(src_id='person_00', dst_id='skill_00') n_set = self.db_ctl.exec_op(op) -- cgit v1.3.1 From f76f673a8c4f9f3d69671fee8847ce066a85852d Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 12 Oct 2014 19:11:08 +0200 Subject: cleanup load_node_set_XXX tests --- src-py/test_db_controller.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 176fce4b..daa4fec9 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -70,26 +70,26 @@ class TestDBController(unittest.TestCase): {'__src': src_id, '__dst': dst_id_1}] } l_set = self.db_ctl.exec_op(dbc.DBO_add_link_set(l_map)) self.assertEqual(len(l_set), 2) - + def test_load_node_set_by_type(self): - filter_type = 'Person' - n_set = self.db_ctl.exec_op(dbc.DBO_load_node_id_set(filter_type=filter_type)) - self.assertEqual(len(n_set), 2) + op = dbc.DBO_load_node_id_set(filter_type='Person') + id_set = self.db_ctl.exec_op(op) + self.assertEqual(len(id_set), 2) - filter_type = 'Nan_Type' - n_set = self.db_ctl.exec_op(dbc.DBO_load_node_id_set(filter_type=filter_type)) - self.assertEqual(len(n_set), 0) + op = dbc.DBO_load_node_id_set(filter_type='Nan_Type') + id_set = self.db_ctl.exec_op(op) + self.assertEqual(len(id_set), 0) def test_load_node_set_by_attribute(self): - filter_map = { 'name': ['Bob', u'Judo'], - 'age': [128] } - n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_attribute(filter_map)) + fam = { 'name': ['Bob', u'Judo'], 'age': [128] } + n_set = self.db_ctl.exec_op(dbc.DBO_load_node_id_set(filter_attr_map=fam)) self.assertEqual(len(n_set), 1) - filter_map = { 'age': [128, 256, 404] } - n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_attribute(filter_map)) + fam = { 'age': [128, 256, 404] } + n_set = self.db_ctl.exec_op(dbc.DBO_load_node_id_set(filter_attr_map=fam)) self.assertEqual(len(n_set), 2) + def test_load_node_set_by_DB_id(self): pass # TODO def test_load_node_set_by_id_attribute(self): n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_id_attribute(['skill_00', 'person_01'])) self.assertEqual(len(n_set), 2) -- cgit v1.3.1 From 252ec42ffdd065b3db0d564a1b57b0a446a7fcd8 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 12 Oct 2014 19:12:08 +0200 Subject: misc --- src-py/test_db_controller.py | 31 +++++++++++++++++++------------ src-py/test_rhizi_api.py | 25 ++++++++++++++++++++----- 2 files changed, 39 insertions(+), 17 deletions(-) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index daa4fec9..b768898d 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -1,9 +1,10 @@ import unittest -import db_controller import logging import db_controller as dbc from rhizi_server import Config +from neo4j_test_util import rand_id +from neo4j_test_util import flush_db class TestDBController(unittest.TestCase): @@ -19,11 +20,15 @@ class TestDBController(unittest.TestCase): ] } + l_map = { 'Knows' : [{'__src': 'person_00', '__dst': 'skill_00'}, + {'__src': 'person_00', '__dst': 'skill_01'}] } + @classmethod def setUpClass(self): cfg = Config.init_from_file('res/etc/rhizi-server.conf') self.db_ctl = dbc.DB_Controller(cfg) self.db_ctl.exec_op(dbc.DBO_add_node_set(self.n_map)) + self.db_ctl.exec_op(dbc.DBO_add_link_set(self.l_map)) self.log = logging.getLogger('rhizi') def setUp(self): @@ -31,25 +36,27 @@ class TestDBController(unittest.TestCase): def test_db_op_statement_iteration(self): s_arr = ['create (b:Book {title: \'foo\'}) return b', - 'match (n) return n',] + 'match (n) return n', ] op = dbc.DB_op() op.add_statement(s_arr[0]) op.add_statement(s_arr[1]) i = 0 - for s_id, s, r in op: + for _, s, r in op: # access: second tuple item -> REST-form 'statement' key self.assertEqual(s_arr[i], s['statement']) self.assertEqual(None, r) i = i + 1 - + self.db_ctl.exec_op(op) i = 0 - for s_id, s, r in op: + for _, s, r_set in op: # access: second tuple item -> REST-form 'statement' key - self.assertNotEqual(None, r) + self.assertNotEqual(None, r_set) + for x in r_set: + pass i = i + 1 def test_add_node_set(self): @@ -90,6 +97,7 @@ class TestDBController(unittest.TestCase): self.assertEqual(len(n_set), 2) def test_load_node_set_by_DB_id(self): pass # TODO + def test_load_node_set_by_id_attribute(self): n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_id_attribute(['skill_00', 'person_01'])) self.assertEqual(len(n_set), 2) @@ -125,7 +133,6 @@ class TestDBController(unittest.TestCase): n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_DB_id(id_set)) self.assertEqual(len(n_set), len(id_set), 'incorrect result size') - def test_load_node_set_by_DB_id(self): pass # TODO def test_partial_query_set_execution_success(self): """ @@ -137,15 +144,15 @@ class TestDBController(unittest.TestCase): the server will roll back the transaction.' """ n_id = 'test_partial_query_set_execution_success' - + op = dbc.DB_op() - op.add_statement("create (n:Person {id: '%s'}) return n" % (n_id), {}) # valid statement - op.add_statement("match (n) return n", {}) # valid statement + op.add_statement("create (n:Person {id: '%s'}) return n" % (n_id), {}) # valid statement + op.add_statement("match (n) return n", {}) # valid statement op.add_statement("non-valid statement #1", {}) op.add_statement("non-valid statement #2", {}) - + self.db_ctl.exec_op(op) - + self.assertEqual(len(op.result_set), 2) self.assertEqual(len(op.error_set), 1) diff --git a/src-py/test_rhizi_api.py b/src-py/test_rhizi_api.py index 9749dfe9..c404edba 100644 --- a/src-py/test_rhizi_api.py +++ b/src-py/test_rhizi_api.py @@ -8,21 +8,31 @@ from rhizi_server import Config from werkzeug.test import EnvironBuilder from werkzeug.test import Client +from db_controller import DB_Driver_Embedded + class TestRhiziAPI(unittest.TestCase): def setUp(self): - cfg = Config.init_from_file('res/etc/rhizi-server.conf') - db_ctl = dbc.DB_Controller(cfg) - rhizi_api.db_ctl = db_ctl + self.flush_db() @classmethod def setUpClass(self): + cfg = Config.init_from_file('res/etc/rhizi-server.conf') + self.db_ctl = dbc.DB_Controller(cfg) + rhizi_api.db_ctl = self.db_ctl + # TODO extract to superclass log = logging.getLogger('rhizi') log.setLevel(logging.DEBUG) log_handler_c = logging.StreamHandler() log.addHandler(log_handler_c) + def flush_db(self): + """ + complete DB flush: remove all nodes & links + """ + self.db_ctl.exec_cypher_query('match (n) optional match (n)-[r]-() delete n,r') + def test_add_node_set(self): """ add node set test @@ -51,13 +61,15 @@ class TestRhiziAPI(unittest.TestCase): self.assertEqual(None, rz_err) self.assertEqual(0, len(rz_data)) - def test_load_node_existing(self): + def test_load_node_set_by_id_existing(self): """ loading an existing node test """ id_set = ['skill_00'] + self.db_ctl.exec_cypher_query('create (s:Skill {id: \'skill_00\'} )') + with rhizi_api.webapp.test_client() as c: - req = c.post('/load/node-set', + req = c.post('/load/node-set-by-id', content_type='application/json', data=json.dumps({ 'id_set': id_set})) n_set = json.loads(req.data)['data'] @@ -65,5 +77,8 @@ class TestRhiziAPI(unittest.TestCase): self.assertEqual(1, len(n_set)) self.assertEqual(n_set[0]['id'], id_set[0]) + def test_load_node_set(self): + pass + if __name__ == "__main__": unittest.main() -- cgit v1.3.1 From b6824dd09a58d2e400851fc310b416f4311bd030 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 15 Oct 2014 18:36:02 +0200 Subject: model.graph package --- src-py/model.py | 36 ------------------------------------ src-py/model/__init__.py | 0 src-py/model/graph.py | 37 +++++++++++++++++++++++++++++++++++++ src-py/model/model.py | 9 +++++++++ 4 files changed, 46 insertions(+), 36 deletions(-) delete mode 100644 src-py/model.py create mode 100644 src-py/model/__init__.py create mode 100644 src-py/model/graph.py create mode 100644 src-py/model/model.py (limited to 'src-py') diff --git a/src-py/model.py b/src-py/model.py deleted file mode 100644 index 3c3ce629..00000000 --- a/src-py/model.py +++ /dev/null @@ -1,36 +0,0 @@ -class link(): - """ - documentation anchor - this class currently carries no implementation - and only acts as a documentation anchor - - link['__src'] - meta attribute for link source - link['__dst'] - meta attribute for link destination - """ - pass - -class GDiff(): - - def __init__(self, link_set_rm=[], - node_set_rm=[], - node_set_add=[], - link_set_add=[]): - self.link_set_rm = link_set_rm - self.node_set_rm = node_set_rm - self.node_set_add = node_set_add - self.link_set_add = link_set_add - - @property - def link_set_rm(self): - return self.link_set_rm - - @property - def node_set_rm(self): - return self.node_set_rm - - @property - def link_set_add(self): - return self.link_set_add - - @property - def node_set_add(self): - return self.node_set_add diff --git a/src-py/model/__init__.py b/src-py/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src-py/model/graph.py b/src-py/model/graph.py new file mode 100644 index 00000000..9891c4ef --- /dev/null +++ b/src-py/model/graph.py @@ -0,0 +1,37 @@ +class Attribute_Diff(): + """ + Represents a change to note attributes, where nodes can represent + either logical nodes or logical links, and attributes can be added, + changed or removed + """ + pass + +class Topo_Diff(): + """ + Represents a change to the graph topology + """ + + def __init__(self, link_set_rm=[], + node_set_rm=[], + node_set_add=[], + link_set_add=[]): + self.link_set_rm = link_set_rm + self.node_set_rm = node_set_rm + self.node_set_add = node_set_add + self.link_set_add = link_set_add + + @property + def link_set_rm(self): + return self.link_set_rm + + @property + def node_set_rm(self): + return self.node_set_rm + + @property + def link_set_add(self): + return self.link_set_add + + @property + def node_set_add(self): + return self.node_set_add diff --git a/src-py/model/model.py b/src-py/model/model.py new file mode 100644 index 00000000..9ee61397 --- /dev/null +++ b/src-py/model/model.py @@ -0,0 +1,9 @@ +class link(): + """ + documentation anchor - this class currently carries no implementation + and only acts as a documentation anchor + + link['__src'] - meta attribute for link source + link['__dst'] - meta attribute for link destination + """ + pass -- cgit v1.3.1 From 93873bd93ccc8463eaa3931e1efe7d30d1007e0b Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 15 Oct 2014 18:37:00 +0200 Subject: DB_composed_op --- src-py/db_controller.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 1e80ff84..9a6151ec 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -74,9 +74,33 @@ class DB_op(object): pass def on_completion(self, data): + pass + + def _assign_results_errors(self, data): self.result_set = data['results'] self.error_set = data['errors'] +class DB_composed_op(DB_op): + def __init__(self): + super(DB_composed_op, self).__init__() + self.sub_op_set = [] + + def add_statement(self, query, query_params={}): + assert False, "composed_op may not contain statements, only sub-ops" + + def add_sub_op(self, op): + self.sub_op_set.append(op) + + def __getattribute__(self, attr): + """ + intercept 'statement_set' attr get + """ + if attr == 'statement_set': + # construct a list comprehension composed of all sup_op statements + return [s for s_op in self.sub_op_set for s in s_op.statement_set] + + return object.__getattribute__(self, attr) + pass class DBO_add_node_set(DB_op): -- cgit v1.3.1 From cd4c8fc54cdd6d24ef6385a89afdec1c9d65e9cb Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 15 Oct 2014 18:38:53 +0200 Subject: DBO_topo_diff_commit & test --- src-py/db_controller.py | 22 ++++++++++++++++++++++ src-py/test_db_controller.py | 22 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 9a6151ec..dcb70c10 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -101,6 +101,28 @@ class DB_composed_op(DB_op): return object.__getattribute__(self, attr) +class DBO_topo_diff_commit(DB_composed_op): + """ + commit a + """ + def __init__(self, topo_diff): + super(DBO_topo_diff_commit, self).__init__() + + # TODO rm link set + # TODO rm node set + assert not topo_diff.node_set_rm, 'unsupported' + assert not topo_diff.link_set_rm, 'unsupported' + + n_add_map = db_util.meta_attr_list_to_meta_attr_map(topo_diff.node_set_add) + l_add_map = db_util.meta_attr_list_to_meta_attr_map(topo_diff.link_set_add) + + op_n_add = DBO_add_node_set(n_add_map) + op_l_add = DBO_add_link_set(l_add_map) + + # [!] order critical + self.add_sub_op(op_n_add) + self.add_sub_op(op_l_add) + pass class DBO_add_node_set(DB_op): diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index b768898d..2820068e 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -160,6 +160,28 @@ class TestDBController(unittest.TestCase): n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_id_attribute([n_id])) self.assertEqual(len(n_set), 0) + def test_topo_diff_commit(self): + n_0_id = rand_id() + n_1_id = rand_id() + + n_set = [{'__type': 'T_test_topo_diff_commit', 'id': n_0_id }, + {'__type': 'T_test_topo_diff_commit', 'id': n_1_id }] + l_set = [{'__type': 'T_test_topo_diff_commit', '__src': n_0_id, '__dst': n_1_id}, + {'__type': 'T_test_topo_diff_commit', '__src': n_1_id, '__dst': n_0_id}] + + topo_diff = Topo_Diff(node_set_add=n_set, + link_set_add=l_set) + + op = dbc.DBO_topo_diff_commit(topo_diff) + self.assertEqual(len(op.statement_set), 3) # one parameterized node create. 2 link create + self.db_ctl.exec_op(op) + + id_set = self.db_ctl.exec_op(dbc.DBO_match_node_set_by_id_attribute([n_0_id, n_1_id])) + self.assertEqual(len(id_set), 2) + id_set = self.db_ctl.exec_op(dbc.DBO_match_link_set_by_src_or_dst_id_attributes(src_id=n_0_id, dst_id=n_1_id)) + self.assertEqual(len(id_set), 1) + id_set = self.db_ctl.exec_op(dbc.DBO_match_link_set_by_src_or_dst_id_attributes(src_id=n_1_id, dst_id=n_0_id)) + self.assertEqual(len(id_set), 1) def tearDown(self): pass if __name__ == "__main__": -- cgit v1.3.1 From 5f9a2481861c27f32208671f166785ba1e9cbdda Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 15 Oct 2014 18:39:58 +0200 Subject: update gen_clause_attr_filter_from_filter_attr_map() --- src-py/neo4j_util.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 7c228dea..1c4ee081 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -81,6 +81,21 @@ def statement_set_to_REST_form(statement_set): return {'statements': statement_set} +def gen_clause_attr_filter_from_filter_attr_map(filter_attr_map, node_label="n"): + if not filter_attr_map: + return "{}" + + __type_check_filter_attr_map(filter_attr_map) + + filter_arr = [] + for attr_name in filter_attr_map.keys(): + # create a cypher query parameter place holder for each attr set + # eg. n.foo in {foo}, where foo is passed as a query parameter + f_attr = cfmt("{attr_name}: {{{attr}}}", attr_name=attr_name) + filter_arr.append(f_attr) + + filter_str = "{{{0}}}".format(', '.join(filter_arr)) + return filter_str def gen_clause_where_from_filter_attr_map(filter_attr_map, node_label="n"): """ convert a filter attribute map to a parameterized Cypher where clause, eg. -- cgit v1.3.1 From 4edff7438274771d30849633b140d40a434fd491 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 15 Oct 2014 18:40:54 +0200 Subject: meta_attr_list_to_meta_attr_map - used for '__type' injected meta attributes --- src-py/neo4j_util.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 1c4ee081..6b79a84c 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -164,6 +164,28 @@ def gen_query_create_from_link_map(link_map, input_to_DB_property_map=lambda _: 'link_attr' : input_to_DB_property_map(prop_dict)} q_params_set.append(q_params) +def meta_attr_list_to_meta_attr_map(e_set, meta_attr='__type'): + """ + convert a list of maps each containing a meta_attr key into a + meta_attr-mapped collection of lists with the meta_attr removed - eg: + + in: [{'id':0, '__type': 'T'}, {'id':1, '__type': 'T'}] + out: { 'T', [{'id':0}, {'id':1}] } + """ + ret = {} + for v in e_set: + assert None != v['__type'] # attert type meta-attr is present + + v_type = v['__type'] + if None == ret.get(v_type):# init type list if necessary + ret[v_type] = [] + + v_no_meta = v.copy() + del v_no_meta['__type'] + + ret[v_type].append(v_no_meta) + + return ret q = "match (src {id: {src}.id}),(dst {id: {dst}.id}) create (src)-[:%(l_type)s {link_attr}]->(dst)" % {'l_type':l_type} return (q, q_params_set) -- cgit v1.3.1 From 1864fdcb50632be4882a006665f749bd2f404fff Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 15 Oct 2014 18:49:13 +0200 Subject: gen_query_create_from_link_map - revert support for parameterized link create statements --- src-py/neo4j_util.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 6b79a84c..120a601a 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -145,8 +145,14 @@ def gen_query_create_from_link_map(link_map, input_to_DB_property_map=lambda _: @param link_map: is a link-type to link map - see model.link """ - q_params_set = [] + __type_check_link_or_node_map(link_map) + + ret = [] for l_type, l_set in link_map.items(): + q = "match (src {id: {src}.id}),(dst {id: {dst}.id}) " + \ + "create (src)-[r:%(__type)s {link_attr}]->(dst) " + \ + "return id(r)" + q = q % {'__type':l_type} for link in l_set: __type_check_link(link) @@ -162,7 +168,9 @@ def gen_query_create_from_link_map(link_map, input_to_DB_property_map=lambda _: q_params = {'src': { 'id': n_src} , 'dst': { 'id': n_dst} , 'link_attr' : input_to_DB_property_map(prop_dict)} - q_params_set.append(q_params) + ret.append((q, q_params)) + + return ret def meta_attr_list_to_meta_attr_map(e_set, meta_attr='__type'): """ -- cgit v1.3.1 From d04ec3ff09ca4b305f8b882f59af4e3aa713d8c0 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 15 Oct 2014 18:50:23 +0200 Subject: topo_diff_commit, attr_diff_commit API stubs --- src-py/rhizi_api.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 0adf6bc3..2744e355 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -83,6 +83,19 @@ def __load_node_set_by_id_attr_common(id_set): def add_node(): # pending decision regarding support for single object operations assert False +@webapp.route("/graph/topo-diff-commit", methods=['POST']) +def topo_diff_commit(): + """ + commit a graph topology diff + """ + pass + +@webapp.route("/graph/attr-diff-commit", methods=['POST']) +def attr_diff_commit(): + """ + commit a graph attribute diff + """ + pass @webapp.route("/add/node-set", methods=['POST']) def add_node_set(): -- cgit v1.3.1 From 12b475ab0f131c6db8f5b28b7d5e5a89d11be9cf Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 15 Oct 2014 18:54:36 +0200 Subject: misc --- src-py/db_controller.py | 40 ++++++++++++++++++++-------------------- src-py/neo4j_util.py | 19 ++++++++++++------- 2 files changed, 32 insertions(+), 27 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index dcb70c10..cb52cc09 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -156,13 +156,10 @@ class DBO_add_link_set(DB_op): self.add_statement(q, q_params) def on_completion(self, data): - super(DBO_add_link_set, self).on_completion(data) - id_set = [] for s_id, s, r_set in self: for row in r_set: - # [!] fragile - parse results - lid = row + lid = row # [!] fragile id_set.append(lid) log.debug('link-set added: ids: ' + str(id_set)) @@ -183,7 +180,7 @@ class DBO_load_node_set_by_DB_id(DB_op): log.debug('loaded node set: ' + str(data)) return self.parse_single_query_response_data(data) -class DBO_load_node_set(DB_op): +class DBO_match_node_id_set(DB_op): def __init__(self, filter_type=None, filter_attr_map=None): """ @@ -208,24 +205,26 @@ class DBO_load_node_set(DB_op): log.debug('loaded id-set: ' + str(data)) return self.parse_single_query_response_data(data) -class DBO_load_node_set_by_id_attribute(DBO_load_node_id_set): +class DBO_match_node_set_by_id_attribute(DBO_match_node_id_set): def __init__(self, id_set): """ convenience op: load a set of nodes by their 'id' attribute != DB node id """ assert isinstance(id_set, list) - super(DBO_load_node_set_by_id_attribute, self).__init__(filter_attr_map={'id': id_set}) + super(DBO_match_node_set_by_id_attribute, self).__init__(filter_attr_map={'id': id_set}) -class DBO_load_link_set_by_src_or_dst_id_attributes(DB_op): +class DBO_match_link_set_by_src_or_dst_id_attributes(DB_op): def __init__(self, src_id=None, dst_id=None): """ - load an id-set of links by source/target id attributes != DB node id + match a set of links by source/target node id attributes + + @return: a set of loaded links """ assert None != src_id or None != dst_id - super(DBO_load_link_set_by_src_or_dst_id_attributes, self).__init__() + super(DBO_match_link_set_by_src_or_dst_id_attributes, self).__init__() if not src_id: q = "match ()-[r]->({id: {dst_id}}) return r" @@ -243,7 +242,7 @@ class DBO_load_link_set_by_src_or_dst_id_attributes(DB_op): log.debug('loaded id-set: ' + str(data)) return self.parse_single_query_response_data(data) -class DBO_load_link_id_set(DB_op): +class DBO_match_link_id_set(DB_op): def __init__(self, filter_type=None, filter_attr_map={}): """ load an id-set of links @@ -253,7 +252,7 @@ class DBO_load_link_id_set(DB_op): attributes to match link properties against @return: a set of loaded link ids """ - super(DBO_load_link_id_set, self).__init__() + super(DBO_match_link_id_set, self).__init__() q = "match ()-[r{filter_type} {filter_attr}]->() return id(r)" q = cfmt(q, filter_type="" if not filter_type else ":" + filter_type) @@ -280,8 +279,8 @@ class DB_Driver_REST(DB_Driver_Base): # # [!] neo4j seems picky about receiving an additional empty statement list # - data = data = dbu.statement_set_to_REST_form([]) - ret = dbu.post_neo4j(tx_open_url, data) + data = data = db_util.statement_set_to_REST_form([]) + ret = db_util.post_neo4j(tx_open_url, data) tx_commit_url = ret['commit'] op.parse_tx_id(tx_commit_url) @@ -291,10 +290,11 @@ class DB_Driver_REST(DB_Driver_Base): def exex_op_statements(self, op): tx_url = "{0}/{1}".format(self.tx_base_url, op.tx_id) - statement_set = dbu.statement_set_to_REST_form(op.statement_set) + statement_set = db_util.statement_set_to_REST_form(op.statement_set) try: - ret = dbu.post_neo4j(tx_url, statement_set) + ret = db_util.post_neo4j(tx_url, statement_set) + op._assign_results_errors(ret) self.log_committed_queries(statement_set) return ret except Exception as e: @@ -307,8 +307,8 @@ class DB_Driver_REST(DB_Driver_Base): # # [!] neo4j seems picky about receiving an additional empty statement list # - data = dbu.statement_set_to_REST_form([]) - ret = dbu.post(tx_commit_url, data) + data = db_util.statement_set_to_REST_form([]) + ret = db_util.post(tx_commit_url, data) log.debug('tx-commit: id: {0}, commit-url: {1}'.format(op.tx_id, tx_commit_url)) @@ -379,5 +379,5 @@ class DB_Controller: @deprecated: use transaction based api """ - # call post and not dbu.post_neo4j to avoid response key errors - dbu.post(self.config.db_base_url + '/db/data/cypher', {"query" : q}) + # call post and not db_util.post_neo4j to avoid response key errors + db_util.post(self.config.db_base_url + '/db/data/cypher', {"query" : q}) diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 120a601a..5169af0d 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -30,9 +30,9 @@ class Cypher_String_Formatter(string.Formatter): def get_field(self, field_name, args, kwargs): # ignore key not found, return bracket wrapped key try: - val=super(Cypher_String_Formatter, self).get_field(field_name, args, kwargs) + val = super(Cypher_String_Formatter, self).get_field(field_name, args, kwargs) except (KeyError, AttributeError): - val="{" + field_name + "}", field_name + val = "{" + field_name + "}", field_name return val def cfmt(fmt_str, *args, **kwargs): @@ -72,7 +72,11 @@ def statement_to_REST_form(query, parameters={}): turn cypher query to neo4j json API format """ assert isinstance(query, basestring) - assert isinstance(parameters, dict) + if isinstance(parameters, list): + for v in parameters: + assert isinstance(v, dict) + else: + assert isinstance(parameters, dict) return {'statement' : query, 'parameters': parameters} @@ -96,13 +100,16 @@ def gen_clause_attr_filter_from_filter_attr_map(filter_attr_map, node_label="n") filter_str = "{{{0}}}".format(', '.join(filter_arr)) return filter_str + def gen_clause_where_from_filter_attr_map(filter_attr_map, node_label="n"): """ convert a filter attribute map to a parameterized Cypher where clause, eg. in: { 'att_foo': [ 'a', 'b' ], 'att_goo': [1,2] } - out: where n.att_foo in {att_foo} and n.att_goo in {att_goo} ... + out: {att_foo: {att_foo}, att_goo: {att_goo}, ...} + + this function will essentially ignore all but the first value in the value list - @param filter_attr_map: may be None or empty + @param filter_attr_map: may be None or empty """ if not filter_attr_map: return "" @@ -194,8 +201,6 @@ def meta_attr_list_to_meta_attr_map(e_set, meta_attr='__type'): ret[v_type].append(v_no_meta) return ret - q = "match (src {id: {src}.id}),(dst {id: {dst}.id}) create (src)-[:%(l_type)s {link_attr}]->(dst)" % {'l_type':l_type} - return (q, q_params_set) def __type_check_link(link): assert link.has_key('__src') -- cgit v1.3.1 From 32905da5655424f3e76389f4f007f886f322a7a9 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 15 Oct 2014 18:58:54 +0200 Subject: update db_controller tests --- src-py/test_db_controller.py | 63 ++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 26 deletions(-) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 2820068e..254afb80 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -6,6 +6,9 @@ from rhizi_server import Config from neo4j_test_util import rand_id from neo4j_test_util import flush_db +from model.graph import Attribute_Diff as Attr_Diff +from model.graph import Topo_Diff as Topo_Diff + class TestDBController(unittest.TestCase): db_ctl = None @@ -22,7 +25,7 @@ class TestDBController(unittest.TestCase): l_map = { 'Knows' : [{'__src': 'person_00', '__dst': 'skill_00'}, {'__src': 'person_00', '__dst': 'skill_01'}] } - + @classmethod def setUpClass(self): cfg = Config.init_from_file('res/etc/rhizi-server.conf') @@ -60,9 +63,13 @@ class TestDBController(unittest.TestCase): i = i + 1 def test_add_node_set(self): - n_map = { 'T_test_add_node_set': [{'id': rand_id() }] } - id_set = self.db_ctl.exec_op(dbc.DBO_add_node_set(n_map)) - self.assertEqual(len(id_set), 1) + n_map = { 'T_test_add_node_set': [{'id': rand_id()}, {'id': rand_id()}] } + op = dbc.DBO_add_node_set(n_map) + + self.assertEqual(len(op.statement_set), 1) # assert a single statement is issued + + id_set = self.db_ctl.exec_op(op) + self.assertEqual(len(id_set), 2) def test_add_link_set(self): src_id = rand_id() @@ -72,55 +79,59 @@ class TestDBController(unittest.TestCase): {'id': dst_id_0 }, {'id': dst_id_1 }] } self.db_ctl.exec_op(dbc.DBO_add_node_set(n_map)) - + l_map = { 'T_test_add_link_set' : [{'__src': src_id, '__dst': dst_id_0}, - {'__src': src_id, '__dst': dst_id_1}] } - l_set = self.db_ctl.exec_op(dbc.DBO_add_link_set(l_map)) + {'__src': src_id, '__dst': dst_id_1}] } + + op = dbc.DBO_add_link_set(l_map) + self.assertEqual(len(op.statement_set), 2) # no support yet for parameterized statements for link creation + + l_set = self.db_ctl.exec_op(op) self.assertEqual(len(l_set), 2) - def test_load_node_set_by_type(self): - op = dbc.DBO_load_node_id_set(filter_type='Person') + def test_match_node_set_by_type(self): + op = dbc.DBO_match_node_id_set(filter_type='Person') id_set = self.db_ctl.exec_op(op) self.assertEqual(len(id_set), 2) - op = dbc.DBO_load_node_id_set(filter_type='Nan_Type') + op = dbc.DBO_match_node_id_set(filter_type='Nan_Type') id_set = self.db_ctl.exec_op(op) self.assertEqual(len(id_set), 0) - def test_load_node_set_by_attribute(self): + def test_match_node_set_by_attribute(self): fam = { 'name': ['Bob', u'Judo'], 'age': [128] } - n_set = self.db_ctl.exec_op(dbc.DBO_load_node_id_set(filter_attr_map=fam)) + n_set = self.db_ctl.exec_op(dbc.DBO_match_node_id_set(filter_attr_map=fam)) self.assertEqual(len(n_set), 1) fam = { 'age': [128, 256, 404] } - n_set = self.db_ctl.exec_op(dbc.DBO_load_node_id_set(filter_attr_map=fam)) + n_set = self.db_ctl.exec_op(dbc.DBO_match_node_id_set(filter_attr_map=fam)) self.assertEqual(len(n_set), 2) - def test_load_node_set_by_DB_id(self): pass # TODO + def test_match_node_set_by_DB_id(self): pass # TODO - def test_load_node_set_by_id_attribute(self): - n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_id_attribute(['skill_00', 'person_01'])) + def test_match_node_set_by_id_attribute(self): + n_set = self.db_ctl.exec_op(dbc.DBO_match_node_set_by_id_attribute(['skill_00', 'person_01'])) self.assertEqual(len(n_set), 2) - def test_load_link_set_by_type(self): - op = dbc.DBO_load_link_id_set(filter_type='Knows') + def test_match_link_set_by_type(self): + op = dbc.DBO_match_link_id_set(filter_type='Knows') id_set = self.db_ctl.exec_op(op) self.assertEqual(len(id_set), 2) - op = dbc.DBO_load_link_id_set(filter_type='Nan_Type') + op = dbc.DBO_match_link_id_set(filter_type='Nan_Type') id_set = self.db_ctl.exec_op(op) self.assertEqual(len(id_set), 0) - def test_load_link_set_by_src_or_dst_id_attributes(self): - op = dbc.DBO_load_link_set_by_src_or_dst_id_attributes(src_id='person_00', dst_id='skill_00') + def test_match_link_set_by_src_or_dst_id_attributes(self): + op = dbc.DBO_match_link_set_by_src_or_dst_id_attributes(src_id='person_00', dst_id='skill_00') n_set = self.db_ctl.exec_op(op) self.assertEqual(len(n_set), 1) - - op = dbc.DBO_load_link_set_by_src_or_dst_id_attributes(src_id='person_00') + + op = dbc.DBO_match_link_set_by_src_or_dst_id_attributes(src_id='person_00') n_set = self.db_ctl.exec_op(op) self.assertEqual(len(n_set), 2) - op = dbc.DBO_load_link_set_by_src_or_dst_id_attributes(dst_id='skill_00') + op = dbc.DBO_match_link_set_by_src_or_dst_id_attributes(dst_id='skill_00') n_set = self.db_ctl.exec_op(op) self.assertEqual(len(n_set), 1) @@ -133,7 +144,6 @@ class TestDBController(unittest.TestCase): n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_DB_id(id_set)) self.assertEqual(len(n_set), len(id_set), 'incorrect result size') - def test_partial_query_set_execution_success(self): """ test: @@ -157,7 +167,7 @@ class TestDBController(unittest.TestCase): self.assertEqual(len(op.error_set), 1) # assert node creation did not persist - n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_id_attribute([n_id])) + n_set = self.db_ctl.exec_op(dbc.DBO_match_node_set_by_id_attribute([n_id])) self.assertEqual(len(n_set), 0) def test_topo_diff_commit(self): @@ -182,6 +192,7 @@ class TestDBController(unittest.TestCase): self.assertEqual(len(id_set), 1) id_set = self.db_ctl.exec_op(dbc.DBO_match_link_set_by_src_or_dst_id_attributes(src_id=n_1_id, dst_id=n_0_id)) self.assertEqual(len(id_set), 1) + def tearDown(self): pass if __name__ == "__main__": -- cgit v1.3.1 From 10af77cc4a76448041b333d8ec793751dc3022a1 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 15 Oct 2014 20:25:00 +0200 Subject: DBO_load_node_set_by_DB_id - simplify query --- src-py/db_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index cb52cc09..8e87d9d8 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -173,7 +173,7 @@ class DBO_load_node_set_by_DB_id(DB_op): @return: loaded node set or an empty set if no match was found """ super(DBO_load_node_set_by_DB_id, self).__init__() - q = "match (n) where id(n) in {id_set} return n" + q = "start n=node({id_set}) return n" self.add_statement(q, { 'id_set': id_set}) def on_completion(self, data): -- cgit v1.3.1 From 3d0306271a427f7a2e47aea18429c1cad8e74bee Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 15 Oct 2014 22:11:44 +0200 Subject: Attribute_Diff --- src-py/model/graph.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/model/graph.py b/src-py/model/graph.py index 9891c4ef..9ad4601c 100644 --- a/src-py/model/graph.py +++ b/src-py/model/graph.py @@ -4,7 +4,11 @@ class Attribute_Diff(): either logical nodes or logical links, and attributes can be added, changed or removed """ - pass + def __init__(self, id_to_attr_diff_map={}): + """ + @param id_to_attr_diff_map + """ + self.id_to_attr_diff_map = id_to_attr_diff_map class Topo_Diff(): """ -- cgit v1.3.1 From dcbc18c61d0917008c09c7a15ec57045641a9bce Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 15 Oct 2014 22:12:56 +0200 Subject: DBO_attr_diff_commit & test - todo: parse DB response --- src-py/db_controller.py | 24 ++++++++++++++++++++++++ src-py/test_db_controller.py | 15 +++++++++++++++ 2 files changed, 39 insertions(+) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 8e87d9d8..49ed3b6b 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -123,6 +123,30 @@ class DBO_topo_diff_commit(DB_composed_op): self.add_sub_op(op_n_add) self.add_sub_op(op_l_add) + def on_completion(self, data): + pass + +class DBO_attr_diff_commit(DB_op): + """ + commit a Attr_Diff + """ + def __init__(self, attr_diff): + super(DBO_attr_diff_commit, self).__init__() + + for id_attr, n_attr_diff in attr_diff.items(): + # TODO parameterize multiple attr removal + rm_attr_set = n_attr_diff['attr_remove'] + rm_attr_str = ', '.join(['n.' + attr for attr in rm_attr_set]) + + q = ("match (n {id: {id}}) " + + "set n += {attr_set}" + + "remove " + rm_attr_str + + " return n.id, n") + q_param_set = {'id': id_attr, + 'attr_set': n_attr_diff['attr_write']} + self.add_statement(q, q_param_set) + + def on_completion(self, data): pass class DBO_add_node_set(DB_op): diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 254afb80..66ecfd09 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -193,6 +193,21 @@ class TestDBController(unittest.TestCase): id_set = self.db_ctl.exec_op(dbc.DBO_match_link_set_by_src_or_dst_id_attributes(src_id=n_1_id, dst_id=n_0_id)) self.assertEqual(len(id_set), 1) + def test_attr_diff_commit(self): + # create test node + n_id = rand_id() + topo_diff = Topo_Diff(node_set_add=[{'__type': 'T_test_attr_diff_commit', 'id': n_id, 'attr_rm': 0}]) + op = dbc.DBO_topo_diff_commit(topo_diff) + self.db_ctl.exec_op(op) + + # apply attr_diff + attr_diff = {n_id: {'attr_write': {'attr_0': 0, + 'attr_1': 'a'}, + 'attr_remove': ['attr_rm']}} + op = dbc.DBO_attr_diff_commit(attr_diff) + n_set = self.db_ctl.exec_op(op) + pass + def tearDown(self): pass if __name__ == "__main__": -- cgit v1.3.1 From d3d8bd18bfbf2da267ea30f5625f092d24b21474 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Thu, 16 Oct 2014 16:01:33 +0200 Subject: DB_row, update DBO_XXX iteration loops, test updates --- src-py/db_controller.py | 15 +++++++++------ src-py/neo4j_util.py | 12 ++++++++++-- src-py/test_db_controller.py | 14 ++++++++++---- 3 files changed, 29 insertions(+), 12 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 49ed3b6b..f4bfbb75 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -67,7 +67,8 @@ class DB_op(object): ret = [] for _, _, r_set in self: for row in r_set: - ret.append(row) + for cloumn in row: + ret.append(row) return ret def parse_multi_statement_response_data(self, data): @@ -155,6 +156,7 @@ class DBO_add_node_set(DB_op): DB op: add node set @param node_map: node-type to node-set map + @return: set of new node DB ids """ super(DBO_add_node_set, self).__init__() for q, q_param_set in db_util.gen_query_create_from_node_map(node_map): @@ -164,8 +166,8 @@ class DBO_add_node_set(DB_op): id_set = [] for _, _, r_set in self: for row in r_set: - nid = row # [!] fragile - id_set.append(nid) + for clo in row: + id_set.append(clo) log.debug('node-set added: ids: ' + str(id_set)) return id_set @@ -174,6 +176,7 @@ class DBO_add_link_set(DB_op): def __init__(self, link_map): """ @param link_map: is a link-type to link-set map - see model.link + @return: set of new node DB ids """ super(DBO_add_link_set, self).__init__() for q, q_params in db_util.gen_query_create_from_link_map(link_map): @@ -181,10 +184,10 @@ class DBO_add_link_set(DB_op): def on_completion(self, data): id_set = [] - for s_id, s, r_set in self: + for _, _, r_set in self: for row in r_set: - lid = row # [!] fragile - id_set.append(lid) + for col_val in row: + id_set.append(col_val) log.debug('link-set added: ids: ' + str(id_set)) return id_set diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 5169af0d..8d70ced9 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -7,6 +7,14 @@ import urllib2 import model import string +class DB_row(object): + def __init__(self, data): + self.data = data + + def __iter__(self): + for column_val in self.data: + yield column_val + class DB_result_set(object): def __init__(self, data): self.data = data @@ -14,9 +22,9 @@ class DB_result_set(object): def __iter__(self): for db_row_dict in self.data['data']: # example: dict: {u'row': [{u'title': u'foo'}]} - assert None != db_row_dict['row'][0] + assert None != db_row_dict['row'] - yield db_row_dict['row'][0] + yield DB_row(db_row_dict['row']) class Cypher_String_Formatter(string.Formatter): """ diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 66ecfd09..edfd4a22 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -135,13 +135,19 @@ class TestDBController(unittest.TestCase): n_set = self.db_ctl.exec_op(op) self.assertEqual(len(n_set), 1) - def test_node_DB_id_lifecycle(self): + def test_load_node_set_by_DB_id(self): """ test node DB id life cycle """ - id_set = self.db_ctl.exec_op(dbc.DBO_add_node_set({'Person': [{'name': 'John Doe', 'id': 'jdoe_00'}, - {'name': 'John Doe', 'id': 'jdoe_01'}]})) - n_set = self.db_ctl.exec_op(dbc.DBO_load_node_set_by_DB_id(id_set)) + + # create nodes, get DB ids + op = dbc.DBO_add_node_set({'T_test_load_node_set_by_DB_id': [{'name': 'John Doe'}, + {'name': 'John Doe'}]}) + id_set = self.db_ctl.exec_op(op) + + # match against DB ids + op = dbc.DBO_load_node_set_by_DB_id(id_set) + n_set = self.db_ctl.exec_op(op) self.assertEqual(len(n_set), len(id_set), 'incorrect result size') def test_partial_query_set_execution_success(self): -- cgit v1.3.1 From 3b3d3b5e91dac517627f1a080fffbff9a72b7e1a Mon Sep 17 00:00:00 2001 From: LV-426 Date: Thu, 16 Oct 2014 16:30:22 +0200 Subject: DBO_attr_diff_commit on_completion response processing, test_attr_diff_commit() update --- src-py/db_controller.py | 11 ++++++++--- src-py/test_db_controller.py | 9 +++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index f4bfbb75..2c18427c 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -141,14 +141,19 @@ class DBO_attr_diff_commit(DB_op): q = ("match (n {id: {id}}) " + "set n += {attr_set}" + - "remove " + rm_attr_str + - " return n.id, n") + "remove " + rm_attr_str + " " + + "return n.id, n") q_param_set = {'id': id_attr, 'attr_set': n_attr_diff['attr_write']} self.add_statement(q, q_param_set) def on_completion(self, data): - pass + ret = {} + for _, _, r_set in self: + for row in r_set: + n_id, n = [v for v in row] # we expect a [n_id, n] array + ret[n_id] = n + return ret class DBO_add_node_set(DB_op): def __init__(self, node_map): diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index edfd4a22..c7834749 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -211,8 +211,13 @@ class TestDBController(unittest.TestCase): 'attr_1': 'a'}, 'attr_remove': ['attr_rm']}} op = dbc.DBO_attr_diff_commit(attr_diff) - n_set = self.db_ctl.exec_op(op) - pass + n_map = self.db_ctl.exec_op(op) + self.assertEqual(len(n_map), 1) + n = n_map.get(n_id) + self.assertTrue(None != n) + self.assertTrue(None == n.get('attr_rm')) + self.assertTrue(None != n.get('attr_0')) + self.assertTrue(None != n.get('attr_1')) def tearDown(self): pass -- cgit v1.3.1 From bf3e34d7835aa75c5ff4d131d327143e611c4066 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 20 Oct 2014 17:09:44 +0200 Subject: fix missing ' ' in DBO_attr_diff_commit() query --- src-py/db_controller.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 2c18427c..64e9b5eb 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -138,9 +138,9 @@ class DBO_attr_diff_commit(DB_op): # TODO parameterize multiple attr removal rm_attr_set = n_attr_diff['attr_remove'] rm_attr_str = ', '.join(['n.' + attr for attr in rm_attr_set]) - - q = ("match (n {id: {id}}) " + - "set n += {attr_set}" + + + q = ("match (n {id: {id}}) " + + "set n += {attr_set} " + "remove " + rm_attr_str + " " + "return n.id, n") q_param_set = {'id': id_attr, -- cgit v1.3.1 From 92668e664144b15797f83c2abd61addac8b3c43a Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 20 Oct 2014 19:21:59 +0200 Subject: DBO_attr_diff_commit - augment query generation --- src-py/db_controller.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 64e9b5eb..b282a38b 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -136,15 +136,25 @@ class DBO_attr_diff_commit(DB_op): for id_attr, n_attr_diff in attr_diff.items(): # TODO parameterize multiple attr removal - rm_attr_set = n_attr_diff['attr_remove'] - rm_attr_str = ', '.join(['n.' + attr for attr in rm_attr_set]) - - q = ("match (n {id: {id}}) " + - "set n += {attr_set} " + - "remove " + rm_attr_str + " " + - "return n.id, n") - q_param_set = {'id': id_attr, - 'attr_set': n_attr_diff['attr_write']} + r_attr_set = n_attr_diff['attr_remove'] + w_attr_set = n_attr_diff['attr_write'] + + assert len(r_attr_set) > 0 or len(w_attr_set) > 0 + + q_arr = ["match (n {id: {id}}) ", + "return n.id, n"] + q_param_set = {'id': id_attr} + + if len(r_attr_set) > 0: + stmt_attr_rm = "remove " + ', '.join(['n.' + attr for attr in r_attr_set]) + q_arr.insert(1, stmt_attr_rm) + + if len(w_attr_set) > 0: + stmt_attr_set = "set n += {attr_set}" + q_arr.insert(1, stmt_attr_set) + q_param_set['attr_set'] = w_attr_set + + q = " ".join(q_arr) self.add_statement(q, q_param_set) def on_completion(self, data): -- cgit v1.3.1 From 3ad760c3b77dab1b3039d9ff757aef61a2068b17 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 20 Oct 2014 19:25:23 +0200 Subject: special handling of DB_composed_op execution --- src-py/db_controller.py | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index b282a38b..632e1dcd 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -86,22 +86,38 @@ class DB_composed_op(DB_op): super(DB_composed_op, self).__init__() self.sub_op_set = [] - def add_statement(self, query, query_params={}): + def __assert_false_statement_access(self): assert False, "composed_op may not contain statements, only sub-ops" + def add_statement(self, query, query_params={}): + self.__assert_false_statement_access() + def add_sub_op(self, op): self.sub_op_set.append(op) def __getattribute__(self, attr): """ - intercept 'statement_set' attr get + intercept 'statement_set' attr get """ if attr == 'statement_set': - # construct a list comprehension composed of all sup_op statements - return [s for s_op in self.sub_op_set for s in s_op.statement_set] + self.__assert_false_statement_access() return object.__getattribute__(self, attr) + def __iter__(self): + """ + iterate over sub_op_set + """ + for s_op in self.sub_op_set: + yield s_op + + def process_result_set(self): + ret = [] + for s_op in self: + s_result_set = s_op.process_result_set() + ret.append(s_result_set) + return ret + class DBO_topo_diff_commit(DB_composed_op): """ commit a @@ -401,16 +417,24 @@ class DB_Controller: """ execute operation within a DB transaction """ + if isinstance(op, DB_composed_op): + # construct a list comprehension composed of all sup_op statements + for s_op in op.sub_op_set: + self.exec_op(s_op) + return op.process_result_set() + try: self.db_driver.begin_tx(op) - ret_tx = self.db_driver.exex_op_statements(op) - ret_commit = self.db_driver.commit_tx(op) - return op.on_completion(ret_tx) + self.db_driver.exec_statement_set(op) + self.db_driver.commit_tx(op) + + return op.process_result_set() except Exception as e: # here we watch for IOExecptions, etc - not db errors # these are returned in the db response itself log.error(e.message) log.error(traceback.print_exc()) + raise e def create_db_op(self, f_work, f_cont): ret = DB_op(f_work, f_cont) -- cgit v1.3.1 From 81cdcfa9cac04031082d93bb9e7bf6c844b7b75b Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 20 Oct 2014 19:25:52 +0200 Subject: misc --- src-py/db_controller.py | 52 ++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 24 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 632e1dcd..bd085371 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -6,10 +6,15 @@ import re import logging import traceback +from model.graph import Attr_Diff +from model.graph import Topo_Diff + import urllib2 -import neo4j_util as dbu +import neo4j_util as db_util +from neo4j_util import cfmt from neo4j_util import DB_result_set +from neo4j_util import Neo4JException log = logging.getLogger('rhizi') @@ -326,6 +331,28 @@ class DBO_match_link_id_set(DB_op): class DB_Driver_Base(): pass +class DB_Driver_Embedded(DB_Driver_Base): + def __init__(self, db_base_url): + self.tx_base_url = db_base_url + '/db/data/transaction' + + from org.rhizi.db.neo4j.util import EmbeddedNeo4j + self.edb = EmbeddedNeo4j.createDb() + self.edb.createDb() + + def begin_tx(self, op): + pass + + def exec_statement_set(self, op): + s_set = op.statement_set + self.edb.executeCypherQury() + + def commit_tx(self, op): + pass + + def log_committed_queries(self, statement_set): + for sp_dict in statement_set['statements']: + log.debug('\tq: {0}'.format(sp_dict['statement'])) + class DB_Driver_REST(DB_Driver_Base): def __init__(self, db_base_url): self.tx_base_url = db_base_url + '/db/data/transaction' @@ -378,29 +405,6 @@ class DB_Driver_REST(DB_Driver_Base): for sp_dict in statement_set['statements']: log.debug('\tq: {0}'.format(sp_dict['statement'])) -class DB_Driver_Embedded(DB_Driver_Base): - def __init__(self, db_base_url): - self.tx_base_url = db_base_url + '/db/data/transaction' - - from org.rhizi.db.neo4j.util import EmbeddedNeo4j - self.edb = EmbeddedNeo4j.createDb() - self.edb.createDb() - - def begin_tx(self, op): - pass - - def exex_op_statements(self, op): - s_set = op.statement_set - self.edb.executeCypherQury() - - def commit_tx(self, op): - pass - - def log_committed_queries(self, statement_set): - for sp_dict in statement_set['statements']: - log.debug('\tq: {0}'.format(sp_dict['statement'])) - - class DB_Controller: """ neo4j DB controller -- cgit v1.3.1 From 133fb2a63c260a26730d44bf79d87eee360558c5 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 20 Oct 2014 19:27:09 +0200 Subject: DB_composed_op - optimize for empty n_add, l_add sets --- src-py/db_controller.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index bd085371..ddc25a85 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -125,28 +125,29 @@ class DB_composed_op(DB_op): class DBO_topo_diff_commit(DB_composed_op): """ - commit a + commit a Topo_Diff """ def __init__(self, topo_diff): super(DBO_topo_diff_commit, self).__init__() # TODO rm link set # TODO rm node set - assert not topo_diff.node_set_rm, 'unsupported' - assert not topo_diff.link_set_rm, 'unsupported' + assert 0 == len(topo_diff.node_set_rm), 'unsupported' + assert 0 == len(topo_diff.link_set_rm), 'unsupported' n_add_map = db_util.meta_attr_list_to_meta_attr_map(topo_diff.node_set_add) l_add_map = db_util.meta_attr_list_to_meta_attr_map(topo_diff.link_set_add) - op_n_add = DBO_add_node_set(n_add_map) - op_l_add = DBO_add_link_set(l_add_map) - + # # [!] order critical - self.add_sub_op(op_n_add) - self.add_sub_op(op_l_add) - - def on_completion(self, data): - pass + # + if len(n_add_map) > 0: + op_n_add = DBO_add_node_set(n_add_map) + self.add_sub_op(op_n_add) + + if len(l_add_map) > 0: + op_l_add = DBO_add_link_set(l_add_map) + self.add_sub_op(op_l_add) class DBO_attr_diff_commit(DB_op): """ -- cgit v1.3.1 From 00321278f858ec36fa4c799363bbcc508164f13c Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 20 Oct 2014 19:30:04 +0200 Subject: query result processing: - bubble up common processing - rename on_completion --- src-py/db_controller.py | 62 +++++++++++++++---------------------------------- 1 file changed, 19 insertions(+), 43 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index ddc25a85..0ef70f3b 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -25,7 +25,7 @@ class DB_op(object): def __init__(self): self.statement_set = [] - self.result_set = None + self.result_set = [] self.error_set = None self.tx_id = None self.tx_commit_url = None # cached from response to tx begin @@ -54,17 +54,18 @@ class DB_op(object): TODO: handle partial iteration due to error_set being non-empty """ i = 0 - if self.result_set: - for s in self.statement_set: - rs = DB_result_set(self.result_set[i]) - yield (i, s, rs) - i = i + 1 - else: - for s in self.statement_set: - yield (i, s, None) - i = i + 1 + r_set_len = len(self.result_set) + for s in self.statement_set: + r_set = None # row-set + if i < r_set_len: # support partial result recovery + r_set = DB_result_set(self.result_set[i]) + yield (i, s, r_set) + i = i + 1 - def parse_single_query_response_data(self, data): + def parse_multi_statement_response_data(self, data): + pass + + def process_result_set(self): """ DB op can issue complex sets of quries all at once - this helper method assists in parsing response data from a single query. @@ -72,19 +73,10 @@ class DB_op(object): ret = [] for _, _, r_set in self: for row in r_set: - for cloumn in row: - ret.append(row) + for col in row: + ret.append(col) return ret - def parse_multi_statement_response_data(self, data): - pass - - def on_completion(self, data): - pass - - def _assign_results_errors(self, data): - self.result_set = data['results'] - self.error_set = data['errors'] class DB_composed_op(DB_op): def __init__(self): @@ -179,7 +171,7 @@ class DBO_attr_diff_commit(DB_op): q = " ".join(q_arr) self.add_statement(q, q_param_set) - def on_completion(self, data): + def process_result_set(self): ret = {} for _, _, r_set in self: for row in r_set: @@ -199,10 +191,10 @@ class DBO_add_node_set(DB_op): for q, q_param_set in db_util.gen_query_create_from_node_map(node_map): self.add_statement(q, q_param_set) - def on_completion(self, data): + def process_result_set(self): id_set = [] - for _, _, r_set in self: - for row in r_set: + for _, _, row_set in self: + for row in row_set: for clo in row: id_set.append(clo) @@ -219,7 +211,7 @@ class DBO_add_link_set(DB_op): for q, q_params in db_util.gen_query_create_from_link_map(link_map): self.add_statement(q, q_params) - def on_completion(self, data): + def process_result_set(self): id_set = [] for _, _, r_set in self: for row in r_set: @@ -240,10 +232,6 @@ class DBO_load_node_set_by_DB_id(DB_op): q = "start n=node({id_set}) return n" self.add_statement(q, { 'id_set': id_set}) - def on_completion(self, data): - log.debug('loaded node set: ' + str(data)) - return self.parse_single_query_response_data(data) - class DBO_match_node_id_set(DB_op): def __init__(self, filter_type=None, filter_attr_map=None): @@ -265,10 +253,6 @@ class DBO_match_node_id_set(DB_op): self.add_statement(q, q_params) - def on_completion(self, data): - log.debug('loaded id-set: ' + str(data)) - return self.parse_single_query_response_data(data) - class DBO_match_node_set_by_id_attribute(DBO_match_node_id_set): def __init__(self, id_set): """ @@ -302,10 +286,6 @@ class DBO_match_link_set_by_src_or_dst_id_attributes(DB_op): self.add_statement(q, q_params) - def on_completion(self, data): - log.debug('loaded id-set: ' + str(data)) - return self.parse_single_query_response_data(data) - class DBO_match_link_id_set(DB_op): def __init__(self, filter_type=None, filter_attr_map={}): """ @@ -325,10 +305,6 @@ class DBO_match_link_id_set(DB_op): self.add_statement(q, q_params) - def on_completion(self, data): - log.debug('loaded id-set: ' + str(data)) - return self.parse_single_query_response_data(data) - class DB_Driver_Base(): pass -- cgit v1.3.1 From f821ead8d1697702917817ce916e50f1c9e0cc3c Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 20 Oct 2014 19:30:34 +0200 Subject: DBO_match_node_id_set query generation --- src-py/db_controller.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 0ef70f3b..875eb0bc 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -234,22 +234,23 @@ class DBO_load_node_set_by_DB_id(DB_op): class DBO_match_node_id_set(DB_op): - def __init__(self, filter_type=None, filter_attr_map=None): + def __init__(self, filter_type=None, filter_attr_map={}): """ - load a set of nodes according to filter_attr_map + match a set of nodes by type / attr_map + @param filter_type: node type filter @param filter_attr_map: is a filter_key to filter_value_set map of - attributes to match against, eg.: + possible attributes to match against, eg.: { 'id':[0,1], 'color: ['red','blue'] } - @param filter_type: node type filter - @return: loaded node set or an empty set if no match was found + @return: a set of node DB id's """ + super(DBO_match_node_id_set, self).__init__() - filter_str = dbu.where_clause_from_filter_attr_map() + q = "match (n{filter_type}) {where_clause} return id(n)" + q = cfmt(q, filter_type="" if not filter_type else ":" + filter_type) + q = cfmt(q, where_clause=db_util.gen_clause_where_from_filter_attr_map(filter_attr_map)) - super(DBO_load_node_set, self).__init__() - q = "match (n) {0} return n".format(filter_str) - self.add_statement(q, params=filter_attr_map) + q_params = filter_attr_map self.add_statement(q, q_params) -- cgit v1.3.1 From 957076abcebb5dc898852b6c58733fa0b408b008 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 20 Oct 2014 19:31:06 +0200 Subject: Neo4JException --- src-py/db_controller.py | 14 ++++++++++---- src-py/neo4j_util.py | 7 +++++++ 2 files changed, 17 insertions(+), 4 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 875eb0bc..7cd1af7b 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -351,15 +351,21 @@ class DB_Driver_REST(DB_Driver_Base): except Exception as e: raise Exception('failed to open transaction:' + e.message) - def exex_op_statements(self, op): + def exec_statement_set(self, op): + tx_url = "{0}/{1}".format(self.tx_base_url, op.tx_id) statement_set = db_util.statement_set_to_REST_form(op.statement_set) try: - ret = db_util.post_neo4j(tx_url, statement_set) - op._assign_results_errors(ret) + post_ret = db_util.post_neo4j(tx_url, statement_set) + op.result_set = post_ret['results'] + op.error_set = post_ret['errors'] + if 0 != len(op.error_set): + raise Neo4JException(op.error_set) + self.log_committed_queries(statement_set) - return ret + except Neo4JException as e: + raise e except Exception as e: raise Exception('failed exec op statements: err: {0}, url: {1}'.format(e.message, tx_url)) diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 8d70ced9..5b7cfee7 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -7,6 +7,13 @@ import urllib2 import model import string +class Neo4JException(Exception): + def __init__(self, error_set): + self.error_set = error_set + + def __str__(self): + return 'neo4j error set: ' + str(self.error_set) + class DB_row(object): def __init__(self, data): self.data = data -- cgit v1.3.1 From f7c4f306e5ab1ecf8dbe39c6997836f7cfddad45 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 20 Oct 2014 19:32:16 +0200 Subject: augment test_attr_diff_commit - test attr_write, attr_rm separately --- src-py/test_db_controller.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index c7834749..359474b6 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -207,17 +207,33 @@ class TestDBController(unittest.TestCase): self.db_ctl.exec_op(op) # apply attr_diff - attr_diff = {n_id: {'attr_write': {'attr_0': 0, - 'attr_1': 'a'}, - 'attr_remove': ['attr_rm']}} + attr_diff = Attr_Diff() + attr_diff.add_node_attr_write(n_id, 'attr_0', 0) + attr_diff.add_node_attr_write(n_id, 'attr_1', 'a') + attr_diff.add_node_attr_rm(n_id, 'attr_rm') + op = dbc.DBO_attr_diff_commit(attr_diff) n_map = self.db_ctl.exec_op(op) self.assertEqual(len(n_map), 1) n = n_map.get(n_id) self.assertTrue(None != n) self.assertTrue(None == n.get('attr_rm')) - self.assertTrue(None != n.get('attr_0')) - self.assertTrue(None != n.get('attr_1')) + self.assertEqual(0, n.get('attr_0')) + self.assertEqual('a', n.get('attr_1')) + + # attr-set only + attr_diff = Attr_Diff() + attr_diff.add_node_attr_write(n_id, 'attr_2', 0) + + op = dbc.DBO_attr_diff_commit(attr_diff) + n_map = self.db_ctl.exec_op(op) + + # attr-remove only + attr_diff = Attr_Diff() + attr_diff.add_node_attr_rm(n_id, 'attr_2') + + op = dbc.DBO_attr_diff_commit(attr_diff) + n_map = self.db_ctl.exec_op(op) def tearDown(self): pass -- cgit v1.3.1 From 182827506334e8ebe4e8478a49c4e0c9b0187677 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 20 Oct 2014 19:32:50 +0200 Subject: augment test_topo_diff_commit - extra node creation --- src-py/test_db_controller.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 359474b6..502768a3 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -179,9 +179,11 @@ class TestDBController(unittest.TestCase): def test_topo_diff_commit(self): n_0_id = rand_id() n_1_id = rand_id() + n_2_id = rand_id() n_set = [{'__type': 'T_test_topo_diff_commit', 'id': n_0_id }, - {'__type': 'T_test_topo_diff_commit', 'id': n_1_id }] + {'__type': 'T_test_topo_diff_commit', 'id': n_1_id }, + {'__type': 'T_test_topo_diff_commit', 'id': n_2_id }] l_set = [{'__type': 'T_test_topo_diff_commit', '__src': n_0_id, '__dst': n_1_id}, {'__type': 'T_test_topo_diff_commit', '__src': n_1_id, '__dst': n_0_id}] @@ -189,14 +191,16 @@ class TestDBController(unittest.TestCase): link_set_add=l_set) op = dbc.DBO_topo_diff_commit(topo_diff) - self.assertEqual(len(op.statement_set), 3) # one parameterized node create. 2 link create - self.db_ctl.exec_op(op) - + op_ret = self.db_ctl.exec_op(op) + self.assertEqual(len(op_ret), 2) # to id-sets, nodes & links + self.assertEqual(len(op_ret[0]), 3) # expect id-set of length 3 + self.assertEqual(len(op_ret[1]), 2) # expect id-set of length 2 + id_set = self.db_ctl.exec_op(dbc.DBO_match_node_set_by_id_attribute([n_0_id, n_1_id])) self.assertEqual(len(id_set), 2) - id_set = self.db_ctl.exec_op(dbc.DBO_match_link_set_by_src_or_dst_id_attributes(src_id=n_0_id, dst_id=n_1_id)) + id_set = self.db_ctl.exec_op(dbc.DBO_load_link_set_by_src_or_dst_id_attributes(src_id=n_0_id, dst_id=n_1_id)) self.assertEqual(len(id_set), 1) - id_set = self.db_ctl.exec_op(dbc.DBO_match_link_set_by_src_or_dst_id_attributes(src_id=n_1_id, dst_id=n_0_id)) + id_set = self.db_ctl.exec_op(dbc.DBO_load_link_set_by_src_or_dst_id_attributes(src_id=n_1_id, dst_id=n_0_id)) self.assertEqual(len(id_set), 1) def test_attr_diff_commit(self): -- cgit v1.3.1 From 7095cc48865cc46ad20f28de79ba62d80e8d2153 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 20 Oct 2014 19:34:34 +0200 Subject: Attr_Diff: - add_node_attr_write() - add_node_attr_rm() --- src-py/model/graph.py | 33 +++++++++++++++++++++++++++------ src-py/test_db_controller.py | 2 +- 2 files changed, 28 insertions(+), 7 deletions(-) (limited to 'src-py') diff --git a/src-py/model/graph.py b/src-py/model/graph.py index 9ad4601c..52ed39b1 100644 --- a/src-py/model/graph.py +++ b/src-py/model/graph.py @@ -1,14 +1,35 @@ -class Attribute_Diff(): +class Attr_Diff(dict): """ Represents a change to note attributes, where nodes can represent either logical nodes or logical links, and attributes can be added, changed or removed + + Example: + attr_diff = {n_id: {'attr_write': {'attr_0': 0, + 'attr_1': 'a'}, + 'attr_remove': ['attr_2'] } + } """ - def __init__(self, id_to_attr_diff_map={}): - """ - @param id_to_attr_diff_map - """ - self.id_to_attr_diff_map = id_to_attr_diff_map + def __init__(self): + pass + + def init_node_attr_diff(self, n_id): + ret = {'attr_write': {}, + 'attr_remove': []} + self[n_id] = ret + return ret + + def add_node_attr_write(self, n_id, attr_name, attr_val): + n_attr_diff = self.get(n_id) + if None == n_attr_diff: + n_attr_diff = self.init_node_attr_diff(n_id) + n_attr_diff['attr_write'][attr_name] = attr_val + + def add_node_attr_rm(self, n_id, attr_name): + n_attr_diff = self.get(n_id) + if None == n_attr_diff: + n_attr_diff = self.init_node_attr_diff(n_id) + n_attr_diff['attr_remove'].append(attr_name) class Topo_Diff(): """ diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 502768a3..4af4cabc 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -167,7 +167,7 @@ class TestDBController(unittest.TestCase): op.add_statement("non-valid statement #1", {}) op.add_statement("non-valid statement #2", {}) - self.db_ctl.exec_op(op) + self.assertRaises(Neo4JException, self.db_ctl.exec_op, op) self.assertEqual(len(op.result_set), 2) self.assertEqual(len(op.error_set), 1) -- cgit v1.3.1 From c97f56e93d3bb3a7b838093b0ecb4fec9c9e0d34 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 20 Oct 2014 19:34:43 +0200 Subject: misc --- src-py/model/graph.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) (limited to 'src-py') diff --git a/src-py/model/graph.py b/src-py/model/graph.py index 52ed39b1..8942285d 100644 --- a/src-py/model/graph.py +++ b/src-py/model/graph.py @@ -31,7 +31,7 @@ class Attr_Diff(dict): n_attr_diff = self.init_node_attr_diff(n_id) n_attr_diff['attr_remove'].append(attr_name) -class Topo_Diff(): +class Topo_Diff(object): """ Represents a change to the graph topology """ @@ -45,18 +45,15 @@ class Topo_Diff(): self.node_set_add = node_set_add self.link_set_add = link_set_add - @property - def link_set_rm(self): - return self.link_set_rm - - @property - def node_set_rm(self): - return self.node_set_rm - - @property - def link_set_add(self): - return self.link_set_add + def check_validity(self, topo_diff_dict): + """ + Topo_Diff may represent invalid operations, eg. adding a link while + removing it's end-point - this stub should check for that + """ + pass - @property - def node_set_add(self): - return self.node_set_add + @staticmethod + def from_dict(topo_diff_dict): + ret = Topo_Diff() + ret.__dict__ = topo_diff_dict + return ret -- cgit v1.3.1 From 8d35498c06d3e832bdbcad2916efe2df9b23492d Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 21 Oct 2014 19:56:58 +0200 Subject: introducing the Link_Ptr concept - able to fuzzy point at a link by src_id, dst_id or both --- src-py/db_controller.py | 40 +++++++++++++++++++++++++--------------- src-py/model/model.py | 25 +++++++++++++++++++++++-- src-py/test_db_controller.py | 29 +++++++++++++++++++---------- 3 files changed, 67 insertions(+), 27 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 7cd1af7b..ceb5267c 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -264,28 +264,38 @@ class DBO_match_node_set_by_id_attribute(DBO_match_node_id_set): super(DBO_match_node_set_by_id_attribute, self).__init__(filter_attr_map={'id': id_set}) -class DBO_match_link_set_by_src_or_dst_id_attributes(DB_op): - def __init__(self, src_id=None, dst_id=None): +class DBO_load_link_set(DB_op): + def __init__(self, link_ptr_set): """ - match a set of links by source/target node id attributes + match a set of sets of links by source/target node id attributes + This class should be instantiated through a static factory function + + @link_ptr_set link pointer set @return: a set of loaded links """ - assert None != src_id or None != dst_id + super(DBO_load_link_set, self).__init__() + + for l_ptr in link_ptr_set: + if not l_ptr.src_id: + q = "match ()-[r]->({id: {dst_id}}) return r" + q_params = {'dst_id': l_ptr.dst_id} + elif not l_ptr.dst_id: + q = "match ({id: {src_id}})-[r]->() return r" + q_params = {'src_id': l_ptr.src_id} + else: + q = "match ({id: {src_id}})-[r]->({id: {dst_id}}) return r" + q_params = {'src_id': l_ptr.src_id, 'dst_id': l_ptr.dst_id} - super(DBO_match_link_set_by_src_or_dst_id_attributes, self).__init__() + self.add_statement(q, q_params) - if not src_id: - q = "match ()-[r]->({id: {dst_id}}) return r" - q_params = {'dst_id': dst_id} - elif not dst_id: - q = "match ({id: {src_id}})-[r]->() return r" - q_params = {'src_id': src_id} - else: - q = "match ({id: {src_id}})-[r]->({id: {dst_id}}) return r" - q_params = {'src_id': src_id, 'dst_id': dst_id} + @staticmethod + def init_from_link_ptr(l_ptr): + return DBO_load_link_set([l_ptr]) - self.add_statement(q, q_params) + @staticmethod + def init_from_link_ptr_set(l_ptr_set): + return DBO_load_link_set(l_ptr_set) class DBO_match_link_id_set(DB_op): def __init__(self, filter_type=None, filter_attr_map={}): diff --git a/src-py/model/model.py b/src-py/model/model.py index 9ee61397..701074ab 100644 --- a/src-py/model/model.py +++ b/src-py/model/model.py @@ -1,4 +1,4 @@ -class link(): +class Link(): """ documentation anchor - this class currently carries no implementation and only acts as a documentation anchor @@ -6,4 +6,25 @@ class link(): link['__src'] - meta attribute for link source link['__dst'] - meta attribute for link destination """ - pass + + class Link_Ptr(dict): + def __init__(self, src_id=None, dst_id=None): + assert None != src_id or None != dst_id + + self['__src'] = src_id + self['__dst'] = dst_id + + @property + def src_id(self): + return self['__src'] + + @property + def dst_id(self): + return self['__dst'] + + @staticmethod + def link_ptr(src_id=None, dst_id=None): + """ + init from src_id or dst_id attributes - at least one must be provided + """ + return Link.Link_Ptr(src_id, dst_id) diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 4af4cabc..cdc0c5e9 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -122,19 +122,28 @@ class TestDBController(unittest.TestCase): id_set = self.db_ctl.exec_op(op) self.assertEqual(len(id_set), 0) - def test_match_link_set_by_src_or_dst_id_attributes(self): - op = dbc.DBO_match_link_set_by_src_or_dst_id_attributes(src_id='person_00', dst_id='skill_00') - n_set = self.db_ctl.exec_op(op) - self.assertEqual(len(n_set), 1) + def test_load_link_set(self): + l_ptr = Link.link_ptr(src_id='person_00', dst_id='skill_00') + op = dbc.DBO_load_link_set.init_from_link_ptr(l_ptr) + l_set = self.db_ctl.exec_op(op) + self.assertEqual(len(l_set), 1) - op = dbc.DBO_match_link_set_by_src_or_dst_id_attributes(src_id='person_00') - n_set = self.db_ctl.exec_op(op) - self.assertEqual(len(n_set), 2) + l_ptr = Link.link_ptr(src_id='person_00') + op = dbc.DBO_load_link_set.init_from_link_ptr(l_ptr) + l_set = self.db_ctl.exec_op(op) + self.assertEqual(len(l_set), 2) - op = dbc.DBO_match_link_set_by_src_or_dst_id_attributes(dst_id='skill_00') - n_set = self.db_ctl.exec_op(op) - self.assertEqual(len(n_set), 1) + l_ptr = Link.link_ptr(dst_id='skill_00') + op = dbc.DBO_load_link_set.init_from_link_ptr(l_ptr) + l_set = self.db_ctl.exec_op(op) + self.assertEqual(len(l_set), 1) + # load sets + l_ptr_set = [Link.link_ptr(s,d) for (s,d) in [('person_00', 'skill_00'), ('person_00', 'skill_01')]] + op = dbc.DBO_load_link_set.init_from_link_ptr_set(l_ptr_set) + l_set = self.db_ctl.exec_op(op) + self.assertEqual(len(l_set), 2) + def test_load_node_set_by_DB_id(self): """ test node DB id life cycle -- cgit v1.3.1 From a48c2f33fe91cfb2645013b0174e7ce0e94a04b9 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 21 Oct 2014 19:57:38 +0200 Subject: mv DB_Driver classes to their own file --- src-py/db_controller.py | 83 -------------------------------------------- src-py/db_driver.py | 91 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 83 deletions(-) create mode 100644 src-py/db_driver.py (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index ceb5267c..84c0743e 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -316,89 +316,6 @@ class DBO_match_link_id_set(DB_op): self.add_statement(q, q_params) -class DB_Driver_Base(): - pass - -class DB_Driver_Embedded(DB_Driver_Base): - def __init__(self, db_base_url): - self.tx_base_url = db_base_url + '/db/data/transaction' - - from org.rhizi.db.neo4j.util import EmbeddedNeo4j - self.edb = EmbeddedNeo4j.createDb() - self.edb.createDb() - - def begin_tx(self, op): - pass - - def exec_statement_set(self, op): - s_set = op.statement_set - self.edb.executeCypherQury() - - def commit_tx(self, op): - pass - - def log_committed_queries(self, statement_set): - for sp_dict in statement_set['statements']: - log.debug('\tq: {0}'.format(sp_dict['statement'])) - -class DB_Driver_REST(DB_Driver_Base): - def __init__(self, db_base_url): - self.tx_base_url = db_base_url + '/db/data/transaction' - - def begin_tx(self, op): - tx_open_url = self.tx_base_url - - try: - # - # [!] neo4j seems picky about receiving an additional empty statement list - # - data = data = db_util.statement_set_to_REST_form([]) - ret = db_util.post_neo4j(tx_open_url, data) - tx_commit_url = ret['commit'] - op.parse_tx_id(tx_commit_url) - - log.debug('tx-open: id: {0}, commit-url: {1}'.format(op.tx_id, tx_commit_url)) - except Exception as e: - raise Exception('failed to open transaction:' + e.message) - - def exec_statement_set(self, op): - - tx_url = "{0}/{1}".format(self.tx_base_url, op.tx_id) - statement_set = db_util.statement_set_to_REST_form(op.statement_set) - - try: - post_ret = db_util.post_neo4j(tx_url, statement_set) - op.result_set = post_ret['results'] - op.error_set = post_ret['errors'] - if 0 != len(op.error_set): - raise Neo4JException(op.error_set) - - self.log_committed_queries(statement_set) - except Neo4JException as e: - raise e - except Exception as e: - raise Exception('failed exec op statements: err: {0}, url: {1}'.format(e.message, tx_url)) - - def commit_tx(self, op): - tx_commit_url = "{0}/{1}/commit".format(self.tx_base_url, op.tx_id) - - try: - # - # [!] neo4j seems picky about receiving an additional empty statement list - # - data = db_util.statement_set_to_REST_form([]) - ret = db_util.post(tx_commit_url, data) - - log.debug('tx-commit: id: {0}, commit-url: {1}'.format(op.tx_id, tx_commit_url)) - - return ret - except Exception as e: - raise Exception('failed to commit transaction:' + e.message) - - def log_committed_queries(self, statement_set): - for sp_dict in statement_set['statements']: - log.debug('\tq: {0}'.format(sp_dict['statement'])) - class DB_Controller: """ neo4j DB controller diff --git a/src-py/db_driver.py b/src-py/db_driver.py new file mode 100644 index 00000000..11eace2c --- /dev/null +++ b/src-py/db_driver.py @@ -0,0 +1,91 @@ +import logging + +from neo4j_util import Neo4JException +import neo4j_util as db_util + + +log = logging.getLogger('rhizi') + +class DB_Driver_Base(): + pass + +class DB_Driver_Embedded(DB_Driver_Base): + def __init__(self, db_base_url): + self.tx_base_url = db_base_url + '/db/data/transaction' + + from org.rhizi.db.neo4j.util import EmbeddedNeo4j + self.edb = EmbeddedNeo4j.createDb() + self.edb.createDb() + + def begin_tx(self, op): + pass + + def exec_statement_set(self, op): + s_set = op.statement_set + self.edb.executeCypherQury() + + def commit_tx(self, op): + pass + + def log_committed_queries(self, statement_set): + for sp_dict in statement_set['statements']: + log.debug('\tq: {0}'.format(sp_dict['statement'])) + +class DB_Driver_REST(DB_Driver_Base): + def __init__(self, db_base_url): + self.tx_base_url = db_base_url + '/db/data/transaction' + + def begin_tx(self, op): + tx_open_url = self.tx_base_url + + try: + # + # [!] neo4j seems picky about receiving an additional empty statement list + # + data = data = db_util.statement_set_to_REST_form([]) + ret = db_util.post_neo4j(tx_open_url, data) + tx_commit_url = ret['commit'] + op.parse_tx_id(tx_commit_url) + + log.debug('tx-open: id: {0}, commit-url: {1}'.format(op.tx_id, tx_commit_url)) + except Exception as e: + raise Exception('failed to open transaction:' + e.message) + + def exec_statement_set(self, op): + + tx_url = "{0}/{1}".format(self.tx_base_url, op.tx_id) + statement_set = db_util.statement_set_to_REST_form(op.statement_set) + + try: + post_ret = db_util.post_neo4j(tx_url, statement_set) + op.result_set = post_ret['results'] + op.error_set = post_ret['errors'] + if 0 != len(op.error_set): + raise Neo4JException(op.error_set) + + self.log_committed_queries(statement_set) + except Neo4JException as e: + raise e + except Exception as e: + raise Exception('failed exec op statements: err: {0}, url: {1}'.format(e.message, tx_url)) + + def commit_tx(self, op): + tx_commit_url = "{0}/{1}/commit".format(self.tx_base_url, op.tx_id) + + try: + # + # [!] neo4j seems picky about receiving an additional empty statement list + # + data = db_util.statement_set_to_REST_form([]) + ret = db_util.post(tx_commit_url, data) + + log.debug('tx-commit: id: {0}, commit-url: {1}'.format(op.tx_id, tx_commit_url)) + + return ret + except Exception as e: + raise Exception('failed to commit transaction:' + e.message) + + def log_committed_queries(self, statement_set): + for sp_dict in statement_set['statements']: + log.debug('\tq: {0}'.format(sp_dict['statement'])) + -- cgit v1.3.1 From d0278d6627d5b1d50ac0697307a3259f469698f8 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 21 Oct 2014 19:58:19 +0200 Subject: enable neo4j JSON streaming by default --- src-py/neo4j_util.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 5b7cfee7..642e094a 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -75,6 +75,8 @@ def post(url, data): req.add_header('Accept', 'application/json; charset=UTF-8') req.add_header('Content-Type', 'application/json') + req.add_header('X-Stream', 'true') # enable neo4j JSON streaming + try: ret = urllib2.urlopen(req, post_data_json) except urllib2.HTTPError as e: -- cgit v1.3.1 From 53e56e2caec6863cc590ac1e74dfa950d307e2b9 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 21 Oct 2014 20:40:11 +0200 Subject: revert back to flush_db test setup - see https://github.com/Rhizi/rhizi-server/issues/1 --- src-py/test_db_controller.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index cdc0c5e9..1c658056 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -5,9 +5,11 @@ import db_controller as dbc from rhizi_server import Config from neo4j_test_util import rand_id from neo4j_test_util import flush_db +from neo4j_util import Neo4JException -from model.graph import Attribute_Diff as Attr_Diff -from model.graph import Topo_Diff as Topo_Diff +from model.graph import Attr_Diff +from model.graph import Topo_Diff +from model.model import Link class TestDBController(unittest.TestCase): @@ -30,12 +32,13 @@ class TestDBController(unittest.TestCase): def setUpClass(self): cfg = Config.init_from_file('res/etc/rhizi-server.conf') self.db_ctl = dbc.DB_Controller(cfg) - self.db_ctl.exec_op(dbc.DBO_add_node_set(self.n_map)) - self.db_ctl.exec_op(dbc.DBO_add_link_set(self.l_map)) self.log = logging.getLogger('rhizi') + self.log.addHandler(logging.StreamHandler()) def setUp(self): - pass + flush_db(self.db_ctl) # remove once embedded DB test mode is supported + self.db_ctl.exec_op(dbc.DBO_add_node_set(self.n_map)) + self.db_ctl.exec_op(dbc.DBO_add_link_set(self.l_map)) def test_db_op_statement_iteration(self): s_arr = ['create (b:Book {title: \'foo\'}) return b', @@ -107,7 +110,8 @@ class TestDBController(unittest.TestCase): n_set = self.db_ctl.exec_op(dbc.DBO_match_node_id_set(filter_attr_map=fam)) self.assertEqual(len(n_set), 2) - def test_match_node_set_by_DB_id(self): pass # TODO + def test_match_node_set_by_DB_id(self): + pass # TODO def test_match_node_set_by_id_attribute(self): n_set = self.db_ctl.exec_op(dbc.DBO_match_node_set_by_id_attribute(['skill_00', 'person_01'])) @@ -148,7 +152,7 @@ class TestDBController(unittest.TestCase): """ test node DB id life cycle """ - + # create nodes, get DB ids op = dbc.DBO_add_node_set({'T_test_load_node_set_by_DB_id': [{'name': 'John Doe'}, {'name': 'John Doe'}]}) -- cgit v1.3.1 From 671e9d2efe7f3ad6b6a67ba737da8758e24fbb73 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 21 Oct 2014 21:39:39 +0200 Subject: load_link_set_by_link_ptr_set: introducing a deserialize_param_set() step --- src-py/rhizi_api.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 2744e355..07778896 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -83,6 +83,32 @@ def __load_node_set_by_id_attr_common(id_set): def add_node(): # pending decision regarding support for single object operations assert False +@webapp.route("/load/link-set/by_link_ptr_set", methods=['POST']) +def load_link_set_by_link_ptr_set(): + + def deserialize_param_set(param_json): + l_ptr_set_raw = param_json['link_ptr_set'] + + __sanitize_input(l_ptr_set_raw) + + l_ptr_set = [] + for lptr_dict in l_ptr_set_raw: + src_id = lptr_dict.get('src_id') + dst_id = lptr_dict.get('dst_id') + l_ptr_set += [Link.Link_Ptr(src_id=src_id, dst_id=dst_id) ] + + return l_ptr_set + + l_ptr_set = deserialize_param_set(request.get_json()) + + op = dbc.DBO_load_link_set.init_from_link_ptr_set(l_ptr_set) + try: + l_set = db_ctl.exec_op(op) + return __common_resp_handle(data=l_set) + except Exception as e: + log.exception(e) + return __common_resp_handle(error='unable to load link set') + @webapp.route("/graph/topo-diff-commit", methods=['POST']) def topo_diff_commit(): """ -- cgit v1.3.1 From 4ef04b9a1758ccd5bfc6f355a821de852efebe68 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 21 Oct 2014 21:41:41 +0200 Subject: acknowledge duplicate links may be returned in response --- src-py/test_db_controller.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 1c658056..54952df1 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -142,12 +142,19 @@ class TestDBController(unittest.TestCase): l_set = self.db_ctl.exec_op(op) self.assertEqual(len(l_set), 1) - # load sets - l_ptr_set = [Link.link_ptr(s,d) for (s,d) in [('person_00', 'skill_00'), ('person_00', 'skill_01')]] + # load by l_ptr sets + l_ptr_set = [Link.link_ptr(s, d) for (s, d) in [('person_00', 'skill_00'), ('person_00', 'skill_01')]] op = dbc.DBO_load_link_set.init_from_link_ptr_set(l_ptr_set) l_set = self.db_ctl.exec_op(op) self.assertEqual(len(l_set), 2) - + + # this should return the same link twice + l_ptr_set = [Link.link_ptr(s, d) for (s, d) in [('person_00', 'skill_00'), ('person_00', 'skill_01')]] + l_ptr_set.append(Link.link_ptr(dst_id='skill_00')) + op = dbc.DBO_load_link_set.init_from_link_ptr_set(l_ptr_set) + l_set = self.db_ctl.exec_op(op) + self.assertEqual(len(l_set), 3) + def test_load_node_set_by_DB_id(self): """ test node DB id life cycle -- cgit v1.3.1 From 9b89717dfc286af0127108b221630f9c1d054971 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 27 Oct 2014 12:31:48 +0200 Subject: DBO_rm_node_set & test --- src-py/db_controller.py | 19 +++++++++++++++++++ src-py/test_db_controller.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 84c0743e..c73a6882 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -316,6 +316,25 @@ class DBO_match_link_id_set(DB_op): self.add_statement(q, q_params) +class DBO_rm_node_set(DB_op): + def __init__(self, id_set, rm_links=False): + super(DBO_rm_node_set, self).__init__() + + if rm_links: + q_arr = ['match (n)', + 'where n.id in ' + str(id_set), + 'optional match (n)-[r]-()', + 'delete n,r' + ] + else: + q_arr = ['match (n)', + 'where n.id in ' + str(id_set), + 'delete n' + ] + + q = ' '.join(q_arr) # TODO: use id param upon neo4j support: q_params = {'id_set': id_set} + self.add_statement(q) + class DB_Controller: """ neo4j DB controller diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 54952df1..4b17ad3c 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -259,6 +259,37 @@ class TestDBController(unittest.TestCase): op = dbc.DBO_attr_diff_commit(attr_diff) n_map = self.db_ctl.exec_op(op) + def test_rm_node_set(self): + n_0_id = rand_id() + n_1_id = rand_id() + n_2_id = rand_id() + n_3_id = rand_id() + n_T = 'T_test_rm_node_set' + + n_set = [{'__type': n_T, 'id': n_0_id }, + {'__type': n_T, 'id': n_1_id }, + {'__type': n_T, 'id': n_2_id }, + {'__type': n_T, 'id': n_3_id }] + l_set = [{'__type': n_T, '__src': n_2_id, '__dst': n_2_id}, + {'__type': n_T, '__src': n_2_id, '__dst': n_3_id}] + + topo_diff = Topo_Diff(node_set_add=n_set, + link_set_add=l_set) + + op = dbc.DBO_topo_diff_commit(topo_diff) + self.db_ctl.exec_op(op) + + op = dbc.DBO_rm_node_set([n_0_id, n_1_id]) + self.db_ctl.exec_op(op) + + op = dbc.DBO_rm_node_set([n_2_id, n_3_id], rm_links=True) + self.db_ctl.exec_op(op) + + # assert all deleted + op = dbc.DBO_match_node_id_set(filter_type=n_T) + id_set = self.db_ctl.exec_op(op) + self.assertEqual(len(id_set), 0) + def tearDown(self): pass if __name__ == "__main__": -- cgit v1.3.1 From 6629438b2f1a9d54d152281342d4d7e0641b7254 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 27 Oct 2014 12:33:02 +0200 Subject: support node removals in DBO_topo_diff_commit --- src-py/db_controller.py | 18 +++++++++++++----- src-py/test_db_controller.py | 27 ++++++++++++++++++++------- 2 files changed, 33 insertions(+), 12 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index c73a6882..aa1e8450 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -124,22 +124,30 @@ class DBO_topo_diff_commit(DB_composed_op): # TODO rm link set # TODO rm node set - assert 0 == len(topo_diff.node_set_rm), 'unsupported' assert 0 == len(topo_diff.link_set_rm), 'unsupported' n_add_map = db_util.meta_attr_list_to_meta_attr_map(topo_diff.node_set_add) l_add_map = db_util.meta_attr_list_to_meta_attr_map(topo_diff.link_set_add) + l_rm_set = [] + n_rm_set = topo_diff.node_set_rm # # [!] order critical # if len(n_add_map) > 0: - op_n_add = DBO_add_node_set(n_add_map) - self.add_sub_op(op_n_add) + op = DBO_add_node_set(n_add_map) + self.add_sub_op(op) if len(l_add_map) > 0: - op_l_add = DBO_add_link_set(l_add_map) - self.add_sub_op(op_l_add) + op = DBO_add_link_set(l_add_map) + self.add_sub_op(op) + + if len(l_rm_set) > 0: + pass + + if len(n_rm_set) > 0: + op = DBO_rm_node_set(n_rm_set) + self.add_sub_op(op) class DBO_attr_diff_commit(DB_op): """ diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 4b17ad3c..002777d7 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -200,12 +200,13 @@ class TestDBController(unittest.TestCase): n_0_id = rand_id() n_1_id = rand_id() n_2_id = rand_id() + n_T = 'T_test_topo_diff_commit' - n_set = [{'__type': 'T_test_topo_diff_commit', 'id': n_0_id }, - {'__type': 'T_test_topo_diff_commit', 'id': n_1_id }, - {'__type': 'T_test_topo_diff_commit', 'id': n_2_id }] - l_set = [{'__type': 'T_test_topo_diff_commit', '__src': n_0_id, '__dst': n_1_id}, - {'__type': 'T_test_topo_diff_commit', '__src': n_1_id, '__dst': n_0_id}] + n_set = [{'__type': n_T, 'id': n_0_id }, + {'__type': n_T, 'id': n_1_id }, + {'__type': n_T, 'id': n_2_id }] + l_set = [{'__type': n_T, '__src': n_0_id, '__dst': n_1_id}, + {'__type': n_T, '__src': n_1_id, '__dst': n_0_id}] topo_diff = Topo_Diff(node_set_add=n_set, link_set_add=l_set) @@ -218,11 +219,23 @@ class TestDBController(unittest.TestCase): id_set = self.db_ctl.exec_op(dbc.DBO_match_node_set_by_id_attribute([n_0_id, n_1_id])) self.assertEqual(len(id_set), 2) - id_set = self.db_ctl.exec_op(dbc.DBO_load_link_set_by_src_or_dst_id_attributes(src_id=n_0_id, dst_id=n_1_id)) + + l_ptr = Link.link_ptr(src_id=n_0_id, dst_id=n_1_id) + id_set = self.db_ctl.exec_op(dbc.DBO_load_link_set.init_from_link_ptr(l_ptr)) self.assertEqual(len(id_set), 1) - id_set = self.db_ctl.exec_op(dbc.DBO_load_link_set_by_src_or_dst_id_attributes(src_id=n_1_id, dst_id=n_0_id)) + + l_ptr = Link.link_ptr(src_id=n_1_id, dst_id=n_0_id) + id_set = self.db_ctl.exec_op(dbc.DBO_load_link_set.init_from_link_ptr(l_ptr)) self.assertEqual(len(id_set), 1) + id_set_rm=[n_2_id] + topo_diff = Topo_Diff(node_set_rm=id_set_rm) + op = dbc.DBO_topo_diff_commit(topo_diff) + self.db_ctl.exec_op(op) + op = dbc.DBO_match_node_set_by_id_attribute(id_set_rm) + id_set = self.db_ctl.exec_op(op) + self.assertEqual(len(id_set), 0) + def test_attr_diff_commit(self): # create test node n_id = rand_id() -- cgit v1.3.1 From 1e314f66a75a0cd32b933771fe1031891af68d2c Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 27 Oct 2014 12:35:13 +0200 Subject: REST api impl: '/graph/topo-diff-commit' --- src-py/rhizi_api.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 07778896..77f753e6 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -114,7 +114,13 @@ def topo_diff_commit(): """ commit a graph topology diff """ - pass + topo_diff_dict = request.get_json()['topo_diff'] + __sanitize_input(topo_diff_dict) + + topo_diff = Topo_Diff.from_dict(topo_diff_dict) + + op = dbc.DBO_topo_diff_commit(topo_diff) + return __common_exec(op) @webapp.route("/graph/attr-diff-commit", methods=['POST']) def attr_diff_commit(): -- cgit v1.3.1 From d311fcc30a3da95a5a8e4b15f99721a1aae0229c Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 27 Oct 2014 12:35:40 +0200 Subject: REST api impl: '/graph/attr-diff-commit' --- src-py/rhizi_api.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 77f753e6..5fce4542 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -127,7 +127,11 @@ def attr_diff_commit(): """ commit a graph attribute diff """ - pass + attr_diff = request.get_json()['attr_diff'] + __sanitize_input(attr_diff) + + op = dbc.DBO_attr_diff_commit(attr_diff) + return __common_exec(op) @webapp.route("/add/node-set", methods=['POST']) def add_node_set(): -- cgit v1.3.1 From de25fac01980581f98c1ea1f0740e4088aa0c0be Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 27 Oct 2014 12:40:55 +0200 Subject: rm single obj api calls --- src-py/rhizi_api.py | 8 -------- 1 file changed, 8 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 5fce4542..c22df635 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -52,10 +52,6 @@ def __common_resp_handle(data=None, error=None): return resp -@webapp.route("/load/node-single", methods=['POST']) -def load_node_single_by_id_attr(): - # pending decision regarding support for single object operations - assert False @webapp.route("/load/node-set", methods=['POST']) def load_node_set_by_id_attr(): @@ -79,10 +75,6 @@ def __load_node_set_by_id_attr_common(id_set): log.exception(e) return __common_resp_handle(error='unable to load node with ids: {0}'.format(id_set)) -@webapp.route("/add/node-single", methods=['POST']) -def add_node(): - # pending decision regarding support for single object operations - assert False @webapp.route("/load/link-set/by_link_ptr_set", methods=['POST']) def load_link_set_by_link_ptr_set(): -- cgit v1.3.1 From 69d70685cfdea0ea6b6083d36f6fefd0be0741fe Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 27 Oct 2014 12:44:49 +0200 Subject: misc --- src-py/db_controller.py | 18 +++++++++--------- src-py/rhizi_api.py | 24 ++++++++++++++++++++---- 2 files changed, 29 insertions(+), 13 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index aa1e8450..f9656212 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -1,20 +1,19 @@ #!/usr/bin/python -import os import json -import re import logging +import os +import re import traceback +import urllib2 +from db_driver import DB_Driver_REST, DB_Driver_Base from model.graph import Attr_Diff from model.graph import Topo_Diff - -import urllib2 - -import neo4j_util as db_util -from neo4j_util import cfmt from neo4j_util import DB_result_set -from neo4j_util import Neo4JException +from neo4j_util import cfmt +import neo4j_util as db_util +from model.model import Link log = logging.getLogger('rhizi') @@ -183,7 +182,7 @@ class DBO_attr_diff_commit(DB_op): ret = {} for _, _, r_set in self: for row in r_set: - n_id, n = [v for v in row] # we expect a [n_id, n] array + n_id, n = [v for v in row] # we expect a [n_id, n] array ret[n_id] = n return ret @@ -234,6 +233,7 @@ class DBO_load_node_set_by_DB_id(DB_op): """ load a set of nodes whose DB id is in id_set + @param id_set: DB node id set @return: loaded node set or an empty set if no match was found """ super(DBO_load_node_set_by_DB_id, self).__init__() diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index c22df635..7a6a44e9 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -48,26 +48,42 @@ def __common_resp_handle(data=None, error=None): ret_data = __response_wrap(data, error) resp = jsonify(ret_data) + resp.headers['Access-Control-Allow-Origin'] = '*' + # more response processing return resp +def __common_exec(op, on_success=__common_resp_handle): + try: + op_ret = db_ctl.exec_op(op) + return on_success(op_ret) + except Exception as e: + return __common_resp_handle('exception raised: add_node_set') -@webapp.route("/load/node-set", methods=['POST']) +@webapp.route("/load/node-set-by-id", methods=['POST']) def load_node_set_by_id_attr(): """ + load node-set by ID attribute + @param id_set: list of node ids to match id attribute against - @return: a list containing a single node whose id attribute matches 'id' or + @return: a list of nodes whose id attribute matches 'id' or an empty list if the requested node is not found @raise exception: on error """ - id_set = request.get_json()['id_set'] + req_json = request.get_json() + id_set = req_json['id_set'] + __sanitize_input(id_set) return __load_node_set_by_id_attr_common(id_set) def __load_node_set_by_id_attr_common(id_set): - op = dbc.DBO_load_node_set_by_id_attribute(id_set) + """ + @param f_k: optional attribute filter key + @param f_vset: possible key values to match against + """ + op = dbc.DBO_match_node_set_by_id_attribute(id_set=id_set) try: n_set = db_ctl.exec_op(op) return __common_resp_handle(data=n_set) -- cgit v1.3.1 From a4ec4c13f3dfaebe3e1b727108e7bc7e05b7fe2d Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 28 Oct 2014 00:31:15 +0200 Subject: DBO_cypher_query --- src-py/db_controller.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index f9656212..f43c9673 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -114,6 +114,14 @@ class DB_composed_op(DB_op): ret.append(s_result_set) return ret +class DBO_cypher_query(DB_op): + """ + freeform cypher query + """ + def __init__(self, q, q_params={}): + super(DBO_cypher_query, self).__init__() + self.add_statement(q, q_params) + class DBO_topo_diff_commit(DB_composed_op): """ commit a Topo_Diff @@ -384,8 +392,13 @@ class DB_Controller: def exec_cypher_query(self, q): """ - @deprecated: use transaction based api + @deprecated: use DBO_cypher_query """ # call post and not db_util.post_neo4j to avoid response key errors - db_util.post(self.config.db_base_url + '/db/data/cypher', {"query" : q}) + try: + db_util.post(self.config.db_base_url + '/db/data/cypher', {"query" : q}) + except Exception as e: + log.error(e.message) + log.error(traceback.print_exc()) + raise e -- cgit v1.3.1 From 89915ecebb2c3f8994ce3b727050ce35714acd78 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 28 Oct 2014 00:31:55 +0200 Subject: initial DBO_rz_clone op --- src-py/db_controller.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index f43c9673..a86de1a8 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -351,6 +351,51 @@ class DBO_rm_node_set(DB_op): q = ' '.join(q_arr) # TODO: use id param upon neo4j support: q_params = {'id_set': id_set} self.add_statement(q) +class DBO_rz_clone(DB_op): + def __init__(self, filter_label=None, limit=128): + """ + clone rhizi + + @return: a dict: {'node_set': n_set, + 'link_set': l_set } + where l_set is a list of (src.id, dst.id, link) tuples + """ + super(DBO_rz_clone, self).__init__() + + self.limit = limit + self.skip = 0 + + q_arr = ['match (n)' if not filter_label else 'match (n:%s)' % (filter_label), + 'optional match (n)-[r]->(m)', + 'with n,r,m', + 'order by n.id', + 'skip %d' % (self.skip), + 'limit %d' % (self.limit), + 'return n,collect([n.id, m.id, r])'] + + q = ' '.join(q_arr) + self.add_statement(q) + + def process_result_set(self): + ret_n_set = [] + ret_l_set = [] + for _, _, row_set in self: + for row in row_set: + itr = iter(row) + n = itr.next() + + l_set = itr.next() + for l in l_set: + assert 3 == len(l) # (n.id, m.id, r) tuples + if None == l[1]: + # as link matching is optional, collect may yield empty sets + continue + ret_l_set.append(l) + ret_n_set.append(n) + + return {'node_set': ret_n_set, + 'link_set': ret_l_set } + class DB_Controller: """ neo4j DB controller -- cgit v1.3.1 From 0f36eab1af1978bb0b1bacc5edcf59df253e573e Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 28 Oct 2014 00:33:33 +0200 Subject: initial random neo4j data generator - use to avoid loading DB dumps --- src-py/neo4j_test_util.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'src-py') diff --git a/src-py/neo4j_test_util.py b/src-py/neo4j_test_util.py index 8edac1b2..d5c30a7f 100644 --- a/src-py/neo4j_test_util.py +++ b/src-py/neo4j_test_util.py @@ -8,3 +8,37 @@ def flush_db(db_ctl): complete DB flush: remove all nodes & links """ db_ctl.exec_cypher_query('match (n) optional match (n)-[r]-() delete n,r') + + +def gen_rand_data(db_ctl, lim_n=128, lim_r=256, prob_link_create = 0.3): + """ + generate random DB data + + @return: tuple consisting of the random node,link labels generated + """ + assert 2 <= lim_n + + n_label = rand_label() + r_label = rand_label() + q_arr = ['with 0 as _', # TODO clean: foreach triggers SyntaxException: otherwise + 'foreach (rid in range(0,%d)' % (lim_n - 1), + '|', + 'create (:%s {id:rid, n_attr_0:toInt(%d * rand())}))' % (n_label, lim_n) + ] + + q = ' '.join(q_arr) + op = dbc.DBO_cypher_query(q) + db_ctl.exec_op(op) + + q_arr = ['match (s:%s),(d:%s)' % (n_label, n_label), + 'with s,d', + 'limit %d' % (lim_r - 1), + 'where rand() < %.2f' % (prob_link_create), + 'create (s)-[:%s {l_attr_0:toInt(%d * rand())}]->(d)' % (r_label,lim_r)] + + q = ' '.join(q_arr) + op = dbc.DBO_cypher_query(q) + db_ctl.exec_op(op) + + return (n_label, r_label) + -- cgit v1.3.1 From 3057043640e3e42ba24584a624786dddb73704a1 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 28 Oct 2014 00:34:09 +0200 Subject: test_rz_clone - DBO_rz_clone test --- src-py/test_db_controller.py | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 002777d7..4ff0c4de 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -303,6 +303,17 @@ class TestDBController(unittest.TestCase): id_set = self.db_ctl.exec_op(op) self.assertEqual(len(id_set), 0) + def test_rz_clone(self): + l_n, l_r = gen_rand_data(self.db_ctl, lim_n=8, lim_r=16, prob_link_create=0.7) + op = dbc.DBO_rz_clone(filter_label = l_n, limit=32) + ret = self.db_ctl.exec_op(op) + n_set = ret['node_set'] + l_set = ret['link_set'] + + # TODO improve assertions + self.assertTrue(0 < len(n_set)) + self.assertTrue(0 < len(l_set)) + def tearDown(self): pass if __name__ == "__main__": -- cgit v1.3.1 From 24fbefdcd0b82cd53d7961f7412e4292844af3a2 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 28 Oct 2014 00:34:40 +0200 Subject: random neo4j label generator --- src-py/neo4j_test_util.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src-py') diff --git a/src-py/neo4j_test_util.py b/src-py/neo4j_test_util.py index d5c30a7f..d5aa640a 100644 --- a/src-py/neo4j_test_util.py +++ b/src-py/neo4j_test_util.py @@ -1,8 +1,18 @@ import uuid +import string +from random import choice +import db_controller as dbc def rand_id(): return str(uuid.uuid4()) +def rand_label(length=8): + """ + return random label + """ + char_set = string.ascii_lowercase + string.ascii_uppercase + string.digits + return ''.join([choice(string.ascii_lowercase)] + [choice(char_set) for _ in range(length - 1)]) + def flush_db(db_ctl): """ complete DB flush: remove all nodes & links -- cgit v1.3.1 From 414afa4b510d49e90a4a776bdeb4c9c066a64922 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 28 Oct 2014 00:35:48 +0200 Subject: follow neo4j semantics: rename filter_type to filter_label --- src-py/db_controller.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index a86de1a8..a924a164 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -21,7 +21,6 @@ class DB_op(object): """ tx wrapped DB operation possibly composing multiple DB queries """ - def __init__(self): self.statement_set = [] self.result_set = [] @@ -76,7 +75,6 @@ class DB_op(object): ret.append(col) return ret - class DB_composed_op(DB_op): def __init__(self): super(DB_composed_op, self).__init__() @@ -250,11 +248,11 @@ class DBO_load_node_set_by_DB_id(DB_op): class DBO_match_node_id_set(DB_op): - def __init__(self, filter_type=None, filter_attr_map={}): + def __init__(self, filter_label=None, filter_attr_map={}): """ match a set of nodes by type / attr_map - @param filter_type: node type filter + @param filter_label: node type filter @param filter_attr_map: is a filter_key to filter_value_set map of possible attributes to match against, eg.: { 'id':[0,1], 'color: ['red','blue'] } @@ -262,8 +260,8 @@ class DBO_match_node_id_set(DB_op): """ super(DBO_match_node_id_set, self).__init__() - q = "match (n{filter_type}) {where_clause} return id(n)" - q = cfmt(q, filter_type="" if not filter_type else ":" + filter_type) + q = "match (n{filter_label}) {where_clause} return id(n)" + q = cfmt(q, filter_label="" if not filter_label else ":" + filter_label) q = cfmt(q, where_clause=db_util.gen_clause_where_from_filter_attr_map(filter_attr_map)) q_params = filter_attr_map @@ -291,7 +289,7 @@ class DBO_load_link_set(DB_op): @return: a set of loaded links """ super(DBO_load_link_set, self).__init__() - + for l_ptr in link_ptr_set: if not l_ptr.src_id: q = "match ()-[r]->({id: {dst_id}}) return r" @@ -314,19 +312,19 @@ class DBO_load_link_set(DB_op): return DBO_load_link_set(l_ptr_set) class DBO_match_link_id_set(DB_op): - def __init__(self, filter_type=None, filter_attr_map={}): + def __init__(self, filter_label=None, filter_attr_map={}): """ load an id-set of links - @param filter_type: link type filter + @param filter_label: link type filter @param filter_attr_map: is a filter_key to filter_value_set map of attributes to match link properties against @return: a set of loaded link ids """ super(DBO_match_link_id_set, self).__init__() - q = "match ()-[r{filter_type} {filter_attr}]->() return id(r)" - q = cfmt(q, filter_type="" if not filter_type else ":" + filter_type) + q = "match ()-[r{filter_label} {filter_attr}]->() return id(r)" + q = cfmt(q, filter_label="" if not filter_label else ":" + filter_label) q = cfmt(q, filter_attr=db_util.gen_clause_attr_filter_from_filter_attr_map(filter_attr_map)) q_params = {k: v[0] for (k, v) in filter_attr_map.items()} # pass on only first value from each value set @@ -334,6 +332,9 @@ class DBO_match_link_id_set(DB_op): class DBO_rm_node_set(DB_op): def __init__(self, id_set, rm_links=False): + """ + remove node set + """ super(DBO_rm_node_set, self).__init__() if rm_links: -- cgit v1.3.1 From 223adad23867e19ba4d43e14104d40309c54280a Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 12:07:18 +0200 Subject: unify logging --- src-py/db_controller.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index a924a164..40038ed8 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -211,7 +211,6 @@ class DBO_add_node_set(DB_op): for clo in row: id_set.append(clo) - log.debug('node-set added: ids: ' + str(id_set)) return id_set class DBO_add_link_set(DB_op): @@ -231,7 +230,6 @@ class DBO_add_link_set(DB_op): for col_val in row: id_set.append(col_val) - log.debug('link-set added: ids: ' + str(id_set)) return id_set class DBO_load_node_set_by_DB_id(DB_op): @@ -424,7 +422,10 @@ class DB_Controller: self.db_driver.exec_statement_set(op) self.db_driver.commit_tx(op) - return op.process_result_set() + ret = op.process_result_set() + + log.debug('exec_op:' + op.name + ': return value: ' + str(ret)) + return ret except Exception as e: # here we watch for IOExecptions, etc - not db errors # these are returned in the db response itself -- cgit v1.3.1 From 85feae0a74a97ad7bc04e13b511601e14be5f234 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 12:09:12 +0200 Subject: DBO_rz_clone - reconstruct links from link tuples --- src-py/db_controller.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 40038ed8..273686fd 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -370,7 +370,7 @@ class DBO_rz_clone(DB_op): 'order by n.id', 'skip %d' % (self.skip), 'limit %d' % (self.limit), - 'return n,collect([n.id, m.id, r])'] + 'return n,labels(n),collect([m.id, r, type(r)])'] q = ' '.join(q_arr) self.add_statement(q) @@ -380,17 +380,28 @@ class DBO_rz_clone(DB_op): ret_l_set = [] for _, _, row_set in self: for row in row_set: - itr = iter(row) - n = itr.next() + n, n_lbl_set, l_set = row.items() # see query return statement - l_set = itr.next() - for l in l_set: - assert 3 == len(l) # (n.id, m.id, r) tuples - if None == l[1]: + # reconstruct nodes + assert None != n['id'] + + n['__label_set'] = n_lbl_set + ret_n_set.append(n) + + # reconstruct links from link tuples + for l_tuple in l_set: + assert 3 == len(l_tuple) # see query return statement + + if None == l_tuple[0]: # check if link dst is None # as link matching is optional, collect may yield empty sets continue + + l = l_tuple[1] + l['__src'] = n['id'] + l['__dst'] = l_tuple[0] + l['__label_set'] = [l_tuple[2]] # box single value returned by type() + ret_l_set.append(l) - ret_n_set.append(n) return {'node_set': ret_n_set, 'link_set': ret_l_set } -- cgit v1.3.1 From e5bb0ee5976f2f1348387ed6ed57cc43e34caa7c Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 12:10:57 +0200 Subject: Topo_Diff - support omitted-argument construction --- src-py/model/graph.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/model/graph.py b/src-py/model/graph.py index 8942285d..bc642a69 100644 --- a/src-py/model/graph.py +++ b/src-py/model/graph.py @@ -54,6 +54,15 @@ class Topo_Diff(object): @staticmethod def from_dict(topo_diff_dict): + """ + construct from dict - no node/link constructor set must be provided + """ ret = Topo_Diff() - ret.__dict__ = topo_diff_dict + + # merge keys - this allows obj constructor argument ommision, + # such as when constructing from POST JSON data + for k, _ in ret.__dict__.items(): + v = topo_diff_dict.get(k) + if None != v: + ret.__dict__[k] = v return ret -- cgit v1.3.1 From 48e483a6f7bb11c22b810341088f441706474a06 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 12:12:57 +0200 Subject: misc --- src-py/db_controller.py | 4 ++++ src-py/neo4j_util.py | 19 ++++++++++++++----- src-py/test_db_controller.py | 29 ++++++++++++++++++----------- 3 files changed, 36 insertions(+), 16 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 273686fd..d9d8d737 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -63,6 +63,10 @@ class DB_op(object): def parse_multi_statement_response_data(self, data): pass + @property + def name(self): + return self.__class__.__name__ + def process_result_set(self): """ DB op can issue complex sets of quries all at once - this helper method diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 642e094a..2b6257b4 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -6,14 +6,17 @@ import json import urllib2 import model import string +import time + +from util import debug_log_duration class Neo4JException(Exception): def __init__(self, error_set): self.error_set = error_set - + def __str__(self): return 'neo4j error set: ' + str(self.error_set) - + class DB_row(object): def __init__(self, data): self.data = data @@ -22,6 +25,9 @@ class DB_row(object): for column_val in self.data: yield column_val + def items(self): + return [x for x in self] + class DB_result_set(object): def __init__(self, data): self.data = data @@ -33,6 +39,9 @@ class DB_result_set(object): yield DB_row(db_row_dict['row']) + def items(self): + return [x for x in self] + class Cypher_String_Formatter(string.Formatter): """ Despite parameter support in Cypher, we sometimes do engage in query string building @@ -206,12 +215,12 @@ def meta_attr_list_to_meta_attr_map(e_set, meta_attr='__type'): """ ret = {} for v in e_set: - assert None != v['__type'] # attert type meta-attr is present + assert None != v['__type'] # assert type meta-attr is present v_type = v['__type'] - if None == ret.get(v_type):# init type list if necessary + if None == ret.get(v_type): # init type list if necessary ret[v_type] = [] - + v_no_meta = v.copy() del v_no_meta['__type'] diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py index 4ff0c4de..da8ae364 100644 --- a/src-py/test_db_controller.py +++ b/src-py/test_db_controller.py @@ -5,6 +5,7 @@ import db_controller as dbc from rhizi_server import Config from neo4j_test_util import rand_id from neo4j_test_util import flush_db +from neo4j_test_util import gen_rand_data from neo4j_util import Neo4JException from model.graph import Attr_Diff @@ -35,8 +36,12 @@ class TestDBController(unittest.TestCase): self.log = logging.getLogger('rhizi') self.log.addHandler(logging.StreamHandler()) + # TODO rm when implemented: neo4j_test_util + self.db_ctl.exec_cypher_query('create index on :Person(id)') + self.db_ctl.exec_cypher_query('create index on :Skill(id)') + def setUp(self): - flush_db(self.db_ctl) # remove once embedded DB test mode is supported + flush_db(self.db_ctl) # remove once embedded DB test mode is supported self.db_ctl.exec_op(dbc.DBO_add_node_set(self.n_map)) self.db_ctl.exec_op(dbc.DBO_add_link_set(self.l_map)) @@ -127,6 +132,8 @@ class TestDBController(unittest.TestCase): self.assertEqual(len(id_set), 0) def test_load_link_set(self): + + # load by l_ptr l_ptr = Link.link_ptr(src_id='person_00', dst_id='skill_00') op = dbc.DBO_load_link_set.init_from_link_ptr(l_ptr) l_set = self.db_ctl.exec_op(op) @@ -164,7 +171,7 @@ class TestDBController(unittest.TestCase): op = dbc.DBO_add_node_set({'T_test_load_node_set_by_DB_id': [{'name': 'John Doe'}, {'name': 'John Doe'}]}) id_set = self.db_ctl.exec_op(op) - + # match against DB ids op = dbc.DBO_load_node_set_by_DB_id(id_set) n_set = self.db_ctl.exec_op(op) @@ -219,16 +226,16 @@ class TestDBController(unittest.TestCase): id_set = self.db_ctl.exec_op(dbc.DBO_match_node_set_by_id_attribute([n_0_id, n_1_id])) self.assertEqual(len(id_set), 2) - + l_ptr = Link.link_ptr(src_id=n_0_id, dst_id=n_1_id) id_set = self.db_ctl.exec_op(dbc.DBO_load_link_set.init_from_link_ptr(l_ptr)) self.assertEqual(len(id_set), 1) - + l_ptr = Link.link_ptr(src_id=n_1_id, dst_id=n_0_id) id_set = self.db_ctl.exec_op(dbc.DBO_load_link_set.init_from_link_ptr(l_ptr)) self.assertEqual(len(id_set), 1) - id_set_rm=[n_2_id] + id_set_rm = [n_2_id] topo_diff = Topo_Diff(node_set_rm=id_set_rm) op = dbc.DBO_topo_diff_commit(topo_diff) self.db_ctl.exec_op(op) @@ -278,7 +285,7 @@ class TestDBController(unittest.TestCase): n_2_id = rand_id() n_3_id = rand_id() n_T = 'T_test_rm_node_set' - + n_set = [{'__type': n_T, 'id': n_0_id }, {'__type': n_T, 'id': n_1_id }, {'__type': n_T, 'id': n_2_id }, @@ -291,13 +298,13 @@ class TestDBController(unittest.TestCase): op = dbc.DBO_topo_diff_commit(topo_diff) self.db_ctl.exec_op(op) - + op = dbc.DBO_rm_node_set([n_0_id, n_1_id]) self.db_ctl.exec_op(op) - + op = dbc.DBO_rm_node_set([n_2_id, n_3_id], rm_links=True) self.db_ctl.exec_op(op) - + # assert all deleted op = dbc.DBO_match_node_id_set(filter_type=n_T) id_set = self.db_ctl.exec_op(op) @@ -305,11 +312,11 @@ class TestDBController(unittest.TestCase): def test_rz_clone(self): l_n, l_r = gen_rand_data(self.db_ctl, lim_n=8, lim_r=16, prob_link_create=0.7) - op = dbc.DBO_rz_clone(filter_label = l_n, limit=32) + op = dbc.DBO_rz_clone(filter_label=l_n, limit=32) ret = self.db_ctl.exec_op(op) n_set = ret['node_set'] l_set = ret['link_set'] - + # TODO improve assertions self.assertTrue(0 < len(n_set)) self.assertTrue(0 < len(l_set)) -- cgit v1.3.1 From d5dd1d471b0c16caf395e32996cb7caa25fce326 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 12:13:44 +0200 Subject: rename --- src-py/rhizi_server.py | 59 ++++++++++++++++++++++++++++++++++++++++++ src-py/rhizi_webapp.py | 70 -------------------------------------------------- 2 files changed, 59 insertions(+), 70 deletions(-) create mode 100644 src-py/rhizi_server.py delete mode 100644 src-py/rhizi_webapp.py (limited to 'src-py') diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py new file mode 100644 index 00000000..5393a61e --- /dev/null +++ b/src-py/rhizi_server.py @@ -0,0 +1,59 @@ +import logging +import json +import util +import neo4j_util + +import db_controller as dbc +import rhizi_api + +class Config(object): + """ + rhizi-server configuration + """ + + @staticmethod + def init_from_file(file_path): + ret = Config() + + with open(file_path, 'r') as f: + cfg = json.loads(f.read()) + + # + # TODO: config option documentation + # + ret.neo4j_url = cfg['neo4j_url'] + ret.listen_address = cfg['listen_address'] + ret.listen_port = cfg['listen_port'] + + return ret + + @property + def db_base_url(self): + return self.neo4j_url + + @property + def tx_api_path(self): + return '/db/data/transaction' + +def init_logging(): + + log = logging.getLogger('rhizi') + log.setLevel(logging.DEBUG) + log_handler_c = logging.StreamHandler() + log_handler_f = logging.FileHandler('/tmp/rhizi-server.log') + + log.addHandler(log_handler_c) + log.addHandler(log_handler_f) + +class RhiziServer(object): + pass + +if __name__ == "__main__": + + init_logging() + + cfg = Config.init_from_file('res/etc/rhizi-server.conf') + db_ctl = dbc.DB_Controller(cfg) + + rhizi_api.db_ctl = db_ctl + rhizi_api.webapp.run(host=cfg.listen_address, port=cfg.listen_port) diff --git a/src-py/rhizi_webapp.py b/src-py/rhizi_webapp.py deleted file mode 100644 index b63407d6..00000000 --- a/src-py/rhizi_webapp.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -Rhizi webapp -""" -import os -import json -import logging - -from flask import Flask, request, url_for -import db_controller as dbc - -cwd = os.getcwd() -app = Flask(__name__) -app.debug = True - -class Config: - """ - rhizi-server configuration - """ - - @staticmethod - def init_from_file(file_path): - ret = Config() - - with open(file_path, 'r') as f: - cfg = json.loads(f.read()) - ret.db_base_url = cfg['neo4j_url'] - - return ret - - @property - def db_base_url(self): - return self.neo4j_url - - @property - def tx_api_path(self): - return '/db/data/transaction' - -@app.route("/add-node-set") -def foo(): - pass - -def init_logging(): - global log - - log = logging.getLogger('rhizi') - log.setLevel(logging.DEBUG) - log_handler_c = logging.StreamHandler() - log_handler_f = logging.FileHandler('/tmp/rhizi-backend.log') - - log.addHandler(log_handler_c) - log.addHandler(log_handler_f) - -def test_DB_controller_api(): - db_ctl = dbc.DB_Controller(cfg) - - n_map = { 'Skill': [{'name': 'kung fu' }, - {'name': 'judo' } - ], - 'Person': [{'name': 'Bob' }, {'name': 'Alice' }] - } - - db_ctl.exec_op(dbc.DBO_add_node_set(n_map)) - id_set = db_ctl.exec_op(dbc.DBO_load_node_id_set(filter_type='Skill')) - -if __name__ == "__main__": - cfg = Config.init_from_file('res/etc/rhizi-backend.conf') - init_logging() - - test_DB_controller_api() - # app.run(host='127.0.0.1', port=rhizi_backend_cfg['port'], ssl_context=ctx) -- cgit v1.3.1 From 8e6eeb0db8616b79211f0c03472074bc36885658 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 12:18:05 +0200 Subject: common request handling --- src-py/rhizi_api.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 7a6a44e9..53edf8f5 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -59,7 +59,9 @@ def __common_exec(op, on_success=__common_resp_handle): op_ret = db_ctl.exec_op(op) return on_success(op_ret) except Exception as e: - return __common_resp_handle('exception raised: add_node_set') + log.error(e.message) + log.error(traceback.print_exc()) + return __common_resp_handle('error occurred') @webapp.route("/load/node-set-by-id", methods=['POST']) def load_node_set_by_id_attr(): @@ -110,12 +112,7 @@ def load_link_set_by_link_ptr_set(): l_ptr_set = deserialize_param_set(request.get_json()) op = dbc.DBO_load_link_set.init_from_link_ptr_set(l_ptr_set) - try: - l_set = db_ctl.exec_op(op) - return __common_resp_handle(data=l_set) - except Exception as e: - log.exception(e) - return __common_resp_handle(error='unable to load link set') + return __common_exec(op) @webapp.route("/graph/topo-diff-commit", methods=['POST']) def topo_diff_commit(): @@ -150,9 +147,5 @@ def add_node_set(): __sanitize_input(node_map) op = dbc.DBO_add_node_set(node_map) - try: - n_set = db_ctl.exec_op(op) - return __common_resp_handle(n_set) - except Exception as e: - return __common_resp_handle('exception raised: add_node_set') + return __common_exec(op) -- cgit v1.3.1 From 2f460418e342c378e575a5693b38ae4b3c8ff254 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 12:18:47 +0200 Subject: match_node_set_by_attr_filter_map --- src-py/rhizi_api.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 53edf8f5..b88a1ff2 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -93,6 +93,16 @@ def __load_node_set_by_id_attr_common(id_set): log.exception(e) return __common_resp_handle(error='unable to load node with ids: {0}'.format(id_set)) +@webapp.route("/match/node-set", methods=['POST']) +def match_node_set_by_attr_filter_map(attr_filter_map): + """ + @param attr_filter_map + + @return: a set of node DB id's + """ + op = dbc.DBO_match_node_id_set(attr_filter_map) + return __common_exec(op) + @webapp.route("/load/link-set/by_link_ptr_set", methods=['POST']) def load_link_set_by_link_ptr_set(): -- cgit v1.3.1 From 71c9e8dfd4c58942bcfa8f1cdd89d769ca769660 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 12:19:29 +0200 Subject: /graph/clone REST path --- src-py/rhizi_api.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index b88a1ff2..21c7d2a6 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -124,8 +124,11 @@ def load_link_set_by_link_ptr_set(): op = dbc.DBO_load_link_set.init_from_link_ptr_set(l_ptr_set) return __common_exec(op) -@webapp.route("/graph/topo-diff-commit", methods=['POST']) -def topo_diff_commit(): +@webapp.route("/graph/clone", methods=['POST']) +def rz_clone(): + op = dbc.DBO_rz_clone() + return __common_exec(op) + """ commit a graph topology diff """ -- cgit v1.3.1 From 2aff3e7698d08194c05c658c11158943395911d7 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 12:20:15 +0200 Subject: /graph/diff-commit-topo REST path --- src-py/rhizi_api.py | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 21c7d2a6..36463531 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -137,6 +137,17 @@ def rz_clone(): topo_diff = Topo_Diff.from_dict(topo_diff_dict) +@webapp.route("/graph/diff-commit-topo", methods=['POST']) +def diff_commit_topo(): + """ + commit a graph topology diff + """ + def sanitize_input(req): + topo_diff_dict = request.get_json()['topo_diff'] + topo_diff = Topo_Diff.from_dict(topo_diff_dict) + return topo_diff; + + topo_diff = sanitize_input(request) op = dbc.DBO_topo_diff_commit(topo_diff) return __common_exec(op) -- cgit v1.3.1 From 30677e0342e796e7699bd2958aced711659e5ecc Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 12:20:43 +0200 Subject: /graph/diff-commit-attr REST path --- src-py/rhizi_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 36463531..461f3e49 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -151,8 +151,8 @@ def diff_commit_topo(): op = dbc.DBO_topo_diff_commit(topo_diff) return __common_exec(op) -@webapp.route("/graph/attr-diff-commit", methods=['POST']) -def attr_diff_commit(): +@webapp.route("/graph/diff-commit-attr", methods=['POST']) +def diff_commit_attr(): """ commit a graph attribute diff """ -- cgit v1.3.1 From 92fdd5d1d8eb09eae6122ab803a24bf9cdc7dd10 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 12:21:03 +0200 Subject: /graph/diff-commit-set REST path --- src-py/rhizi_api.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 461f3e49..cbaa82ec 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -129,13 +129,20 @@ def rz_clone(): op = dbc.DBO_rz_clone() return __common_exec(op) +@webapp.route("/graph/diff-commit-set", methods=['POST']) +def diff_commit_set(): """ - commit a graph topology diff + commit a diff set """ - topo_diff_dict = request.get_json()['topo_diff'] - __sanitize_input(topo_diff_dict) + def sanitize_input(req): + diff_set_dict = request.get_json()['diff_set'] + topo_diff_dict = diff_set_dict['__diff_set_topo'][0] + topo_diff = Topo_Diff.from_dict(topo_diff_dict) + return topo_diff; - topo_diff = Topo_Diff.from_dict(topo_diff_dict) + topo_diff = sanitize_input(request) + op = dbc.DBO_topo_diff_commit(topo_diff) + return __common_exec(op) @webapp.route("/graph/diff-commit-topo", methods=['POST']) def diff_commit_topo(): -- cgit v1.3.1 From 500d95dc5d6fde2a3f583538492f346c245a641e Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 12:21:34 +0200 Subject: /graph/diff-commit-vis REST path stub --- src-py/rhizi_api.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index cbaa82ec..e624b7ba 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -169,6 +169,10 @@ def diff_commit_attr(): op = dbc.DBO_attr_diff_commit(attr_diff) return __common_exec(op) +@webapp.route("/graph/diff-commit-vis", methods=['POST']) +def diff_commit_vis(): + pass + @webapp.route("/add/node-set", methods=['POST']) def add_node_set(): """ -- cgit v1.3.1 From 0f6443adf711b9f540b83e74f8a3d0767f01a38e Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 12:22:03 +0200 Subject: mv to __src,__dst meta attr use --- src-py/rhizi_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index e624b7ba..8c9df6e2 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -113,8 +113,8 @@ def load_link_set_by_link_ptr_set(): l_ptr_set = [] for lptr_dict in l_ptr_set_raw: - src_id = lptr_dict.get('src_id') - dst_id = lptr_dict.get('dst_id') + src_id = lptr_dict.get('__src') + dst_id = lptr_dict.get('__dst') l_ptr_set += [Link.Link_Ptr(src_id=src_id, dst_id=dst_id) ] return l_ptr_set -- cgit v1.3.1 From bfedd0aaba765d86053225fb4221c2ae80e451c0 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 12:22:38 +0200 Subject: deprecate /add/node-set --- src-py/rhizi_api.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 8c9df6e2..3e96d860 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -176,6 +176,8 @@ def diff_commit_vis(): @webapp.route("/add/node-set", methods=['POST']) def add_node_set(): """ + @deprecated: use topo_attr_commit + @param node_map: node type to node map, eg. { 'Skill': { 'name': 'kung-fu' } } """ node_map = request.get_json()['node_map'] -- cgit v1.3.1 From a4c02f07573640e2c1b88cb7315293f3df2c2c07 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 12:24:57 +0200 Subject: mv tests to src-py_test/ --- src-py/test_db_controller.py | 327 -------------------------------------- src-py/test_rhizi_api.py | 84 ---------- src-py_test/test_db_controller.py | 327 ++++++++++++++++++++++++++++++++++++++ src-py_test/test_rhizi_api.py | 84 ++++++++++ 4 files changed, 411 insertions(+), 411 deletions(-) delete mode 100644 src-py/test_db_controller.py delete mode 100644 src-py/test_rhizi_api.py create mode 100644 src-py_test/test_db_controller.py create mode 100644 src-py_test/test_rhizi_api.py (limited to 'src-py') diff --git a/src-py/test_db_controller.py b/src-py/test_db_controller.py deleted file mode 100644 index da8ae364..00000000 --- a/src-py/test_db_controller.py +++ /dev/null @@ -1,327 +0,0 @@ -import unittest -import logging -import db_controller as dbc - -from rhizi_server import Config -from neo4j_test_util import rand_id -from neo4j_test_util import flush_db -from neo4j_test_util import gen_rand_data -from neo4j_util import Neo4JException - -from model.graph import Attr_Diff -from model.graph import Topo_Diff -from model.model import Link - -class TestDBController(unittest.TestCase): - - db_ctl = None - log = None - - n_map = { 'Skill': [{'name': 'Kung Fu', 'id': 'skill_00' }, - {'name': 'Judo', 'id': 'skill_01' } - ], - - 'Person': [{'name': 'Bob', 'id': 'person_00', 'age': 128 }, - {'name': 'Alice', 'id': 'person_01', 'age': 256 } - ] - } - - l_map = { 'Knows' : [{'__src': 'person_00', '__dst': 'skill_00'}, - {'__src': 'person_00', '__dst': 'skill_01'}] } - - @classmethod - def setUpClass(self): - cfg = Config.init_from_file('res/etc/rhizi-server.conf') - self.db_ctl = dbc.DB_Controller(cfg) - self.log = logging.getLogger('rhizi') - self.log.addHandler(logging.StreamHandler()) - - # TODO rm when implemented: neo4j_test_util - self.db_ctl.exec_cypher_query('create index on :Person(id)') - self.db_ctl.exec_cypher_query('create index on :Skill(id)') - - def setUp(self): - flush_db(self.db_ctl) # remove once embedded DB test mode is supported - self.db_ctl.exec_op(dbc.DBO_add_node_set(self.n_map)) - self.db_ctl.exec_op(dbc.DBO_add_link_set(self.l_map)) - - def test_db_op_statement_iteration(self): - s_arr = ['create (b:Book {title: \'foo\'}) return b', - 'match (n) return n', ] - - op = dbc.DB_op() - op.add_statement(s_arr[0]) - op.add_statement(s_arr[1]) - - i = 0 - for _, s, r in op: - # access: second tuple item -> REST-form 'statement' key - self.assertEqual(s_arr[i], s['statement']) - self.assertEqual(None, r) - i = i + 1 - - self.db_ctl.exec_op(op) - - i = 0 - for _, s, r_set in op: - # access: second tuple item -> REST-form 'statement' key - self.assertNotEqual(None, r_set) - for x in r_set: - pass - i = i + 1 - - def test_add_node_set(self): - n_map = { 'T_test_add_node_set': [{'id': rand_id()}, {'id': rand_id()}] } - op = dbc.DBO_add_node_set(n_map) - - self.assertEqual(len(op.statement_set), 1) # assert a single statement is issued - - id_set = self.db_ctl.exec_op(op) - self.assertEqual(len(id_set), 2) - - def test_add_link_set(self): - src_id = rand_id() - dst_id_0 = rand_id() - dst_id_1 = rand_id() - n_map = { 'T_test_add_node_set': [{'id': src_id }, - {'id': dst_id_0 }, - {'id': dst_id_1 }] } - self.db_ctl.exec_op(dbc.DBO_add_node_set(n_map)) - - l_map = { 'T_test_add_link_set' : [{'__src': src_id, '__dst': dst_id_0}, - {'__src': src_id, '__dst': dst_id_1}] } - - op = dbc.DBO_add_link_set(l_map) - self.assertEqual(len(op.statement_set), 2) # no support yet for parameterized statements for link creation - - l_set = self.db_ctl.exec_op(op) - self.assertEqual(len(l_set), 2) - - def test_match_node_set_by_type(self): - op = dbc.DBO_match_node_id_set(filter_type='Person') - id_set = self.db_ctl.exec_op(op) - self.assertEqual(len(id_set), 2) - - op = dbc.DBO_match_node_id_set(filter_type='Nan_Type') - id_set = self.db_ctl.exec_op(op) - self.assertEqual(len(id_set), 0) - - def test_match_node_set_by_attribute(self): - fam = { 'name': ['Bob', u'Judo'], 'age': [128] } - n_set = self.db_ctl.exec_op(dbc.DBO_match_node_id_set(filter_attr_map=fam)) - self.assertEqual(len(n_set), 1) - - fam = { 'age': [128, 256, 404] } - n_set = self.db_ctl.exec_op(dbc.DBO_match_node_id_set(filter_attr_map=fam)) - self.assertEqual(len(n_set), 2) - - def test_match_node_set_by_DB_id(self): - pass # TODO - - def test_match_node_set_by_id_attribute(self): - n_set = self.db_ctl.exec_op(dbc.DBO_match_node_set_by_id_attribute(['skill_00', 'person_01'])) - self.assertEqual(len(n_set), 2) - - def test_match_link_set_by_type(self): - op = dbc.DBO_match_link_id_set(filter_type='Knows') - id_set = self.db_ctl.exec_op(op) - self.assertEqual(len(id_set), 2) - - op = dbc.DBO_match_link_id_set(filter_type='Nan_Type') - id_set = self.db_ctl.exec_op(op) - self.assertEqual(len(id_set), 0) - - def test_load_link_set(self): - - # load by l_ptr - l_ptr = Link.link_ptr(src_id='person_00', dst_id='skill_00') - op = dbc.DBO_load_link_set.init_from_link_ptr(l_ptr) - l_set = self.db_ctl.exec_op(op) - self.assertEqual(len(l_set), 1) - - l_ptr = Link.link_ptr(src_id='person_00') - op = dbc.DBO_load_link_set.init_from_link_ptr(l_ptr) - l_set = self.db_ctl.exec_op(op) - self.assertEqual(len(l_set), 2) - - l_ptr = Link.link_ptr(dst_id='skill_00') - op = dbc.DBO_load_link_set.init_from_link_ptr(l_ptr) - l_set = self.db_ctl.exec_op(op) - self.assertEqual(len(l_set), 1) - - # load by l_ptr sets - l_ptr_set = [Link.link_ptr(s, d) for (s, d) in [('person_00', 'skill_00'), ('person_00', 'skill_01')]] - op = dbc.DBO_load_link_set.init_from_link_ptr_set(l_ptr_set) - l_set = self.db_ctl.exec_op(op) - self.assertEqual(len(l_set), 2) - - # this should return the same link twice - l_ptr_set = [Link.link_ptr(s, d) for (s, d) in [('person_00', 'skill_00'), ('person_00', 'skill_01')]] - l_ptr_set.append(Link.link_ptr(dst_id='skill_00')) - op = dbc.DBO_load_link_set.init_from_link_ptr_set(l_ptr_set) - l_set = self.db_ctl.exec_op(op) - self.assertEqual(len(l_set), 3) - - def test_load_node_set_by_DB_id(self): - """ - test node DB id life cycle - """ - - # create nodes, get DB ids - op = dbc.DBO_add_node_set({'T_test_load_node_set_by_DB_id': [{'name': 'John Doe'}, - {'name': 'John Doe'}]}) - id_set = self.db_ctl.exec_op(op) - - # match against DB ids - op = dbc.DBO_load_node_set_by_DB_id(id_set) - n_set = self.db_ctl.exec_op(op) - self.assertEqual(len(n_set), len(id_set), 'incorrect result size') - - def test_partial_query_set_execution_success(self): - """ - test: - - statement execution stops at first invalid statement - - assert create statement with result data does not actually persist in DB - - From the REST API doc: 'If any errors occur while executing statements, - the server will roll back the transaction.' - """ - n_id = 'test_partial_query_set_execution_success' - - op = dbc.DB_op() - op.add_statement("create (n:Person {id: '%s'}) return n" % (n_id), {}) # valid statement - op.add_statement("match (n) return n", {}) # valid statement - op.add_statement("non-valid statement #1", {}) - op.add_statement("non-valid statement #2", {}) - - self.assertRaises(Neo4JException, self.db_ctl.exec_op, op) - - self.assertEqual(len(op.result_set), 2) - self.assertEqual(len(op.error_set), 1) - - # assert node creation did not persist - n_set = self.db_ctl.exec_op(dbc.DBO_match_node_set_by_id_attribute([n_id])) - self.assertEqual(len(n_set), 0) - - def test_topo_diff_commit(self): - n_0_id = rand_id() - n_1_id = rand_id() - n_2_id = rand_id() - n_T = 'T_test_topo_diff_commit' - - n_set = [{'__type': n_T, 'id': n_0_id }, - {'__type': n_T, 'id': n_1_id }, - {'__type': n_T, 'id': n_2_id }] - l_set = [{'__type': n_T, '__src': n_0_id, '__dst': n_1_id}, - {'__type': n_T, '__src': n_1_id, '__dst': n_0_id}] - - topo_diff = Topo_Diff(node_set_add=n_set, - link_set_add=l_set) - - op = dbc.DBO_topo_diff_commit(topo_diff) - op_ret = self.db_ctl.exec_op(op) - self.assertEqual(len(op_ret), 2) # to id-sets, nodes & links - self.assertEqual(len(op_ret[0]), 3) # expect id-set of length 3 - self.assertEqual(len(op_ret[1]), 2) # expect id-set of length 2 - - id_set = self.db_ctl.exec_op(dbc.DBO_match_node_set_by_id_attribute([n_0_id, n_1_id])) - self.assertEqual(len(id_set), 2) - - l_ptr = Link.link_ptr(src_id=n_0_id, dst_id=n_1_id) - id_set = self.db_ctl.exec_op(dbc.DBO_load_link_set.init_from_link_ptr(l_ptr)) - self.assertEqual(len(id_set), 1) - - l_ptr = Link.link_ptr(src_id=n_1_id, dst_id=n_0_id) - id_set = self.db_ctl.exec_op(dbc.DBO_load_link_set.init_from_link_ptr(l_ptr)) - self.assertEqual(len(id_set), 1) - - id_set_rm = [n_2_id] - topo_diff = Topo_Diff(node_set_rm=id_set_rm) - op = dbc.DBO_topo_diff_commit(topo_diff) - self.db_ctl.exec_op(op) - op = dbc.DBO_match_node_set_by_id_attribute(id_set_rm) - id_set = self.db_ctl.exec_op(op) - self.assertEqual(len(id_set), 0) - - def test_attr_diff_commit(self): - # create test node - n_id = rand_id() - topo_diff = Topo_Diff(node_set_add=[{'__type': 'T_test_attr_diff_commit', 'id': n_id, 'attr_rm': 0}]) - op = dbc.DBO_topo_diff_commit(topo_diff) - self.db_ctl.exec_op(op) - - # apply attr_diff - attr_diff = Attr_Diff() - attr_diff.add_node_attr_write(n_id, 'attr_0', 0) - attr_diff.add_node_attr_write(n_id, 'attr_1', 'a') - attr_diff.add_node_attr_rm(n_id, 'attr_rm') - - op = dbc.DBO_attr_diff_commit(attr_diff) - n_map = self.db_ctl.exec_op(op) - self.assertEqual(len(n_map), 1) - n = n_map.get(n_id) - self.assertTrue(None != n) - self.assertTrue(None == n.get('attr_rm')) - self.assertEqual(0, n.get('attr_0')) - self.assertEqual('a', n.get('attr_1')) - - # attr-set only - attr_diff = Attr_Diff() - attr_diff.add_node_attr_write(n_id, 'attr_2', 0) - - op = dbc.DBO_attr_diff_commit(attr_diff) - n_map = self.db_ctl.exec_op(op) - - # attr-remove only - attr_diff = Attr_Diff() - attr_diff.add_node_attr_rm(n_id, 'attr_2') - - op = dbc.DBO_attr_diff_commit(attr_diff) - n_map = self.db_ctl.exec_op(op) - - def test_rm_node_set(self): - n_0_id = rand_id() - n_1_id = rand_id() - n_2_id = rand_id() - n_3_id = rand_id() - n_T = 'T_test_rm_node_set' - - n_set = [{'__type': n_T, 'id': n_0_id }, - {'__type': n_T, 'id': n_1_id }, - {'__type': n_T, 'id': n_2_id }, - {'__type': n_T, 'id': n_3_id }] - l_set = [{'__type': n_T, '__src': n_2_id, '__dst': n_2_id}, - {'__type': n_T, '__src': n_2_id, '__dst': n_3_id}] - - topo_diff = Topo_Diff(node_set_add=n_set, - link_set_add=l_set) - - op = dbc.DBO_topo_diff_commit(topo_diff) - self.db_ctl.exec_op(op) - - op = dbc.DBO_rm_node_set([n_0_id, n_1_id]) - self.db_ctl.exec_op(op) - - op = dbc.DBO_rm_node_set([n_2_id, n_3_id], rm_links=True) - self.db_ctl.exec_op(op) - - # assert all deleted - op = dbc.DBO_match_node_id_set(filter_type=n_T) - id_set = self.db_ctl.exec_op(op) - self.assertEqual(len(id_set), 0) - - def test_rz_clone(self): - l_n, l_r = gen_rand_data(self.db_ctl, lim_n=8, lim_r=16, prob_link_create=0.7) - op = dbc.DBO_rz_clone(filter_label=l_n, limit=32) - ret = self.db_ctl.exec_op(op) - n_set = ret['node_set'] - l_set = ret['link_set'] - - # TODO improve assertions - self.assertTrue(0 < len(n_set)) - self.assertTrue(0 < len(l_set)) - - def tearDown(self): pass - -if __name__ == "__main__": - unittest.main() diff --git a/src-py/test_rhizi_api.py b/src-py/test_rhizi_api.py deleted file mode 100644 index c404edba..00000000 --- a/src-py/test_rhizi_api.py +++ /dev/null @@ -1,84 +0,0 @@ -import unittest -import db_controller as dbc -import rhizi_api -import json -import logging - -from rhizi_server import Config -from werkzeug.test import EnvironBuilder -from werkzeug.test import Client - -from db_controller import DB_Driver_Embedded - -class TestRhiziAPI(unittest.TestCase): - - def setUp(self): - self.flush_db() - - @classmethod - def setUpClass(self): - cfg = Config.init_from_file('res/etc/rhizi-server.conf') - self.db_ctl = dbc.DB_Controller(cfg) - rhizi_api.db_ctl = self.db_ctl - - # TODO extract to superclass - log = logging.getLogger('rhizi') - log.setLevel(logging.DEBUG) - log_handler_c = logging.StreamHandler() - log.addHandler(log_handler_c) - - def flush_db(self): - """ - complete DB flush: remove all nodes & links - """ - self.db_ctl.exec_cypher_query('match (n) optional match (n)-[r]-() delete n,r') - - def test_add_node_set(self): - """ - add node set test - """ - node_map = { 'Skill': [{ 'name': 'kung-fu' }, { 'name': 'judo' }] } - with rhizi_api.webapp.test_client() as c: - req = c.post('/add/node-set', - content_type='application/json', - data=json.dumps(dict(node_map=node_map))) - id_set = json.loads(req.data)['data'] - self.assertEqual(2, len(id_set)) - self.assertTrue(isinstance(id_set[0], int)) - - def test_load_node_non_existing(self): - """ - loading a non existing node test - """ - id_set = ['non_existing_id'] - with rhizi_api.webapp.test_client() as c: - req = c.post('/load/node-set-by-id', - content_type='application/json', - data=json.dumps({ 'id_set': id_set})) - req_data = json.loads(req.data) - rz_data = req_data['data'] - rz_err = req_data['error'] - self.assertEqual(None, rz_err) - self.assertEqual(0, len(rz_data)) - - def test_load_node_set_by_id_existing(self): - """ - loading an existing node test - """ - id_set = ['skill_00'] - self.db_ctl.exec_cypher_query('create (s:Skill {id: \'skill_00\'} )') - - with rhizi_api.webapp.test_client() as c: - req = c.post('/load/node-set-by-id', - content_type='application/json', - data=json.dumps({ 'id_set': id_set})) - n_set = json.loads(req.data)['data'] - - self.assertEqual(1, len(n_set)) - self.assertEqual(n_set[0]['id'], id_set[0]) - - def test_load_node_set(self): - pass - -if __name__ == "__main__": - unittest.main() diff --git a/src-py_test/test_db_controller.py b/src-py_test/test_db_controller.py new file mode 100644 index 00000000..da8ae364 --- /dev/null +++ b/src-py_test/test_db_controller.py @@ -0,0 +1,327 @@ +import unittest +import logging +import db_controller as dbc + +from rhizi_server import Config +from neo4j_test_util import rand_id +from neo4j_test_util import flush_db +from neo4j_test_util import gen_rand_data +from neo4j_util import Neo4JException + +from model.graph import Attr_Diff +from model.graph import Topo_Diff +from model.model import Link + +class TestDBController(unittest.TestCase): + + db_ctl = None + log = None + + n_map = { 'Skill': [{'name': 'Kung Fu', 'id': 'skill_00' }, + {'name': 'Judo', 'id': 'skill_01' } + ], + + 'Person': [{'name': 'Bob', 'id': 'person_00', 'age': 128 }, + {'name': 'Alice', 'id': 'person_01', 'age': 256 } + ] + } + + l_map = { 'Knows' : [{'__src': 'person_00', '__dst': 'skill_00'}, + {'__src': 'person_00', '__dst': 'skill_01'}] } + + @classmethod + def setUpClass(self): + cfg = Config.init_from_file('res/etc/rhizi-server.conf') + self.db_ctl = dbc.DB_Controller(cfg) + self.log = logging.getLogger('rhizi') + self.log.addHandler(logging.StreamHandler()) + + # TODO rm when implemented: neo4j_test_util + self.db_ctl.exec_cypher_query('create index on :Person(id)') + self.db_ctl.exec_cypher_query('create index on :Skill(id)') + + def setUp(self): + flush_db(self.db_ctl) # remove once embedded DB test mode is supported + self.db_ctl.exec_op(dbc.DBO_add_node_set(self.n_map)) + self.db_ctl.exec_op(dbc.DBO_add_link_set(self.l_map)) + + def test_db_op_statement_iteration(self): + s_arr = ['create (b:Book {title: \'foo\'}) return b', + 'match (n) return n', ] + + op = dbc.DB_op() + op.add_statement(s_arr[0]) + op.add_statement(s_arr[1]) + + i = 0 + for _, s, r in op: + # access: second tuple item -> REST-form 'statement' key + self.assertEqual(s_arr[i], s['statement']) + self.assertEqual(None, r) + i = i + 1 + + self.db_ctl.exec_op(op) + + i = 0 + for _, s, r_set in op: + # access: second tuple item -> REST-form 'statement' key + self.assertNotEqual(None, r_set) + for x in r_set: + pass + i = i + 1 + + def test_add_node_set(self): + n_map = { 'T_test_add_node_set': [{'id': rand_id()}, {'id': rand_id()}] } + op = dbc.DBO_add_node_set(n_map) + + self.assertEqual(len(op.statement_set), 1) # assert a single statement is issued + + id_set = self.db_ctl.exec_op(op) + self.assertEqual(len(id_set), 2) + + def test_add_link_set(self): + src_id = rand_id() + dst_id_0 = rand_id() + dst_id_1 = rand_id() + n_map = { 'T_test_add_node_set': [{'id': src_id }, + {'id': dst_id_0 }, + {'id': dst_id_1 }] } + self.db_ctl.exec_op(dbc.DBO_add_node_set(n_map)) + + l_map = { 'T_test_add_link_set' : [{'__src': src_id, '__dst': dst_id_0}, + {'__src': src_id, '__dst': dst_id_1}] } + + op = dbc.DBO_add_link_set(l_map) + self.assertEqual(len(op.statement_set), 2) # no support yet for parameterized statements for link creation + + l_set = self.db_ctl.exec_op(op) + self.assertEqual(len(l_set), 2) + + def test_match_node_set_by_type(self): + op = dbc.DBO_match_node_id_set(filter_type='Person') + id_set = self.db_ctl.exec_op(op) + self.assertEqual(len(id_set), 2) + + op = dbc.DBO_match_node_id_set(filter_type='Nan_Type') + id_set = self.db_ctl.exec_op(op) + self.assertEqual(len(id_set), 0) + + def test_match_node_set_by_attribute(self): + fam = { 'name': ['Bob', u'Judo'], 'age': [128] } + n_set = self.db_ctl.exec_op(dbc.DBO_match_node_id_set(filter_attr_map=fam)) + self.assertEqual(len(n_set), 1) + + fam = { 'age': [128, 256, 404] } + n_set = self.db_ctl.exec_op(dbc.DBO_match_node_id_set(filter_attr_map=fam)) + self.assertEqual(len(n_set), 2) + + def test_match_node_set_by_DB_id(self): + pass # TODO + + def test_match_node_set_by_id_attribute(self): + n_set = self.db_ctl.exec_op(dbc.DBO_match_node_set_by_id_attribute(['skill_00', 'person_01'])) + self.assertEqual(len(n_set), 2) + + def test_match_link_set_by_type(self): + op = dbc.DBO_match_link_id_set(filter_type='Knows') + id_set = self.db_ctl.exec_op(op) + self.assertEqual(len(id_set), 2) + + op = dbc.DBO_match_link_id_set(filter_type='Nan_Type') + id_set = self.db_ctl.exec_op(op) + self.assertEqual(len(id_set), 0) + + def test_load_link_set(self): + + # load by l_ptr + l_ptr = Link.link_ptr(src_id='person_00', dst_id='skill_00') + op = dbc.DBO_load_link_set.init_from_link_ptr(l_ptr) + l_set = self.db_ctl.exec_op(op) + self.assertEqual(len(l_set), 1) + + l_ptr = Link.link_ptr(src_id='person_00') + op = dbc.DBO_load_link_set.init_from_link_ptr(l_ptr) + l_set = self.db_ctl.exec_op(op) + self.assertEqual(len(l_set), 2) + + l_ptr = Link.link_ptr(dst_id='skill_00') + op = dbc.DBO_load_link_set.init_from_link_ptr(l_ptr) + l_set = self.db_ctl.exec_op(op) + self.assertEqual(len(l_set), 1) + + # load by l_ptr sets + l_ptr_set = [Link.link_ptr(s, d) for (s, d) in [('person_00', 'skill_00'), ('person_00', 'skill_01')]] + op = dbc.DBO_load_link_set.init_from_link_ptr_set(l_ptr_set) + l_set = self.db_ctl.exec_op(op) + self.assertEqual(len(l_set), 2) + + # this should return the same link twice + l_ptr_set = [Link.link_ptr(s, d) for (s, d) in [('person_00', 'skill_00'), ('person_00', 'skill_01')]] + l_ptr_set.append(Link.link_ptr(dst_id='skill_00')) + op = dbc.DBO_load_link_set.init_from_link_ptr_set(l_ptr_set) + l_set = self.db_ctl.exec_op(op) + self.assertEqual(len(l_set), 3) + + def test_load_node_set_by_DB_id(self): + """ + test node DB id life cycle + """ + + # create nodes, get DB ids + op = dbc.DBO_add_node_set({'T_test_load_node_set_by_DB_id': [{'name': 'John Doe'}, + {'name': 'John Doe'}]}) + id_set = self.db_ctl.exec_op(op) + + # match against DB ids + op = dbc.DBO_load_node_set_by_DB_id(id_set) + n_set = self.db_ctl.exec_op(op) + self.assertEqual(len(n_set), len(id_set), 'incorrect result size') + + def test_partial_query_set_execution_success(self): + """ + test: + - statement execution stops at first invalid statement + - assert create statement with result data does not actually persist in DB + + From the REST API doc: 'If any errors occur while executing statements, + the server will roll back the transaction.' + """ + n_id = 'test_partial_query_set_execution_success' + + op = dbc.DB_op() + op.add_statement("create (n:Person {id: '%s'}) return n" % (n_id), {}) # valid statement + op.add_statement("match (n) return n", {}) # valid statement + op.add_statement("non-valid statement #1", {}) + op.add_statement("non-valid statement #2", {}) + + self.assertRaises(Neo4JException, self.db_ctl.exec_op, op) + + self.assertEqual(len(op.result_set), 2) + self.assertEqual(len(op.error_set), 1) + + # assert node creation did not persist + n_set = self.db_ctl.exec_op(dbc.DBO_match_node_set_by_id_attribute([n_id])) + self.assertEqual(len(n_set), 0) + + def test_topo_diff_commit(self): + n_0_id = rand_id() + n_1_id = rand_id() + n_2_id = rand_id() + n_T = 'T_test_topo_diff_commit' + + n_set = [{'__type': n_T, 'id': n_0_id }, + {'__type': n_T, 'id': n_1_id }, + {'__type': n_T, 'id': n_2_id }] + l_set = [{'__type': n_T, '__src': n_0_id, '__dst': n_1_id}, + {'__type': n_T, '__src': n_1_id, '__dst': n_0_id}] + + topo_diff = Topo_Diff(node_set_add=n_set, + link_set_add=l_set) + + op = dbc.DBO_topo_diff_commit(topo_diff) + op_ret = self.db_ctl.exec_op(op) + self.assertEqual(len(op_ret), 2) # to id-sets, nodes & links + self.assertEqual(len(op_ret[0]), 3) # expect id-set of length 3 + self.assertEqual(len(op_ret[1]), 2) # expect id-set of length 2 + + id_set = self.db_ctl.exec_op(dbc.DBO_match_node_set_by_id_attribute([n_0_id, n_1_id])) + self.assertEqual(len(id_set), 2) + + l_ptr = Link.link_ptr(src_id=n_0_id, dst_id=n_1_id) + id_set = self.db_ctl.exec_op(dbc.DBO_load_link_set.init_from_link_ptr(l_ptr)) + self.assertEqual(len(id_set), 1) + + l_ptr = Link.link_ptr(src_id=n_1_id, dst_id=n_0_id) + id_set = self.db_ctl.exec_op(dbc.DBO_load_link_set.init_from_link_ptr(l_ptr)) + self.assertEqual(len(id_set), 1) + + id_set_rm = [n_2_id] + topo_diff = Topo_Diff(node_set_rm=id_set_rm) + op = dbc.DBO_topo_diff_commit(topo_diff) + self.db_ctl.exec_op(op) + op = dbc.DBO_match_node_set_by_id_attribute(id_set_rm) + id_set = self.db_ctl.exec_op(op) + self.assertEqual(len(id_set), 0) + + def test_attr_diff_commit(self): + # create test node + n_id = rand_id() + topo_diff = Topo_Diff(node_set_add=[{'__type': 'T_test_attr_diff_commit', 'id': n_id, 'attr_rm': 0}]) + op = dbc.DBO_topo_diff_commit(topo_diff) + self.db_ctl.exec_op(op) + + # apply attr_diff + attr_diff = Attr_Diff() + attr_diff.add_node_attr_write(n_id, 'attr_0', 0) + attr_diff.add_node_attr_write(n_id, 'attr_1', 'a') + attr_diff.add_node_attr_rm(n_id, 'attr_rm') + + op = dbc.DBO_attr_diff_commit(attr_diff) + n_map = self.db_ctl.exec_op(op) + self.assertEqual(len(n_map), 1) + n = n_map.get(n_id) + self.assertTrue(None != n) + self.assertTrue(None == n.get('attr_rm')) + self.assertEqual(0, n.get('attr_0')) + self.assertEqual('a', n.get('attr_1')) + + # attr-set only + attr_diff = Attr_Diff() + attr_diff.add_node_attr_write(n_id, 'attr_2', 0) + + op = dbc.DBO_attr_diff_commit(attr_diff) + n_map = self.db_ctl.exec_op(op) + + # attr-remove only + attr_diff = Attr_Diff() + attr_diff.add_node_attr_rm(n_id, 'attr_2') + + op = dbc.DBO_attr_diff_commit(attr_diff) + n_map = self.db_ctl.exec_op(op) + + def test_rm_node_set(self): + n_0_id = rand_id() + n_1_id = rand_id() + n_2_id = rand_id() + n_3_id = rand_id() + n_T = 'T_test_rm_node_set' + + n_set = [{'__type': n_T, 'id': n_0_id }, + {'__type': n_T, 'id': n_1_id }, + {'__type': n_T, 'id': n_2_id }, + {'__type': n_T, 'id': n_3_id }] + l_set = [{'__type': n_T, '__src': n_2_id, '__dst': n_2_id}, + {'__type': n_T, '__src': n_2_id, '__dst': n_3_id}] + + topo_diff = Topo_Diff(node_set_add=n_set, + link_set_add=l_set) + + op = dbc.DBO_topo_diff_commit(topo_diff) + self.db_ctl.exec_op(op) + + op = dbc.DBO_rm_node_set([n_0_id, n_1_id]) + self.db_ctl.exec_op(op) + + op = dbc.DBO_rm_node_set([n_2_id, n_3_id], rm_links=True) + self.db_ctl.exec_op(op) + + # assert all deleted + op = dbc.DBO_match_node_id_set(filter_type=n_T) + id_set = self.db_ctl.exec_op(op) + self.assertEqual(len(id_set), 0) + + def test_rz_clone(self): + l_n, l_r = gen_rand_data(self.db_ctl, lim_n=8, lim_r=16, prob_link_create=0.7) + op = dbc.DBO_rz_clone(filter_label=l_n, limit=32) + ret = self.db_ctl.exec_op(op) + n_set = ret['node_set'] + l_set = ret['link_set'] + + # TODO improve assertions + self.assertTrue(0 < len(n_set)) + self.assertTrue(0 < len(l_set)) + + def tearDown(self): pass + +if __name__ == "__main__": + unittest.main() diff --git a/src-py_test/test_rhizi_api.py b/src-py_test/test_rhizi_api.py new file mode 100644 index 00000000..c404edba --- /dev/null +++ b/src-py_test/test_rhizi_api.py @@ -0,0 +1,84 @@ +import unittest +import db_controller as dbc +import rhizi_api +import json +import logging + +from rhizi_server import Config +from werkzeug.test import EnvironBuilder +from werkzeug.test import Client + +from db_controller import DB_Driver_Embedded + +class TestRhiziAPI(unittest.TestCase): + + def setUp(self): + self.flush_db() + + @classmethod + def setUpClass(self): + cfg = Config.init_from_file('res/etc/rhizi-server.conf') + self.db_ctl = dbc.DB_Controller(cfg) + rhizi_api.db_ctl = self.db_ctl + + # TODO extract to superclass + log = logging.getLogger('rhizi') + log.setLevel(logging.DEBUG) + log_handler_c = logging.StreamHandler() + log.addHandler(log_handler_c) + + def flush_db(self): + """ + complete DB flush: remove all nodes & links + """ + self.db_ctl.exec_cypher_query('match (n) optional match (n)-[r]-() delete n,r') + + def test_add_node_set(self): + """ + add node set test + """ + node_map = { 'Skill': [{ 'name': 'kung-fu' }, { 'name': 'judo' }] } + with rhizi_api.webapp.test_client() as c: + req = c.post('/add/node-set', + content_type='application/json', + data=json.dumps(dict(node_map=node_map))) + id_set = json.loads(req.data)['data'] + self.assertEqual(2, len(id_set)) + self.assertTrue(isinstance(id_set[0], int)) + + def test_load_node_non_existing(self): + """ + loading a non existing node test + """ + id_set = ['non_existing_id'] + with rhizi_api.webapp.test_client() as c: + req = c.post('/load/node-set-by-id', + content_type='application/json', + data=json.dumps({ 'id_set': id_set})) + req_data = json.loads(req.data) + rz_data = req_data['data'] + rz_err = req_data['error'] + self.assertEqual(None, rz_err) + self.assertEqual(0, len(rz_data)) + + def test_load_node_set_by_id_existing(self): + """ + loading an existing node test + """ + id_set = ['skill_00'] + self.db_ctl.exec_cypher_query('create (s:Skill {id: \'skill_00\'} )') + + with rhizi_api.webapp.test_client() as c: + req = c.post('/load/node-set-by-id', + content_type='application/json', + data=json.dumps({ 'id_set': id_set})) + n_set = json.loads(req.data)['data'] + + self.assertEqual(1, len(n_set)) + self.assertEqual(n_set[0]['id'], id_set[0]) + + def test_load_node_set(self): + pass + +if __name__ == "__main__": + unittest.main() -- cgit v1.3.1 From 60890e054075d92be557f9a7da6f29b5e8ec9f90 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 9 Nov 2014 13:32:22 +0200 Subject: config-dir command line arg --- src-py/rhizi_server.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index 5393a61e..c9bc65e6 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -1,8 +1,9 @@ import logging import json import util +import os import neo4j_util - +import argparse import db_controller as dbc import rhizi_api @@ -15,6 +16,9 @@ class Config(object): def init_from_file(file_path): ret = Config() + if False == os.path.exists(file_path): + raise Exception('config file not found: ' + file_path) + with open(file_path, 'r') as f: cfg = json.loads(f.read()) @@ -50,9 +54,15 @@ class RhiziServer(object): if __name__ == "__main__": + p = argparse.ArgumentParser(description='rhizi-server') + p.add_argument('--config-dir', help='path to Rhizi config dir') + args = p.parse_args() + + cfg_dir = args.config_dir + init_logging() - cfg = Config.init_from_file('res/etc/rhizi-server.conf') + cfg = Config.init_from_file(os.path.join(cfg_dir, 'rhizi-server.conf')) db_ctl = dbc.DB_Controller(cfg) rhizi_api.db_ctl = db_ctl -- cgit v1.3.1 From 3bcc61d481e9332c3f254cf4f0bb4e79fa0a4b15 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 17 Nov 2014 19:50:46 +0200 Subject: server util code --- src-py/util.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src-py/util.py (limited to 'src-py') diff --git a/src-py/util.py b/src-py/util.py new file mode 100644 index 00000000..e8840114 --- /dev/null +++ b/src-py/util.py @@ -0,0 +1,22 @@ +""" +code with no better place to go +""" +import time + +def debug_log_duration(method): + """ + dubug call durations - use example: + + neo4j_util.post = util.debug_log_duration(neo4j_util.post) + """ + + def timed(*args, **kw): + t_0 = time.time() + result = method(*args, **kw) + t_1 = time.time() + dt = t_1 - t_0 + + print ('%2.2f sec, function: %r' % (dt, method.__name__)) + return result + + return timed -- cgit v1.3.1 From 72aa3ff93981e078357ba30682b12c600b08f2df Mon Sep 17 00:00:00 2001 From: Alon Levy Date: Mon, 17 Nov 2014 19:55:25 +0200 Subject: add default argument to --config-dir --- src-py/rhizi_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index c9bc65e6..49d762d6 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -55,7 +55,7 @@ class RhiziServer(object): if __name__ == "__main__": p = argparse.ArgumentParser(description='rhizi-server') - p.add_argument('--config-dir', help='path to Rhizi config dir') + p.add_argument('--config-dir', help='path to Rhizi config dir', default='res/etc') args = p.parse_args() cfg_dir = args.config_dir -- cgit v1.3.1 From ed2e0747a050f06e0b392e5aacb21f869eaa544b Mon Sep 17 00:00:00 2001 From: Alon Levy Date: Mon, 24 Nov 2014 12:11:11 +0200 Subject: db_controller: remove unused import --- src-py/db_controller.py | 1 - 1 file changed, 1 deletion(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index d9d8d737..5602efac 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -5,7 +5,6 @@ import logging import os import re import traceback -import urllib2 from db_driver import DB_Driver_REST, DB_Driver_Base from model.graph import Attr_Diff -- cgit v1.3.1 From 1045754c6e1b5587f09fef11be8b62094a29d697 Mon Sep 17 00:00:00 2001 From: Alon Levy Date: Mon, 24 Nov 2014 12:11:36 +0200 Subject: db_controller: white space before EOL removal --- src-py/db_controller.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 5602efac..8800222a 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -43,11 +43,11 @@ class DB_op(object): def __iter__(self): """ - iterate over (statement_index, statement, result, error) + iterate over (statement_index, statement, result, error) where result & error are mutually exclusive - + note: statement_index is zero based - + TODO: handle partial iteration due to error_set being non-empty """ i = 0 @@ -94,7 +94,7 @@ class DB_composed_op(DB_op): def __getattribute__(self, attr): """ - intercept 'statement_set' attr get + intercept 'statement_set' attr get """ if attr == 'statement_set': self.__assert_false_statement_access() @@ -199,7 +199,7 @@ class DBO_add_node_set(DB_op): def __init__(self, node_map): """ DB op: add node set - + @param node_map: node-type to node-set map @return: set of new node DB ids """ @@ -239,7 +239,7 @@ class DBO_load_node_set_by_DB_id(DB_op): def __init__(self, id_set): """ load a set of nodes whose DB id is in id_set - + @param id_set: DB node id set @return: loaded node set or an empty set if no match was found """ @@ -252,11 +252,11 @@ class DBO_match_node_id_set(DB_op): def __init__(self, filter_label=None, filter_attr_map={}): """ match a set of nodes by type / attr_map - + @param filter_label: node type filter @param filter_attr_map: is a filter_key to filter_value_set map of possible attributes to match against, eg.: - { 'id':[0,1], 'color: ['red','blue'] } + { 'id':[0,1], 'color: ['red','blue'] } @return: a set of node DB id's """ super(DBO_match_node_id_set, self).__init__() @@ -283,7 +283,7 @@ class DBO_load_link_set(DB_op): def __init__(self, link_ptr_set): """ match a set of sets of links by source/target node id attributes - + This class should be instantiated through a static factory function @link_ptr_set link pointer set @@ -316,8 +316,8 @@ class DBO_match_link_id_set(DB_op): def __init__(self, filter_label=None, filter_attr_map={}): """ load an id-set of links - - @param filter_label: link type filter + + @param filter_label: link type filter @param filter_attr_map: is a filter_key to filter_value_set map of attributes to match link properties against @return: a set of loaded link ids @@ -357,7 +357,7 @@ class DBO_rz_clone(DB_op): def __init__(self, filter_label=None, limit=128): """ clone rhizi - + @return: a dict: {'node_set': n_set, 'link_set': l_set } where l_set is a list of (src.id, dst.id, link) tuples -- cgit v1.3.1 From 12506bd331378a2582ff1e54f1b4e09910b7342d Mon Sep 17 00:00:00 2001 From: Alon Levy Date: Mon, 24 Nov 2014 12:27:16 +0200 Subject: python3 compat: urllib --- src-py/neo4j_util.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 2b6257b4..1d0f0203 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -3,7 +3,8 @@ """ import json -import urllib2 +from six.moves.urllib import request +import six.moves.urllib_error as urllib_error import model import string import time @@ -79,7 +80,7 @@ def post(url, data): post_data_json = json.dumps(data) - req = urllib2.Request(url) + req = request.Request(url) req.add_header('User-Agent', 'rhizi-server/0.1') req.add_header('Accept', 'application/json; charset=UTF-8') req.add_header('Content-Type', 'application/json') @@ -87,8 +88,8 @@ def post(url, data): req.add_header('X-Stream', 'true') # enable neo4j JSON streaming try: - ret = urllib2.urlopen(req, post_data_json) - except urllib2.HTTPError as e: + ret = request.urlopen(req, post_data_json) + except urllib_error.HTTPError as e: raise Exception('post request failed: code: {0}, reason: {1}'.format(e.code, e.reason)) return ret -- cgit v1.3.1 From 83e348827e685798b2200252698c6705e57f7647 Mon Sep 17 00:00:00 2001 From: Alon Levy Date: Mon, 24 Nov 2014 12:27:42 +0200 Subject: neo4j_util: whitespace before EOL fixes --- src-py/neo4j_util.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 1d0f0203..ff0fca96 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -45,9 +45,9 @@ class DB_result_set(object): class Cypher_String_Formatter(string.Formatter): """ - Despite parameter support in Cypher, we sometimes do engage in query string building + Despite parameter support in Cypher, we sometimes do engage in query string building - as both Cypher & Python use brackets to wrap parameters, escaping them in Python makes - queries less readable. This customized formatter will simply ignore unavailable keyworded + queries less readable. This customized formatter will simply ignore unavailable keyworded formatting arguments, allowing the use of non-escaped parameter designation, eg: q = cfmt("match (a:{type} {cypher_param})", type='Book') """ @@ -133,9 +133,9 @@ def gen_clause_where_from_filter_attr_map(filter_attr_map, node_label="n"): convert a filter attribute map to a parameterized Cypher where clause, eg. in: { 'att_foo': [ 'a', 'b' ], 'att_goo': [1,2] } out: {att_foo: {att_foo}, att_goo: {att_goo}, ...} - - this function will essentially ignore all but the first value in the value list - + + this function will essentially ignore all but the first value in the value list + @param filter_attr_map: may be None or empty """ if not filter_attr_map: @@ -155,10 +155,10 @@ def gen_clause_where_from_filter_attr_map(filter_attr_map, node_label="n"): def gen_query_create_from_node_map(node_map, input_to_DB_property_map=lambda _: _): """ generate a set of node create queries - + @param node_map: is a node-type to node map @input_to_DB_property_map: optional function which takes a map of input properties and returns a map of DB properties - use to map input schemas to DB schemas - + @return: a (query, query_parameteres) set of create queries """ __type_check_link_or_node_map(node_map) @@ -176,7 +176,7 @@ def gen_query_create_from_node_map(node_map, input_to_DB_property_map=lambda _: def gen_query_create_from_link_map(link_map, input_to_DB_property_map=lambda _: _): """ generate a set of link create queries - + @param link_map: is a link-type to link map - see model.link """ __type_check_link_or_node_map(link_map) @@ -210,7 +210,7 @@ def meta_attr_list_to_meta_attr_map(e_set, meta_attr='__type'): """ convert a list of maps each containing a meta_attr key into a meta_attr-mapped collection of lists with the meta_attr removed - eg: - + in: [{'id':0, '__type': 'T'}, {'id':1, '__type': 'T'}] out: { 'T', [{'id':0}, {'id':1}] } """ -- cgit v1.3.1 From da5792f2fc055fec121a1c552bb628bdd2dbdbd8 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 24 Nov 2014 14:32:37 +0200 Subject: rhizi_server.py: add hash bang --- src-py/rhizi_server.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src-py') diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index 49d762d6..f085e863 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -1,3 +1,5 @@ +#!/usr/bin/python + import logging import json import util -- cgit v1.3.1 From f778e08693d6b3ca1e4046a7819e130f38c59616 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 24 Nov 2014 17:15:19 +0200 Subject: rhizi_server_fcgi.py --- src-py/rhizi_server_fcgi.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src-py/rhizi_server_fcgi.py (limited to 'src-py') diff --git a/src-py/rhizi_server_fcgi.py b/src-py/rhizi_server_fcgi.py new file mode 100644 index 00000000..917c7ce4 --- /dev/null +++ b/src-py/rhizi_server_fcgi.py @@ -0,0 +1,15 @@ +#!/usr/bin/python + +from flup.server.fcgi import WSGIServer +import rhizi_api +import os +import db_controller as dbc +from rhizi_server import Config + +if __name__ == '__main__': + cfg_dir = '/etc/rhizi' + cfg = Config.init_from_file(os.path.join(cfg_dir, 'rhizi-server.conf')) + + db_ctl = dbc.DB_Controller(cfg) + rhizi_api.db_ctl = db_ctl + WSGIServer(rhizi_api.webapp).run() -- cgit v1.3.1 From 427fbc64eeda746ad2c3e06cd34db296a00dbb78 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 24 Nov 2014 17:34:48 +0200 Subject: monitor__server_info --- src-py/rhizi_api.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 3e96d860..6bc77cb6 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -5,12 +5,18 @@ import os import db_controller as dbc import json import logging +import traceback from flask import jsonify from flask import Flask from flask import request from flask import make_response +from model.graph import Topo_Diff +from model.graph import Attr_Diff +from model.model import Link +from datetime import datetime + log = logging.getLogger('rhizi') class FlaskExt(Flask): @@ -186,3 +192,16 @@ def add_node_set(): op = dbc.DBO_add_node_set(node_map) return __common_exec(op) +@webapp.route("/monitor/server-info", methods=['GET']) +def monitor__server_info(): + """ + server monitor stub + """ + dt = datetime.now() + return "" + \ + "

Rhizi Server v0.1

" + \ + "date: " + dt.strftime("%Y-%m-%d") + "
" + \ + "time: " + dt.strftime("%H:%M:%S") + "
" + \ + "

" + + -- cgit v1.3.1 From 23bb05b2f506743bf9c0008f8a7d9dd68bfa326e Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 30 Nov 2014 12:36:10 +0200 Subject: applicative REST API init - provides logic<>web-framework decoupling --- src-py/rhizi_api.py | 12 ------------ src-py/rhizi_server.py | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 12 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 6bc77cb6..7391c90f 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -31,8 +31,6 @@ class FlaskExt(Flask): ret.headers['Access-Control-Allow-Headers'] = "Origin, Content-Type, Accept, Authorization" return ret -webapp = FlaskExt(__name__) -webapp.debug = True # injected: DB controller db_ctl = None @@ -69,7 +67,6 @@ def __common_exec(op, on_success=__common_resp_handle): log.error(traceback.print_exc()) return __common_resp_handle('error occurred') -@webapp.route("/load/node-set-by-id", methods=['POST']) def load_node_set_by_id_attr(): """ load node-set by ID attribute @@ -99,7 +96,6 @@ def __load_node_set_by_id_attr_common(id_set): log.exception(e) return __common_resp_handle(error='unable to load node with ids: {0}'.format(id_set)) -@webapp.route("/match/node-set", methods=['POST']) def match_node_set_by_attr_filter_map(attr_filter_map): """ @param attr_filter_map @@ -109,7 +105,6 @@ def match_node_set_by_attr_filter_map(attr_filter_map): op = dbc.DBO_match_node_id_set(attr_filter_map) return __common_exec(op) -@webapp.route("/load/link-set/by_link_ptr_set", methods=['POST']) def load_link_set_by_link_ptr_set(): def deserialize_param_set(param_json): @@ -130,12 +125,10 @@ def load_link_set_by_link_ptr_set(): op = dbc.DBO_load_link_set.init_from_link_ptr_set(l_ptr_set) return __common_exec(op) -@webapp.route("/graph/clone", methods=['POST']) def rz_clone(): op = dbc.DBO_rz_clone() return __common_exec(op) -@webapp.route("/graph/diff-commit-set", methods=['POST']) def diff_commit_set(): """ commit a diff set @@ -150,7 +143,6 @@ def diff_commit_set(): op = dbc.DBO_topo_diff_commit(topo_diff) return __common_exec(op) -@webapp.route("/graph/diff-commit-topo", methods=['POST']) def diff_commit_topo(): """ commit a graph topology diff @@ -164,7 +156,6 @@ def diff_commit_topo(): op = dbc.DBO_topo_diff_commit(topo_diff) return __common_exec(op) -@webapp.route("/graph/diff-commit-attr", methods=['POST']) def diff_commit_attr(): """ commit a graph attribute diff @@ -175,11 +166,9 @@ def diff_commit_attr(): op = dbc.DBO_attr_diff_commit(attr_diff) return __common_exec(op) -@webapp.route("/graph/diff-commit-vis", methods=['POST']) def diff_commit_vis(): pass -@webapp.route("/add/node-set", methods=['POST']) def add_node_set(): """ @deprecated: use topo_attr_commit @@ -192,7 +181,6 @@ def add_node_set(): op = dbc.DBO_add_node_set(node_map) return __common_exec(op) -@webapp.route("/monitor/server-info", methods=['GET']) def monitor__server_info(): """ server monitor stub diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index f085e863..cef57f36 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -54,6 +54,33 @@ def init_logging(): class RhiziServer(object): pass +def init_rest_api(flask_webapp): + """ + map REST API calls + """ + rest_entry_set = [ + rest_entry('/add/node-set' , rhizi_api.add_node_set), + rest_entry('/graph/clone', rhizi_api.rz_clone), + rest_entry('/graph/diff-commit-set', rhizi_api.diff_commit_set), + rest_entry('/graph/diff-commit-topo', rhizi_api.diff_commit_topo), + rest_entry('/graph/diff-commit-attr', rhizi_api.diff_commit_attr), + rest_entry('/graph/diff-commit-vis', rhizi_api.diff_commit_vis), + rest_entry('/index', rhizi_api.index), + rest_entry('/load/node-set-by-id', rhizi_api.load_node_set_by_id_attr), + rest_entry('/load/link-set/by_link_ptr_set', rhizi_api.load_link_set_by_link_ptr_set), + rest_entry('/login', rhizi_api.login, {'methods': ['GET', 'POST']}), + rest_entry('/logout', rhizi_api.logout), + rest_entry('/match/node-set', rhizi_api.match_node_set_by_attr_filter_map), + rest_entry('/monitor/server-info', rhizi_api.monitor__server_info), + ] + + for re in rest_entry_set: + rest_path, f, flask_args = re + route_decorator = flask_webapp.route(rest_path, **flask_args) + flask_webapp.f = route_decorator(f) + + if '/login' != rest_path: + if __name__ == "__main__": p = argparse.ArgumentParser(description='rhizi-server') -- cgit v1.3.1 From 7a5e94b48165f76fd86a82fc90a466cb683ccc94 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 2 Dec 2014 02:08:56 +0200 Subject: crypt_util: add/validate logins --- src-py/crypt_util.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src-py/crypt_util.py (limited to 'src-py') diff --git a/src-py/crypt_util.py b/src-py/crypt_util.py new file mode 100644 index 00000000..464fac94 --- /dev/null +++ b/src-py/crypt_util.py @@ -0,0 +1,46 @@ +import pickle +import hashlib, uuid +import os +import logging + +log = logging.getLogger('rhizi') + +def add_user_login(config, u, p): + htpasswd_path = config.htpasswd_path + + if False == os.path.exists(htpasswd_path): + with open(htpasswd_path, 'wb') as f: + pickle.dump({}, f) + + with open(htpasswd_path, 'rb') as f: + data = f.read() + pw_db = pickle.loads(data) + + with open(htpasswd_path, 'wb') as f: + salt = config.secret_key + pw_db[u] = hash_pw(str(p), salt) + pickle.dump(pw_db, f) + + log.info('htpasswd db: added entry: user: %s, pw: %s...' % (u, pw_db[u][:5])) + +def hash_pw(pw_str, salt_str): + salt = hashlib.sha512(salt_str).hexdigest() + ret = hashlib.sha512(pw_str + salt).hexdigest() + return ret + +def validate_login(config, u, p): + htpasswd_path = config.htpasswd_path + + salt = config.secret_key + + with open(htpasswd_path) as f: + pw_db = pickle.load(f) + + existing_pw_hash = pw_db.get(u) + if None == existing_pw_hash: + raise Exception('Not autorhized') + + if hash_pw(p, salt) != existing_pw_hash: + raise Exception('Not autorhized') + + -- cgit v1.3.1 From 4730452ec0f922a8bb26690e5262efa3bb56f4eb Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 2 Dec 2014 02:07:23 +0200 Subject: REST /login,/logout paths --- src-py/rhizi_api.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 7391c90f..8df36b56 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -192,4 +192,34 @@ def monitor__server_info(): "time: " + dt.strftime("%H:%M:%S") + "
" + \ "

" +def index(): + return render_template('index.html') +def login(): + + def sanitize_input(req): + req_json = request.get_json() + u = req_json['username'] + p = req_json['password'] + return u, p + + if request.method == 'POST': + try: + u, p = sanitize_input(request) + crypt_util.validate_login(flask.current_app.rz_config, u, p) + except Exception as e: + log.warn('login: unauthorized: user: %s' % (u)) + abort(401) + + # login successful + session['username'] = u + log.debug('login: success: user: %s' % (u)) + return redirect('/index') + + if request.method == 'GET': + return render_template('login.html') + +def logout(): + # remove the username from the session if it's there + session.pop('username', None) + return redirect(url_for('login')) -- cgit v1.3.1 From e97ade2d8cf8c0a927cd96bd07380ea35b499d98 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 2 Dec 2014 02:20:17 +0200 Subject: simplify config loading, allow missing config values --- src-py/rhizi_server.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index cef57f36..bda762a3 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -27,10 +27,16 @@ class Config(object): # # TODO: config option documentation # - ret.neo4j_url = cfg['neo4j_url'] - ret.listen_address = cfg['listen_address'] - ret.listen_port = cfg['listen_port'] + # htpasswd_path + # listen_address + # listen_port + # neo4j_url + # root_path + for k, v in cfg.items(): + ret.__setattr__(k, v) + + ret.__setattr__('config_dir', os.path.dirname(file_path)) # bypass prop restriction return ret @property @@ -41,6 +47,14 @@ class Config(object): def tx_api_path(self): return '/db/data/transaction' + @property + def config_dir_path(self): + return self.config_dir + + @property + def secret_key(self): + return self.SECRET_KEY + def init_logging(): log = logging.getLogger('rhizi') -- cgit v1.3.1 From 162e1e04660fc0e80635bbd9ad56ece564531a94 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 2 Dec 2014 02:21:35 +0200 Subject: rhizi .fcgi script - renamed on deployment --- src-py/rhizi_server_fcgi.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) mode change 100644 => 100755 src-py/rhizi_server_fcgi.py (limited to 'src-py') diff --git a/src-py/rhizi_server_fcgi.py b/src-py/rhizi_server_fcgi.py old mode 100644 new mode 100755 index 917c7ce4..bda8d91c --- a/src-py/rhizi_server_fcgi.py +++ b/src-py/rhizi_server_fcgi.py @@ -1,15 +1,24 @@ #!/usr/bin/python from flup.server.fcgi import WSGIServer -import rhizi_api import os -import db_controller as dbc -from rhizi_server import Config +import sys +import cgitb +import rhizi_server + +# sys.path.insert(0, '/srv/www/rhizi/rhizi.net/src-py') + +# enable debugging +cgitb.enable() if __name__ == '__main__': cfg_dir = '/etc/rhizi' - cfg = Config.init_from_file(os.path.join(cfg_dir, 'rhizi-server.conf')) + cfg = rhizi_server.Config.init_from_file(os.path.join(cfg_dir, 'rhizi-server.conf')) + + log = rhizi_server.init_logging() + webapp = rhizi_server.init_webapp(cfg) + rhizi_server.init_rest_api(webapp) + + log.info('launching webapp via flup.server.fcgi.WSGIServer') - db_ctl = dbc.DB_Controller(cfg) - rhizi_api.db_ctl = db_ctl - WSGIServer(rhizi_api.webapp).run() + WSGIServer(webapp).run() -- cgit v1.3.1 From d8af6c4be6120fab236f614fb93cb632fae174b5 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 2 Dec 2014 02:25:06 +0200 Subject: REST mapping: apply login_decorator() to all but /login paths --- src-py/rhizi_server.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src-py') diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index bda762a3..2edce5dd 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -72,6 +72,21 @@ def init_rest_api(flask_webapp): """ map REST API calls """ + + def rest_entry(path, f, flask_args={}): + return (path, f, flask_args) + + def login_decorator(f): + """ + check user is logged in before executing REST api call + """ + def wrapped_function(*args, **kw): + if not 'username' in session: + return redirect('/login') + return f(*args, **kw) + + return wrapped_function + rest_entry_set = [ rest_entry('/add/node-set' , rhizi_api.add_node_set), rest_entry('/graph/clone', rhizi_api.rz_clone), @@ -94,6 +109,9 @@ def init_rest_api(flask_webapp): flask_webapp.f = route_decorator(f) if '/login' != rest_path: + # currently require login on all but /login paths + flask_webapp.f = login_decorator(f) + if __name__ == "__main__": -- cgit v1.3.1 From 785610e63fb23d887764bad1094031324354c553 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 2 Dec 2014 02:27:17 +0200 Subject: refactor init functions --- src-py/rhizi_server.py | 48 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 9 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index 2edce5dd..cd2b7d41 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -8,6 +8,12 @@ import neo4j_util import argparse import db_controller as dbc import rhizi_api +import flask +import crypt_util + +from flask import session +from flask import redirect +from flask import url_for class Config(object): """ @@ -55,6 +61,9 @@ class Config(object): def secret_key(self): return self.SECRET_KEY +class RhiziServer(object): + pass + def init_logging(): log = logging.getLogger('rhizi') @@ -64,9 +73,7 @@ def init_logging(): log.addHandler(log_handler_c) log.addHandler(log_handler_f) - -class RhiziServer(object): - pass + return log def init_rest_api(flask_webapp): """ @@ -112,19 +119,42 @@ def init_rest_api(flask_webapp): # currently require login on all but /login paths flask_webapp.f = login_decorator(f) +def init_webapp(cfg): + root_path = cfg.root_path + webapp = rhizi_api.FlaskExt(__name__, + static_folder='static', + template_folder=os.path.join(root_path, 'templates'), + static_url_path='') + webapp.config.from_object(cfg) + webapp.root_path = root_path # for some reason calling config.from_pyfile() + + db_ctl = dbc.DB_Controller(cfg) + rhizi_api.db_ctl = db_ctl + + webapp.rz_config = cfg + return webapp + +def init_config(cfg_dir): + cfg_path = os.path.join(cfg_dir, 'rhizi-server.conf') + cfg = Config.init_from_file(cfg_path) + return cfg + if __name__ == "__main__": p = argparse.ArgumentParser(description='rhizi-server') p.add_argument('--config-dir', help='path to Rhizi config dir', default='res/etc') + p.add_argument('--init-htpasswd-db', help='init login htpasswd db', action='store_const', const=True) args = p.parse_args() - cfg_dir = args.config_dir + log = init_logging() + cfg = init_config(args.config_dir) - init_logging() - cfg = Config.init_from_file(os.path.join(cfg_dir, 'rhizi-server.conf')) - db_ctl = dbc.DB_Controller(cfg) + webapp = init_webapp(cfg) + init_rest_api(webapp) + + log.info('launching webapp via Flusk development server') + webapp.run(host=cfg.listen_address, + port=cfg.listen_port) - rhizi_api.db_ctl = db_ctl - rhizi_api.webapp.run(host=cfg.listen_address, port=cfg.listen_port) -- cgit v1.3.1 From 073da71b6318f1d69cd5f45f4f60da00bd437fa2 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 3 Dec 2014 16:49:33 +0200 Subject: pass username to index template --- src-py/rhizi_api.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 8df36b56..95822be2 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -193,7 +193,8 @@ def monitor__server_info(): "

" def index(): - return render_template('index.html') + username = escape(session['username']) + return render_template('index.html', username=username) def login(): @@ -208,18 +209,20 @@ def login(): u, p = sanitize_input(request) crypt_util.validate_login(flask.current_app.rz_config, u, p) except Exception as e: + # login failed log.warn('login: unauthorized: user: %s' % (u)) - abort(401) + return render_template('login.html', login_failed=True) # login successful session['username'] = u log.debug('login: success: user: %s' % (u)) - return redirect('/index') + return redirect(url_for('index')) if request.method == 'GET': return render_template('login.html') def logout(): # remove the username from the session if it's there - session.pop('username', None) + u = session.pop('username', None) + log.debug('logout: success: user: %s' % (u)) return redirect(url_for('login')) -- cgit v1.3.1 From 52719d012c314eb03240b95c6b34e0d0c85fd08e Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 3 Dec 2014 16:50:08 +0200 Subject: refactor init_rest_api() --- src-py/rhizi_server.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index cd2b7d41..1efa684f 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -80,13 +80,14 @@ def init_rest_api(flask_webapp): map REST API calls """ - def rest_entry(path, f, flask_args={}): + def rest_entry(path, f, flask_args={'methods': ['POST']}): return (path, f, flask_args) def login_decorator(f): """ - check user is logged in before executing REST api call + [!] security boundary: asserd logged-in user before executing REST api call """ + @wraps(f) def wrapped_function(*args, **kw): if not 'username' in session: return redirect('/login') @@ -101,23 +102,27 @@ def init_rest_api(flask_webapp): rest_entry('/graph/diff-commit-topo', rhizi_api.diff_commit_topo), rest_entry('/graph/diff-commit-attr', rhizi_api.diff_commit_attr), rest_entry('/graph/diff-commit-vis', rhizi_api.diff_commit_vis), - rest_entry('/index', rhizi_api.index), + rest_entry('/index', rhizi_api.index, {'methods': ['GET']}), rest_entry('/load/node-set-by-id', rhizi_api.load_node_set_by_id_attr), rest_entry('/load/link-set/by_link_ptr_set', rhizi_api.load_link_set_by_link_ptr_set), rest_entry('/login', rhizi_api.login, {'methods': ['GET', 'POST']}), - rest_entry('/logout', rhizi_api.logout), + rest_entry('/logout', rhizi_api.logout, {'methods': ['GET', 'POST']}), rest_entry('/match/node-set', rhizi_api.match_node_set_by_attr_filter_map), rest_entry('/monitor/server-info', rhizi_api.monitor__server_info), ] - for re in rest_entry_set: - rest_path, f, flask_args = re - route_decorator = flask_webapp.route(rest_path, **flask_args) - flask_webapp.f = route_decorator(f) + for re_entry in rest_entry_set: + rest_path, f, flask_args = re_entry if '/login' != rest_path: # currently require login on all but /login paths - flask_webapp.f = login_decorator(f) + f = login_decorator(f) + + # [!] order seems important - apply route decorator last + route_dec = flask_webapp.route(rest_path, **flask_args) + f = route_dec(f) + + flask_webapp.f = f # assign decorated function def init_webapp(cfg): root_path = cfg.root_path -- cgit v1.3.1 From 64465e6647470731b868b87e19d995899bfd0d31 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 3 Dec 2014 16:50:31 +0200 Subject: mv server log path --- src-py/rhizi_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index 1efa684f..15679db9 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -69,7 +69,7 @@ def init_logging(): log = logging.getLogger('rhizi') log.setLevel(logging.DEBUG) log_handler_c = logging.StreamHandler() - log_handler_f = logging.FileHandler('/tmp/rhizi-server.log') + log_handler_f = logging.FileHandler('/var/log/rhizi/rhizi-server.log') log.addHandler(log_handler_c) log.addHandler(log_handler_f) -- cgit v1.3.1 From ca876ae07e7d01a74e41707ec7510fc59a0445be Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sat, 6 Dec 2014 21:22:51 +0200 Subject: Config.init() - defaults, flask-key handling, type conversion --- src-py/rhizi_server.py | 58 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 13 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index 15679db9..024e7246 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -18,33 +18,65 @@ from flask import url_for class Config(object): """ rhizi-server configuration + + TODO: config option documentation + + htpasswd_path + listen_address + listen_port + neo4j_url + root_path """ @staticmethod def init_from_file(file_path): - ret = Config() if False == os.path.exists(file_path): raise Exception('config file not found: ' + file_path) + # apply defaults + cfg = {} + cfg['access_control'] = True + cfg['config_dir'] = os.path.abspath(os.path.dirname(file_path)) # bypass prop restriction + cfg['development_mode'] = False + cfg['listen_address'] = '127.0.0.1' + cfg['listen_port'] = 8080 + cfg['root_path'] = os.getcwd() + cfg['static_url_path'] = '/static' + + # Flask keys + cfg['SECRET_KEY'] = '' + with open(file_path, 'r') as f: - cfg = json.loads(f.read()) + for line in f: + if re.match('(^#)|(\s+$)', line): + continue - # - # TODO: config option documentation - # - # htpasswd_path - # listen_address - # listen_port - # neo4j_url - # root_path + kv_arr = line.split('=') + if 2 != len(kv_arr): + raise Exception('failed to parse config line: ' + line) - for k, v in cfg.items(): - ret.__setattr__(k, v) + k, v = map(str.strip, kv_arr) - ret.__setattr__('config_dir', os.path.dirname(file_path)) # bypass prop restriction + if None != cfg.get(k): + # apply type conversion based on default value type + type_f = type(cfg[k]) + if bool == type_f: + v = v in ("True", "true") # workaround bool('false') = True + else: + v = type_f(v) + + # [!] we can't use k.lower() as we are loading Flask configuration + # keys which are expected to be capitalized + cfg[k] = v + + ret = Config() + ret.__dict__ = cfg # allows setting of @property attributes return ret + def __str__(self): + return '\n'.join('%s: %s' % (k, v) for k, v in self.__dict__.items()) + @property def db_base_url(self): return self.neo4j_url -- cgit v1.3.1 From f103eae07ae8a61845899879445d4bfadbb0e10d Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sat, 6 Dec 2014 21:23:20 +0200 Subject: mv FlaskExt --- src-py/rhizi_api.py | 13 ------------- src-py/rhizi_server.py | 26 +++++++++++++++++++++++--- 2 files changed, 23 insertions(+), 16 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 95822be2..2ba25ad8 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -19,19 +19,6 @@ from datetime import datetime log = logging.getLogger('rhizi') -class FlaskExt(Flask): - """ - Flask server customization - """ - - def make_default_options_response(self): - # sup = super(Flask, self) - ret = Flask.make_default_options_response(self) - ret.headers['Access-Control-Allow-Origin'] = '*' - ret.headers['Access-Control-Allow-Headers'] = "Origin, Content-Type, Accept, Authorization" - return ret - - # injected: DB controller db_ctl = None diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index 024e7246..58d4ee68 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -93,10 +93,30 @@ class Config(object): def secret_key(self): return self.SECRET_KEY -class RhiziServer(object): - pass +class FlaskExt(Flask): + """ + Flask server customization + """ + + def __init__(self, import_name, *args, **kwargs): + """ + reserved for future use + """ + super(FlaskExt, self).__init__(import_name, *args, **kwargs) -def init_logging(): + def before_request(self, *args, **kwargs): + # TODO impl + pass + + def make_default_options_response(self): + ret = Flask.make_default_options_response(self) + + ret.headers['Access-Control-Allow-Origin'] = 'http://rhizi.net' + ret.headers['Access-Control-Allow-Headers'] = "Accept, Authorization, Content-Type, Origin" + ret.headers['Access-Control-Allow-Credentials'] = 'true' + + # ret.headers['Access-Control-Allow-Methods'] = ', '.join(m_list) + return ret log = logging.getLogger('rhizi') log.setLevel(logging.DEBUG) -- cgit v1.3.1 From 79e4240cccf41de79be6bdd99aa9f0f4f68b04be Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sat, 6 Dec 2014 21:24:13 +0200 Subject: init_rest_api: dev_mode__resend_from_static() - see doc --- src-py/rhizi_server.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index 58d4ee68..d93d285e 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -127,7 +127,7 @@ class FlaskExt(Flask): log.addHandler(log_handler_f) return log -def init_rest_api(flask_webapp): +def init_rest_api(cfg, flask_webapp): """ map REST API calls """ @@ -135,6 +135,22 @@ def init_rest_api(flask_webapp): def rest_entry(path, f, flask_args={'methods': ['POST']}): return (path, f, flask_args) + def dev_mode__resend_from_static(static_url): + """ + redirect broken-on-local-deploy links: + - /src -> '': handle root based files, eg. app.js + - /res, /lib -> res, lib + """ + static_folder = flask.current_app.static_folder + + static_path = request.path + if static_path.startswith('/src'): + # TODO: clean - /src/... links should not exist + static_path = static_path.replace('/src', '') + if static_path.startswith('/'): # convert to relative path + static_path = static_path[1:] + return send_from_directory(static_folder, static_path) + def login_decorator(f): """ [!] security boundary: asserd logged-in user before executing REST api call @@ -163,6 +179,15 @@ def init_rest_api(flask_webapp): rest_entry('/monitor/server-info', rhizi_api.monitor__server_info), ] + if cfg.development_mode: + dev_path_set = ['/src', '/res', '/lib'] + rest_dev_entry_set = [] + for dev_path in dev_path_set: + rest_dev_entry_set.append(rest_entry(dev_path + '/', + dev_mode__resend_from_static, + {'methods': ['GET']})) + rest_entry_set += rest_dev_entry_set + for re_entry in rest_entry_set: rest_path, f, flask_args = re_entry -- cgit v1.3.1 From 1c142b35db054df6aca833480b74a7ec2daeb34e Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sat, 6 Dec 2014 21:25:14 +0200 Subject: cfg.access_control: provide access control configuration switch --- src-py/rhizi_server.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index d93d285e..9b0b479c 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -188,10 +188,13 @@ def init_rest_api(cfg, flask_webapp): {'methods': ['GET']})) rest_entry_set += rest_dev_entry_set + if False == cfg.access_control: + log.warn('access control disabled, public access set on all URLs') + for re_entry in rest_entry_set: rest_path, f, flask_args = re_entry - if '/login' != rest_path: + if cfg.access_control and '/login' != rest_path: # currently require login on all but /login paths f = login_decorator(f) -- cgit v1.3.1 From 6c57c73430d7599ba045db4b518efa5a3c3e3641 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sat, 6 Dec 2014 21:25:52 +0200 Subject: misc --- src-py/rhizi_server.py | 31 ++++++++++++++++++++++--------- src-py/rhizi_server_fcgi.py | 7 ++++--- 2 files changed, 26 insertions(+), 12 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index 9b0b479c..f7b3dbb3 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -10,10 +10,15 @@ import db_controller as dbc import rhizi_api import flask import crypt_util +import re +from flask import Flask from flask import session from flask import redirect -from flask import url_for +from flask import request +from flask import send_from_directory + +from functools import wraps class Config(object): """ @@ -118,10 +123,14 @@ class FlaskExt(Flask): # ret.headers['Access-Control-Allow-Methods'] = ', '.join(m_list) return ret +def init_log(cfg): + """ + init log file, location derived from configuration + """ log = logging.getLogger('rhizi') log.setLevel(logging.DEBUG) log_handler_c = logging.StreamHandler() - log_handler_f = logging.FileHandler('/var/log/rhizi/rhizi-server.log') + log_handler_f = logging.FileHandler(cfg.log_path) log.addHandler(log_handler_c) log.addHandler(log_handler_f) @@ -206,12 +215,12 @@ def init_rest_api(cfg, flask_webapp): def init_webapp(cfg): root_path = cfg.root_path - webapp = rhizi_api.FlaskExt(__name__, - static_folder='static', - template_folder=os.path.join(root_path, 'templates'), - static_url_path='') + webapp = FlaskExt(__name__, + static_folder='static', + template_folder=os.path.join(root_path, 'templates'), + static_url_path=cfg.static_url_path) webapp.config.from_object(cfg) - webapp.root_path = root_path # for some reason calling config.from_pyfile() + webapp.root_path = root_path # for some reason calling config.from_xxx() does not have effect db_ctl = dbc.DB_Controller(cfg) rhizi_api.db_ctl = db_ctl @@ -232,12 +241,16 @@ if __name__ == "__main__": p.add_argument('--init-htpasswd-db', help='init login htpasswd db', action='store_const', const=True) args = p.parse_args() - log = init_logging() cfg = init_config(args.config_dir) + log = init_log(cfg) + log.debug('loaded configuration:\n%s' % cfg) + if args.init_htpasswd_db: + init_pw_db(cfg) + exit(0) webapp = init_webapp(cfg) - init_rest_api(webapp) + init_rest_api(cfg, webapp) log.info('launching webapp via Flusk development server') webapp.run(host=cfg.listen_address, diff --git a/src-py/rhizi_server_fcgi.py b/src-py/rhizi_server_fcgi.py index bda8d91c..f0b9a04b 100755 --- a/src-py/rhizi_server_fcgi.py +++ b/src-py/rhizi_server_fcgi.py @@ -13,11 +13,12 @@ cgitb.enable() if __name__ == '__main__': cfg_dir = '/etc/rhizi' - cfg = rhizi_server.Config.init_from_file(os.path.join(cfg_dir, 'rhizi-server.conf')) - log = rhizi_server.init_logging() + cfg = rhizi_server.init_config(os.path.join(cfg_dir, 'rhizi-server.conf')) + log = rhizi_server.init_log() + webapp = rhizi_server.init_webapp(cfg) - rhizi_server.init_rest_api(webapp) + rhizi_server.init_rest_api(cfg, webapp) log.info('launching webapp via flup.server.fcgi.WSGIServer') -- cgit v1.3.1 From 06e57b020ae3c91ddb7d4faefce7b17f561170ab Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sat, 6 Dec 2014 21:32:54 +0200 Subject: /index: safe template var filling --- src-py/rhizi_api.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 2ba25ad8..829bb6c7 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -6,11 +6,19 @@ import db_controller as dbc import json import logging import traceback -from flask import jsonify +import crypt_util +import flask +from flask import jsonify from flask import Flask from flask import request from flask import make_response +from flask import session +from flask import redirect +from flask import escape +from flask import url_for +from flask import render_template +from flask import send_from_directory from model.graph import Topo_Diff from model.graph import Attr_Diff @@ -180,7 +188,7 @@ def monitor__server_info(): "

" def index(): - username = escape(session['username']) + username = escape(session.get('username')) return render_template('index.html', username=username) def login(): @@ -213,3 +221,4 @@ def logout(): u = session.pop('username', None) log.debug('logout: success: user: %s' % (u)) return redirect(url_for('login')) + -- cgit v1.3.1 From f38afeef4e0276e404be9189634b2914ff28e56c Mon Sep 17 00:00:00 2001 From: Alon Levy Date: Sat, 6 Dec 2014 09:20:14 +0200 Subject: fix typo s/flusk/flask/ --- src-py/rhizi_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index f7b3dbb3..621868cd 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -252,7 +252,7 @@ if __name__ == "__main__": webapp = init_webapp(cfg) init_rest_api(cfg, webapp) - log.info('launching webapp via Flusk development server') + log.info('launching webapp via Flask development server') webapp.run(host=cfg.listen_address, port=cfg.listen_port) -- cgit v1.3.1 From 725f5a620ea241f7bca46b5e5f32786634a8e97c Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 8 Dec 2014 22:33:26 +0200 Subject: DBO_rm_node_set: use query param id_set --- src-py/db_controller.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 8800222a..66bc5a47 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -340,18 +340,19 @@ class DBO_rm_node_set(DB_op): if rm_links: q_arr = ['match (n)', - 'where n.id in ' + str(id_set), + 'where n.id in {id_set}', 'optional match (n)-[r]-()', 'delete n,r' ] else: q_arr = ['match (n)', - 'where n.id in ' + str(id_set), + 'where n.id in {id_set}', 'delete n' ] q = ' '.join(q_arr) # TODO: use id param upon neo4j support: q_params = {'id_set': id_set} - self.add_statement(q) + q_params = {'id_set': id_set} + self.add_statement(q, q_params) class DBO_rz_clone(DB_op): def __init__(self, filter_label=None, limit=128): -- cgit v1.3.1 From cfe10b93a655d433d195440cfd1bf4a239a5da08 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 8 Dec 2014 22:34:02 +0200 Subject: Topo_Diff misc --- src-py/model/graph.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'src-py') diff --git a/src-py/model/graph.py b/src-py/model/graph.py index bc642a69..27f168ed 100644 --- a/src-py/model/graph.py +++ b/src-py/model/graph.py @@ -18,7 +18,7 @@ class Attr_Diff(dict): 'attr_remove': []} self[n_id] = ret return ret - + def add_node_attr_write(self, n_id, attr_name, attr_val): n_attr_diff = self.get(n_id) if None == n_attr_diff: @@ -35,16 +35,19 @@ class Topo_Diff(object): """ Represents a change to the graph topology """ - def __init__(self, link_set_rm=[], - node_set_rm=[], - node_set_add=[], - link_set_add=[]): + node_set_rm=[], + node_set_add=[], + link_set_add=[]): + self.link_set_rm = link_set_rm self.node_set_rm = node_set_rm self.node_set_add = node_set_add self.link_set_add = link_set_add + def __str__(self): + return __name__ + ': ' + ', '.join('%s: %d items' % (k, len(v)) for k, v in self.__dict__.items()) + def check_validity(self, topo_diff_dict): """ Topo_Diff may represent invalid operations, eg. adding a link while @@ -59,10 +62,10 @@ class Topo_Diff(object): """ ret = Topo_Diff() - # merge keys - this allows obj constructor argument ommision, - # such as when constructing from POST JSON data + # merge keys - this allows constructor argument omission (link_set_rm, + # node_set_rm, etc.) such as when constructing from POST JSON data for k, _ in ret.__dict__.items(): - v = topo_diff_dict.get(k) + v = topo_diff_dict.get(k) if None != v: ret.__dict__[k] = v return ret -- cgit v1.3.1 From 89f4e817f13b4359ad8c3ca8fa37fdece2b3aa1d Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 8 Dec 2014 22:35:07 +0200 Subject: python 3: switch from basestring to six.string_types --- src-py/neo4j_util.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index ff0fca96..6c46008c 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -3,6 +3,7 @@ """ import json +import six from six.moves.urllib import request import six.moves.urllib_error as urllib_error import model @@ -98,7 +99,7 @@ def statement_to_REST_form(query, parameters={}): """ turn cypher query to neo4j json API format """ - assert isinstance(query, basestring) + assert isinstance(query, six.string_types) if isinstance(parameters, list): for v in parameters: assert isinstance(v, dict) @@ -235,7 +236,7 @@ def __type_check_link(link): def __type_check_link_or_node_map(x_map): for k, v in x_map.iteritems(): # do some type sanity checking - assert isinstance(k, basestring) + assert isinstance(k, six.string_types) assert isinstance(v, list) def __type_check_filter_attr_map(filter_attr_map): @@ -244,5 +245,5 @@ def __type_check_filter_attr_map(filter_attr_map): """ assert isinstance(filter_attr_map, dict) for k, v in filter_attr_map.items(): - assert isinstance(k, basestring) + assert isinstance(k, six.string_types) assert isinstance(v, list) -- cgit v1.3.1 From 5dc9b4a0f4c6e36fbb72b5f3365ff9100cb1359f Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 8 Dec 2014 22:36:28 +0200 Subject: use '__label_set' as default type meta_attr (only single label support for now) --- src-py/neo4j_util.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index 6c46008c..d9fb1c67 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -207,7 +207,7 @@ def gen_query_create_from_link_map(link_map, input_to_DB_property_map=lambda _: return ret -def meta_attr_list_to_meta_attr_map(e_set, meta_attr='__type'): +def meta_attr_list_to_meta_attr_map(e_set, meta_attr='__label_set'): """ convert a list of maps each containing a meta_attr key into a meta_attr-mapped collection of lists with the meta_attr removed - eg: @@ -217,14 +217,15 @@ def meta_attr_list_to_meta_attr_map(e_set, meta_attr='__type'): """ ret = {} for v in e_set: - assert None != v['__type'] # assert type meta-attr is present + assert None != v[meta_attr], 'missing type meta-attribute' + assert 1 == len(v[meta_attr]), 'only single-type mapping currently suppoerted' - v_type = v['__type'] + v_type = v[meta_attr][0] if None == ret.get(v_type): # init type list if necessary ret[v_type] = [] v_no_meta = v.copy() - del v_no_meta['__type'] + del v_no_meta[meta_attr] ret[v_type].append(v_no_meta) -- cgit v1.3.1 From adaa8509398f18ce91db74d0a78f898bf9ae1a0d Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 8 Dec 2014 22:38:36 +0200 Subject: gen_query_create_from_node_map(): return n.id, assert id present --- src-py/neo4j_util.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index d9fb1c67..cc3d53d8 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -86,7 +86,7 @@ def post(url, data): req.add_header('Accept', 'application/json; charset=UTF-8') req.add_header('Content-Type', 'application/json') - req.add_header('X-Stream', 'true') # enable neo4j JSON streaming + req.add_header('X-Stream', 'true') # enable neo4j JSON streaming try: ret = request.urlopen(req, post_data_json) @@ -166,9 +166,16 @@ def gen_query_create_from_node_map(node_map, input_to_DB_property_map=lambda _: ret = [] for n_type, n_set in node_map.items(): - q = cfmt("create (n:{n_type} {node_attr}) return id(n)", n_type=n_type) + q_arr = ['create (n:%s {node_attr})' % (n_type), + 'return n.id' + ] + + q = ' '.join(q_arr) q_params_set = [] for n_prop_set in n_set: + + assert None != n_prop_set['id'], 'node create query: node id attribute not set' + q_params = input_to_DB_property_map(n_prop_set) q_params_set.append(q_params) ret.append((q, {'node_attr': q_params_set})) -- cgit v1.3.1 From 56c56c9de79c72c2b82ed044445d2e86db75bd58 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 8 Dec 2014 22:39:49 +0200 Subject: sanitize_input__X(): pre-DB data insertion boundary --- src-py/rhizi_api.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 829bb6c7..461f1e03 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -33,6 +33,24 @@ db_ctl = None def __sanitize_input(*args, **kw_args): pass +def sanitize_input__node(n): + """ + provide a control point as to which node fields are persisted + """ + assert None != n['id'], 'invalid input: node: missing id' + +def sanitize_input__link(l): + """ + provide a control point as to which node fields are persisted + """ + pass + +def sanitize_input__topo_diff(topo_diff): + for n in topo_diff.node_set_add: + sanitize_input__node(n) + for l in topo_diff.link_set_add: + sanitize_input__link(l) + def __response_wrap(data=None, error=None): """ wrap response data/errors as dict - this should always be used when returning @@ -132,6 +150,8 @@ def diff_commit_set(): diff_set_dict = request.get_json()['diff_set'] topo_diff_dict = diff_set_dict['__diff_set_topo'][0] topo_diff = Topo_Diff.from_dict(topo_diff_dict) + + sanitize_input__topo_diff(topo_diff) return topo_diff; topo_diff = sanitize_input(request) @@ -145,6 +165,8 @@ def diff_commit_topo(): def sanitize_input(req): topo_diff_dict = request.get_json()['topo_diff'] topo_diff = Topo_Diff.from_dict(topo_diff_dict) + + sanitize_input__topo_diff(topo_diff) return topo_diff; topo_diff = sanitize_input(request) -- cgit v1.3.1 From 3ed7eae2d984493e1b295f853780556a0d4a93e4 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 8 Dec 2014 22:40:56 +0200 Subject: rename diff_commit__X according to diff type --- src-py/rhizi_api.py | 8 ++++---- src-py/rhizi_server.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 461f1e03..8d787a5c 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -142,7 +142,7 @@ def rz_clone(): op = dbc.DBO_rz_clone() return __common_exec(op) -def diff_commit_set(): +def diff_commit__set(): """ commit a diff set """ @@ -158,7 +158,7 @@ def diff_commit_set(): op = dbc.DBO_topo_diff_commit(topo_diff) return __common_exec(op) -def diff_commit_topo(): +def diff_commit__topo(): """ commit a graph topology diff """ @@ -173,7 +173,7 @@ def diff_commit_topo(): op = dbc.DBO_topo_diff_commit(topo_diff) return __common_exec(op) -def diff_commit_attr(): +def diff_commit__attr(): """ commit a graph attribute diff """ @@ -183,7 +183,7 @@ def diff_commit_attr(): op = dbc.DBO_attr_diff_commit(attr_diff) return __common_exec(op) -def diff_commit_vis(): +def diff_commit__vis(): pass def add_node_set(): diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index 621868cd..4f0c7b52 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -175,10 +175,10 @@ def init_rest_api(cfg, flask_webapp): rest_entry_set = [ rest_entry('/add/node-set' , rhizi_api.add_node_set), rest_entry('/graph/clone', rhizi_api.rz_clone), - rest_entry('/graph/diff-commit-set', rhizi_api.diff_commit_set), - rest_entry('/graph/diff-commit-topo', rhizi_api.diff_commit_topo), - rest_entry('/graph/diff-commit-attr', rhizi_api.diff_commit_attr), - rest_entry('/graph/diff-commit-vis', rhizi_api.diff_commit_vis), + rest_entry('/graph/diff-commit-set', rhizi_api.diff_commit__set), + rest_entry('/graph/diff-commit-topo', rhizi_api.diff_commit__topo), + rest_entry('/graph/diff-commit-attr', rhizi_api.diff_commit__attr), + rest_entry('/graph/diff-commit-vis', rhizi_api.diff_commit__vis), rest_entry('/index', rhizi_api.index, {'methods': ['GET']}), rest_entry('/load/node-set-by-id', rhizi_api.load_node_set_by_id_attr), rest_entry('/load/link-set/by_link_ptr_set', rhizi_api.load_link_set_by_link_ptr_set), -- cgit v1.3.1 From 1391f30e366df1c14487f5c21f037258a1e219bd Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 10 Dec 2014 15:06:43 +0200 Subject: DBO_rm_link_set --- src-py/db_controller.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 66bc5a47..00043d04 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -130,13 +130,9 @@ class DBO_topo_diff_commit(DB_composed_op): def __init__(self, topo_diff): super(DBO_topo_diff_commit, self).__init__() - # TODO rm link set - # TODO rm node set - assert 0 == len(topo_diff.link_set_rm), 'unsupported' - n_add_map = db_util.meta_attr_list_to_meta_attr_map(topo_diff.node_set_add) l_add_map = db_util.meta_attr_list_to_meta_attr_map(topo_diff.link_set_add) - l_rm_set = [] + l_rm_set = topo_diff.link_set_rm n_rm_set = topo_diff.node_set_rm # @@ -151,7 +147,8 @@ class DBO_topo_diff_commit(DB_composed_op): self.add_sub_op(op) if len(l_rm_set) > 0: - pass + op = DBO_rm_link_set(l_rm_set) + self.add_sub_op(op) if len(n_rm_set) > 0: op = DBO_rm_node_set(n_rm_set) @@ -351,6 +348,25 @@ class DBO_rm_node_set(DB_op): ] q = ' '.join(q_arr) # TODO: use id param upon neo4j support: q_params = {'id_set': id_set} +class DBO_rm_link_set(DB_op): + def __init__(self, id_set): + """ + remove link set + + [!] when removing as a result of node removal, use DBO_rm_node_set + along with rm_links=True + """ + assert len(id_set) > 0, __name__ + ': empty id set' + + super(DBO_rm_link_set, self).__init__() + + q_arr = ['match ()-[r]->()', + 'where r.id in {id_set}', + 'delete r', + 'return {id_set}' + ] + + q = ' '.join(q_arr) q_params = {'id_set': id_set} self.add_statement(q, q_params) -- cgit v1.3.1 From b6cd93638ee183fb120778ec55fca9f15aa9804b Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 10 Dec 2014 15:13:17 +0200 Subject: DBO_rm_node_set: return id_set --- src-py/db_controller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 00043d04..c5798aa5 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -344,7 +344,8 @@ class DBO_rm_node_set(DB_op): else: q_arr = ['match (n)', 'where n.id in {id_set}', - 'delete n' + 'delete n', + 'return {id_set}' ] q = ' '.join(q_arr) # TODO: use id param upon neo4j support: q_params = {'id_set': id_set} -- cgit v1.3.1 From 4aa6f7dc89509589fa7641ad95e3abc9ba24d528 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 10 Dec 2014 15:13:51 +0200 Subject: assert len(id_set) > 0 --- src-py/db_controller.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index c5798aa5..1e63b0bc 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -333,6 +333,8 @@ class DBO_rm_node_set(DB_op): """ remove node set """ + assert len(id_set) > 0, __name__ + ': empty id set' + super(DBO_rm_node_set, self).__init__() if rm_links: @@ -348,7 +350,10 @@ class DBO_rm_node_set(DB_op): 'return {id_set}' ] - q = ' '.join(q_arr) # TODO: use id param upon neo4j support: q_params = {'id_set': id_set} + q = ' '.join(q_arr) + q_params = {'id_set': id_set} + self.add_statement(q, q_params) + class DBO_rm_link_set(DB_op): def __init__(self, id_set): """ -- cgit v1.3.1 From 18d96ad7f7b5abd58ccfd485de5d8c20ec3f395b Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 10 Dec 2014 15:15:16 +0200 Subject: switch to q_arr format --- src-py/db_controller.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 1e63b0bc..460686a5 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -321,7 +321,10 @@ class DBO_match_link_id_set(DB_op): """ super(DBO_match_link_id_set, self).__init__() - q = "match ()-[r{filter_label} {filter_attr}]->() return id(r)" + q_arr = ['match ()-[r{filter_label} {filter_attr}]->()', + 'return id(r)' + ] + q = ' '.join(q_arr) q = cfmt(q, filter_label="" if not filter_label else ":" + filter_label) q = cfmt(q, filter_attr=db_util.gen_clause_attr_filter_from_filter_attr_map(filter_attr_map)) q_params = {k: v[0] for (k, v) in filter_attr_map.items()} # pass on only first value from each value set -- cgit v1.3.1 From 404828c55672cc84f7ded569d8cc42b5afa140ff Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 10 Dec 2014 15:17:07 +0200 Subject: Tokp_Diff __str__() --- src-py/model/graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/model/graph.py b/src-py/model/graph.py index 27f168ed..86a195d4 100644 --- a/src-py/model/graph.py +++ b/src-py/model/graph.py @@ -46,7 +46,7 @@ class Topo_Diff(object): self.link_set_add = link_set_add def __str__(self): - return __name__ + ': ' + ', '.join('%s: %d items' % (k, len(v)) for k, v in self.__dict__.items()) + return __name__ + ': ' + ', '.join('%s: %s' % (k, v) for k, v in self.__dict__.items()) def check_validity(self, topo_diff_dict): """ -- cgit v1.3.1 From 0950293605eecff2de9cdadccf1def2d7d9b73bc Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 10 Dec 2014 15:16:06 +0200 Subject: distinguish __src (obj) vs. __src_id (str) --- src-py/db_controller.py | 4 ++-- src-py/model/model.py | 25 ++++++++++++++++--------- src-py/neo4j_util.py | 16 ++++++++-------- src-py/rhizi_api.py | 4 ++-- 4 files changed, 28 insertions(+), 21 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 460686a5..2d4fe8ce 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -426,8 +426,8 @@ class DBO_rz_clone(DB_op): continue l = l_tuple[1] - l['__src'] = n['id'] - l['__dst'] = l_tuple[0] + l['__src_id'] = n['id'] + l['__dst_id'] = l_tuple[0] l['__label_set'] = [l_tuple[2]] # box single value returned by type() ret_l_set.append(l) diff --git a/src-py/model/model.py b/src-py/model/model.py index 701074ab..33cf9e6a 100644 --- a/src-py/model/model.py +++ b/src-py/model/model.py @@ -3,24 +3,31 @@ class Link(): documentation anchor - this class currently carries no implementation and only acts as a documentation anchor - link['__src'] - meta attribute for link source - link['__dst'] - meta attribute for link destination + link['__src'] - meta attribute for link source object + link['__dst'] - meta attribute for link destination object """ - + + def __init__(self, src=None, dst=None): + assert False, 'currently unused' + class Link_Ptr(dict): + """ + link['__src_id'] - meta attribute for link source id + link['__dst_id'] - meta attribute for link destination id + """ def __init__(self, src_id=None, dst_id=None): assert None != src_id or None != dst_id - - self['__src'] = src_id - self['__dst'] = dst_id - + + self['__src_id'] = src_id + self['__dst_id'] = dst_id + @property def src_id(self): - return self['__src'] + return self['__src_id'] @property def dst_id(self): - return self['__dst'] + return self['__dst_id'] @staticmethod def link_ptr(src_id=None, dst_id=None): diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index cc3d53d8..a203207f 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -199,16 +199,16 @@ def gen_query_create_from_link_map(link_map, input_to_DB_property_map=lambda _: for link in l_set: __type_check_link(link) - n_src = link['__src'] - n_dst = link['__dst'] + src_id = link['__src_id'] + dst_id = link['__dst_id'] # TODO: use object based link representation prop_dict = link.copy() - del prop_dict['__dst'] - del prop_dict['__src'] + del prop_dict['__dst_id'] + del prop_dict['__src_id'] - q_params = {'src': { 'id': n_src} , - 'dst': { 'id': n_dst} , + q_params = {'src': { 'id': src_id} , + 'dst': { 'id': dst_id} , 'link_attr' : input_to_DB_property_map(prop_dict)} ret.append((q, q_params)) @@ -239,8 +239,8 @@ def meta_attr_list_to_meta_attr_map(e_set, meta_attr='__label_set'): return ret def __type_check_link(link): - assert link.has_key('__src') - assert link.has_key('__dst') + assert link.has_key('__src_id') + assert link.has_key('__dst_id') def __type_check_link_or_node_map(x_map): for k, v in x_map.iteritems(): # do some type sanity checking diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 8d787a5c..ef904a37 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -127,8 +127,8 @@ def load_link_set_by_link_ptr_set(): l_ptr_set = [] for lptr_dict in l_ptr_set_raw: - src_id = lptr_dict.get('__src') - dst_id = lptr_dict.get('__dst') + src_id = lptr_dict.get('__src_id') + dst_id = lptr_dict.get('__dst_id') l_ptr_set += [Link.Link_Ptr(src_id=src_id, dst_id=dst_id) ] return l_ptr_set -- cgit v1.3.1 From 22bcff47e68ca662360017f4829e35e02b4a33c6 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Wed, 10 Dec 2014 15:20:57 +0200 Subject: sanitize_input__link --- src-py/rhizi_api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index ef904a37..0c1b68b5 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -43,7 +43,9 @@ def sanitize_input__link(l): """ provide a control point as to which node fields are persisted """ - pass + assert None != l['id'], 'invalid input: link: missing id' + assert None != l['__src_id'], 'invalid input: link: missing src id' + assert None != l['__dst_id'], 'invalid input: link: missing dst id' def sanitize_input__topo_diff(topo_diff): for n in topo_diff.node_set_add: -- cgit v1.3.1 From a6cd66befa7f9b4041f7b06ecee408dd02617fb2 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 14 Dec 2014 22:41:51 +0200 Subject: complete 'return {id_set}' move --- src-py/db_controller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 2d4fe8ce..2ca7b551 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -344,7 +344,8 @@ class DBO_rm_node_set(DB_op): q_arr = ['match (n)', 'where n.id in {id_set}', 'optional match (n)-[r]-()', - 'delete n,r' + 'delete n,r', + 'return {id_set}' ] else: q_arr = ['match (n)', -- cgit v1.3.1 From 43fc57a01028b374234042ab59fdbdd48157a88e Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 14 Dec 2014 22:45:27 +0200 Subject: Attr_Diff: __type_node, __type_link separation --- src-py/db_controller.py | 9 ++++++--- src-py/model/graph.py | 49 ++++++++++++++++++++++++++++++++++++++----------- src-py/rhizi_api.py | 9 +++++++-- 3 files changed, 51 insertions(+), 16 deletions(-) (limited to 'src-py') diff --git a/src-py/db_controller.py b/src-py/db_controller.py index 2ca7b551..c17fd0cd 100644 --- a/src-py/db_controller.py +++ b/src-py/db_controller.py @@ -161,10 +161,10 @@ class DBO_attr_diff_commit(DB_op): def __init__(self, attr_diff): super(DBO_attr_diff_commit, self).__init__() - for id_attr, n_attr_diff in attr_diff.items(): + for id_attr, n_attr_diff in attr_diff.type__node.items(): # TODO parameterize multiple attr removal - r_attr_set = n_attr_diff['attr_remove'] - w_attr_set = n_attr_diff['attr_write'] + r_attr_set = n_attr_diff['__attr_remove'] + w_attr_set = n_attr_diff['__attr_write'] assert len(r_attr_set) > 0 or len(w_attr_set) > 0 @@ -184,6 +184,9 @@ class DBO_attr_diff_commit(DB_op): q = " ".join(q_arr) self.add_statement(q, q_param_set) + for id_attr, n_attr_diff in attr_diff.type__link.items(): + pass # TODO: handl link attr_diffs + def process_result_set(self): ret = {} for _, _, r_set in self: diff --git a/src-py/model/graph.py b/src-py/model/graph.py index 86a195d4..b40b805a 100644 --- a/src-py/model/graph.py +++ b/src-py/model/graph.py @@ -5,31 +5,58 @@ class Attr_Diff(dict): changed or removed Example: - attr_diff = {n_id: {'attr_write': {'attr_0': 0, - 'attr_1': 'a'}, - 'attr_remove': ['attr_2'] } + attr_diff = {'__type_node' : {n_id: {'__attr_write': {'attr_0': 0, + 'attr_1': 'a'}, + '__attr_remove': ['attr_2'] }} + '__type_link' : {l_id: ... } } """ def __init__(self): - pass + self['__type_node'] = {} + self['__type_link'] = {} def init_node_attr_diff(self, n_id): - ret = {'attr_write': {}, - 'attr_remove': []} - self[n_id] = ret + ret = {'__attr_write': {}, + '__attr_remove': []} + self['__type_node'][n_id] = ret + return ret + + @staticmethod + def from_json_dict(json_dict): + ret = Attr_Diff() + for obj_type in ret.keys(): + obj_ad_set = json_dict.get(obj_type) + if None != obj_ad_set: + for o_id, ad in obj_ad_set.items(): + if None != ad.get('__attr_write'): + for k, v in ad['__attr_write'].items(): + ret.add_node_attr_write(o_id, k, v) + if None != ad.get('__attr_remove'): + for k in ad['__attr_remove']: + ret.add_node_attr_rm(o_id, k) return ret + @property + def type__node(self): + return self['__type_node'] + + @property + def type__link(self): + return self['__type_link'] + def add_node_attr_write(self, n_id, attr_name, attr_val): - n_attr_diff = self.get(n_id) + + + n_attr_diff = self['__type_node'].get(n_id) if None == n_attr_diff: n_attr_diff = self.init_node_attr_diff(n_id) - n_attr_diff['attr_write'][attr_name] = attr_val + n_attr_diff['__attr_write'][attr_name] = attr_val def add_node_attr_rm(self, n_id, attr_name): - n_attr_diff = self.get(n_id) + n_attr_diff = self['__type_node'].get(n_id) if None == n_attr_diff: n_attr_diff = self.init_node_attr_diff(n_id) - n_attr_diff['attr_remove'].append(attr_name) + n_attr_diff['__attr_remove'].append(attr_name) class Topo_Diff(object): """ diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 0c1b68b5..b89e8c18 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -179,9 +179,14 @@ def diff_commit__attr(): """ commit a graph attribute diff """ - attr_diff = request.get_json()['attr_diff'] - __sanitize_input(attr_diff) + def sanitize_input(req): + attr_diff_dict = request.get_json()['attr_diff'] + attr_diff = Attr_Diff.from_json_dict(attr_diff_dict) + + sanitize_input__attr_diff(attr_diff) + return attr_diff; + attr_diff = sanitize_input(request) op = dbc.DBO_attr_diff_commit(attr_diff) return __common_exec(op) -- cgit v1.3.1 From 2595745df5801e416166d9be1cdc360a226b4476 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 14 Dec 2014 22:47:20 +0200 Subject: logging --- src-py/db_driver.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'src-py') diff --git a/src-py/db_driver.py b/src-py/db_driver.py index 11eace2c..0532e512 100644 --- a/src-py/db_driver.py +++ b/src-py/db_driver.py @@ -7,7 +7,15 @@ import neo4j_util as db_util log = logging.getLogger('rhizi') class DB_Driver_Base(): - pass + + def log_committed_queries(self, statement_set): + for sp_dict in statement_set['statements']: + if None != sp_dict['parameters']: + msg = '\tq: {0}\n\tp: {1}'.format(sp_dict['statement'], + sp_dict['parameters']) + else: + msg = '\tq: {0}'.format(sp_dict['statement']) + log.debug(msg) class DB_Driver_Embedded(DB_Driver_Base): def __init__(self, db_base_url): @@ -27,10 +35,6 @@ class DB_Driver_Embedded(DB_Driver_Base): def commit_tx(self, op): pass - def log_committed_queries(self, statement_set): - for sp_dict in statement_set['statements']: - log.debug('\tq: {0}'.format(sp_dict['statement'])) - class DB_Driver_REST(DB_Driver_Base): def __init__(self, db_base_url): self.tx_base_url = db_base_url + '/db/data/transaction' @@ -85,7 +89,3 @@ class DB_Driver_REST(DB_Driver_Base): except Exception as e: raise Exception('failed to commit transaction:' + e.message) - def log_committed_queries(self, statement_set): - for sp_dict in statement_set['statements']: - log.debug('\tq: {0}'.format(sp_dict['statement'])) - -- cgit v1.3.1 From 292e45dd6590ada4a5155ec4cfe9446610eaf502 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 14 Dec 2014 22:48:13 +0200 Subject: rename from_dict -> from_json_dict --- src-py/model/graph.py | 4 ++-- src-py/rhizi_api.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src-py') diff --git a/src-py/model/graph.py b/src-py/model/graph.py index b40b805a..ebe8b31e 100644 --- a/src-py/model/graph.py +++ b/src-py/model/graph.py @@ -83,7 +83,7 @@ class Topo_Diff(object): pass @staticmethod - def from_dict(topo_diff_dict): + def from_json_dict(json_dict): """ construct from dict - no node/link constructor set must be provided """ @@ -92,7 +92,7 @@ class Topo_Diff(object): # merge keys - this allows constructor argument omission (link_set_rm, # node_set_rm, etc.) such as when constructing from POST JSON data for k, _ in ret.__dict__.items(): - v = topo_diff_dict.get(k) + v = json_dict.get(k) if None != v: ret.__dict__[k] = v return ret diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index b89e8c18..228fcd66 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -151,7 +151,7 @@ def diff_commit__set(): def sanitize_input(req): diff_set_dict = request.get_json()['diff_set'] topo_diff_dict = diff_set_dict['__diff_set_topo'][0] - topo_diff = Topo_Diff.from_dict(topo_diff_dict) + topo_diff = Topo_Diff.from_json_dict(topo_diff_dict) sanitize_input__topo_diff(topo_diff) return topo_diff; @@ -166,7 +166,7 @@ def diff_commit__topo(): """ def sanitize_input(req): topo_diff_dict = request.get_json()['topo_diff'] - topo_diff = Topo_Diff.from_dict(topo_diff_dict) + topo_diff = Topo_Diff.from_json_dict(topo_diff_dict) sanitize_input__topo_diff(topo_diff) return topo_diff; -- cgit v1.3.1 From 74d413161c9e847c8ff8f02737bcb66eb71b742b Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 14 Dec 2014 23:11:33 +0200 Subject: assert ID attr_name not used in Attr_Diff --- src-py/model/graph.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src-py') diff --git a/src-py/model/graph.py b/src-py/model/graph.py index ebe8b31e..efc403d4 100644 --- a/src-py/model/graph.py +++ b/src-py/model/graph.py @@ -46,6 +46,7 @@ class Attr_Diff(dict): def add_node_attr_write(self, n_id, attr_name, attr_val): + assert 'id' != attr_name.lower(), 'Attr_Diff: attempt to write to \'id\' attribute' n_attr_diff = self['__type_node'].get(n_id) if None == n_attr_diff: -- cgit v1.3.1 From e6144b232e3ff626d654bf274025f5d55a6bca84 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 14 Dec 2014 23:12:02 +0200 Subject: link attr operations not yet supported... --- src-py/model/graph.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src-py') diff --git a/src-py/model/graph.py b/src-py/model/graph.py index efc403d4..f197921d 100644 --- a/src-py/model/graph.py +++ b/src-py/model/graph.py @@ -59,6 +59,12 @@ class Attr_Diff(dict): n_attr_diff = self.init_node_attr_diff(n_id) n_attr_diff['__attr_remove'].append(attr_name) + def add_link_attr_write(self, l_id, attr_name, attr_val): + assert False, 'unimplemented' + + def add_link_attr_rm(self, l_id, attr_name): + assert False, 'unimplemented' + class Topo_Diff(object): """ Represents a change to the graph topology -- cgit v1.3.1 From dc540c722c5146156fadd2d5db5c14dc96a996c6 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 14 Dec 2014 23:12:38 +0200 Subject: sanitize_input__attr_diff stub --- src-py/rhizi_api.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src-py') diff --git a/src-py/rhizi_api.py b/src-py/rhizi_api.py index 228fcd66..579d41e1 100644 --- a/src-py/rhizi_api.py +++ b/src-py/rhizi_api.py @@ -53,6 +53,9 @@ def sanitize_input__topo_diff(topo_diff): for l in topo_diff.link_set_add: sanitize_input__link(l) +def sanitize_input__attr_diff(attr_diff): + pass # TODO: impl + def __response_wrap(data=None, error=None): """ wrap response data/errors as dict - this should always be used when returning -- cgit v1.3.1 From 6ea834a5dba8017b9e3010aa6610f34f034cdbdd Mon Sep 17 00:00:00 2001 From: LV-426 Date: Sun, 14 Dec 2014 23:13:30 +0200 Subject: partial config validation --- src-py/rhizi_server.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src-py') diff --git a/src-py/rhizi_server.py b/src-py/rhizi_server.py index 4f0c7b52..168e2f53 100644 --- a/src-py/rhizi_server.py +++ b/src-py/rhizi_server.py @@ -77,6 +77,11 @@ class Config(object): ret = Config() ret.__dict__ = cfg # allows setting of @property attributes + + # validate config + if False == os.path.isabs(ret.root_path): + ret.root_path = os.path.abspath(ret.root_path) + return ret def __str__(self): -- cgit v1.3.1 From 2ce705db9086188897df8464ac9cb8cb6fb6ce0e Mon Sep 17 00:00:00 2001 From: LV-426 Date: Mon, 15 Dec 2014 23:38:05 +0200 Subject: gen_query_create_from_node_map(): validate label --- src-py/neo4j_util.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src-py') diff --git a/src-py/neo4j_util.py b/src-py/neo4j_util.py index a203207f..cf2f962e 100644 --- a/src-py/neo4j_util.py +++ b/src-py/neo4j_util.py @@ -165,8 +165,11 @@ def gen_query_create_from_node_map(node_map, input_to_DB_property_map=lambda _: __type_check_link_or_node_map(node_map) ret = [] - for n_type, n_set in node_map.items(): - q_arr = ['create (n:%s {node_attr})' % (n_type), + for label, n_set in node_map.items(): + + assert len(label) > 2 and label[0].isupper() and label[1:].islower(), 'malformed label: ' + label + + q_arr = ['create (n:%s {node_attr})' % (label), 'return n.id' ] -- cgit v1.3.1 From b83b14cb8fbc4159e72f2076cf5370c85e174051 Mon Sep 17 00:00:00 2001 From: LV-426 Date: Tue, 16 Dec 2014 13:00:11 +0200 Subject: mv neo4j_test_util.py --- src-py/neo4j_test_util.py | 54 ------------------------------------------ src-py_test/neo4j_test_util.py | 54 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 54 deletions(-) delete mode 100644 src-py/neo4j_test_util.py create mode 100644 src-py_test/neo4j_test_util.py (limited to 'src-py') diff --git a/src-py/neo4j_test_util.py b/src-py/neo4j_test_util.py deleted file mode 100644 index d5aa640a..00000000 --- a/src-py/neo4j_test_util.py +++ /dev/null @@ -1,54 +0,0 @@ -import uuid -import string -from random import choice -import db_controller as dbc - -def rand_id(): - return str(uuid.uuid4()) - -def rand_label(length=8): - """ - return random label - """ - char_set = string.ascii_lowercase + string.ascii_uppercase + string.digits - return ''.join([choice(string.ascii_lowercase)] + [choice(char_set) for _ in range(length - 1)]) - -def flush_db(db_ctl): - """ - complete DB flush: remove all nodes & links - """ - db_ctl.exec_cypher_query('match (n) optional match (n)-[r]-() delete n,r') - - -def gen_rand_data(db_ctl, lim_n=128, lim_r=256, prob_link_create = 0.3): - """ - generate random DB data - - @return: tuple consisting of the random node,link labels generated - """ - assert 2 <= lim_n - - n_label = rand_label() - r_label = rand_label() - q_arr = ['with 0 as _', # TODO clean: foreach triggers SyntaxException: otherwise - 'foreach (rid in range(0,%d)' % (lim_n - 1), - '|', - 'create (:%s {id:rid, n_attr_0:toInt(%d * rand())}))' % (n_label, lim_n) - ] - - q = ' '.join(q_arr) - op = dbc.DBO_cypher_query(q) - db_ctl.exec_op(op) - - q_arr = ['match (s:%s),(d:%s)' % (n_label, n_label), - 'with s,d', - 'limit %d' % (lim_r - 1), - 'where rand() < %.2f' % (prob_link_create), - 'create (s)-[:%s {l_attr_0:toInt(%d * rand())}]->(d)' % (r_label,lim_r)] - - q = ' '.join(q_arr) - op = dbc.DBO_cypher_query(q) - db_ctl.exec_op(op) - - return (n_label, r_label) - diff --git a/src-py_test/neo4j_test_util.py b/src-py_test/neo4j_test_util.py new file mode 100644 index 00000000..d5aa640a --- /dev/null +++ b/src-py_test/neo4j_test_util.py @@ -0,0 +1,54 @@ +import uuid +import string +from random import choice +import db_controller as dbc + +def rand_id(): + return str(uuid.uuid4()) + +def rand_label(length=8): + """ + return random label + """ + char_set = string.ascii_lowercase + string.ascii_uppercase + string.digits + return ''.join([choice(string.ascii_lowercase)] + [choice(char_set) for _ in range(length - 1)]) + +def flush_db(db_ctl): + """ + complete DB flush: remove all nodes & links + """ + db_ctl.exec_cypher_query('match (n) optional match (n)-[r]-() delete n,r') + + +def gen_rand_data(db_ctl, lim_n=128, lim_r=256, prob_link_create = 0.3): + """ + generate random DB data + + @return: tuple consisting of the random node,link labels generated + """ + assert 2 <= lim_n + + n_label = rand_label() + r_label = rand_label() + q_arr = ['with 0 as _', # TODO clean: foreach triggers SyntaxException: otherwise + 'foreach (rid in range(0,%d)' % (lim_n - 1), + '|', + 'create (:%s {id:rid, n_attr_0:toInt(%d * rand())}))' % (n_label, lim_n) + ] + + q = ' '.join(q_arr) + op = dbc.DBO_cypher_query(q) + db_ctl.exec_op(op) + + q_arr = ['match (s:%s),(d:%s)' % (n_label, n_label), + 'with s,d', + 'limit %d' % (lim_r - 1), + 'where rand() < %.2f' % (prob_link_create), + 'create (s)-[:%s {l_attr_0:toInt(%d * rand())}]->(d)' % (r_label,lim_r)] + + q = ' '.join(q_arr) + op = dbc.DBO_cypher_query(q) + db_ctl.exec_op(op) + + return (n_label, r_label) + -- cgit v1.3.1