Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 25 additions & 18 deletions neo4j/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def main():
parser.add_argument("-s", "--secure", action="store_true")
parser.add_argument("-v", "--verbose", action="count")
parser.add_argument("-x", "--times", type=int, default=1)
parser.add_argument("-z", "--summarize", action="store_true")
args = parser.parse_args()

if args.verbose:
Expand All @@ -102,24 +103,30 @@ def main():

driver = GraphDatabase.driver(args.url, secure=args.secure)
session = driver.session()
if session:
for _ in range(args.times):
for statement in args.statement:
try:
records = session.run(statement, parameters)
except CypherError as error:
stderr.write("%s: %s\r\n" % (error.code, error.message))
else:
if not args.quiet:
has_results = False
for i, record in enumerate(records):
has_results = True
if i == 0:
stdout.write("%s\r\n" % "\t".join(record.__keys__))
stdout.write("%s\r\n" % "\t".join(map(repr, record)))
if has_results:
stdout.write("\r\n")
session.close()
for _ in range(args.times):
for statement in args.statement:
try:
result = session.run(statement, parameters)
except CypherError as error:
stderr.write("%s: %s\r\n" % (error.code, error.message))
else:
if not args.quiet:
has_results = False
for i, record in enumerate(result):
has_results = True
if i == 0:
stdout.write("%s\r\n" % "\t".join(record.__keys__))
stdout.write("%s\r\n" % "\t".join(map(repr, record)))
if has_results:
stdout.write("\r\n")
if args.summarize:
summary = result.summarize()
stdout.write("Statement : %r\r\n" % summary.statement)
stdout.write("Parameters : %r\r\n" % summary.parameters)
stdout.write("Statement Type : %r\r\n" % summary.statement_type)
stdout.write("Statistics : %r\r\n" % summary.statistics)
stdout.write("\r\n")
session.close()


if __name__ == "__main__":
Expand Down
53 changes: 50 additions & 3 deletions neo4j/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ class which can be used to obtain `Driver` instances that are used for
from .typesystem import hydrated


STATEMENT_TYPE_READ_ONLY = "r"
STATEMENT_TYPE_READ_WRITE = "rw"
STATEMENT_TYPE_WRITE_ONLY = "w"
STATEMENT_TYPE_SCHEMA_WRITE = "sw"


Latency = namedtuple("Latency", ["overall", "network", "wait"])


Expand Down Expand Up @@ -98,11 +104,14 @@ def session(self, **config):

class Result(list):

def __init__(self, session):
self.session = session
def __init__(self, session, statement, parameters):
super(Result, self).__init__()
self.session = session
self.statement = statement
self.parameters = parameters
self.keys = None
self.complete = False
self.summary = None
self.bench_test = None

def on_header(self, metadata):
Expand All @@ -115,6 +124,8 @@ def on_record(self, values):

def on_footer(self, metadata):
self.complete = True
self.summary = ResultSummary(self.statement, self.parameters,
metadata.get("type"), metadata.get("stats"))
if self.bench_test:
self.bench_test.end_recv = perf_counter()

Expand All @@ -126,6 +137,42 @@ def consume(self):
while not self.complete:
fetch_next()

def summarize(self):
self.consume()
return self.summary


class ResultSummary(object):

def __init__(self, statement, parameters, statement_type, statistics):
self.statement = statement
self.parameters = parameters
self.statement_type = statement_type
self.statistics = StatementStatistics(statistics or {})


class StatementStatistics(object):
contains_updates = False
nodes_created = 0
nodes_deleted = 0
relationships_created = 0
relationships_deleted = 0
properties_set = 0
labels_added = 0
labels_removed = 0
indexes_added = 0
indexes_removed = 0
constraints_added = 0
constraints_removed = 0

def __init__(self, statistics):
for key, value in dict(statistics).items():
key = key.replace("-", "_")
setattr(self, key, value)

def __repr__(self):
return repr(vars(self))


class Session(object):
""" Logical session carried out over an established TCP connection.
Expand Down Expand Up @@ -162,7 +209,7 @@ def run(self, statement, parameters=None):
t = BenchTest()
t.init = perf_counter()

result = Result(self)
result = Result(self, statement, parameters)
result.bench_test = t

run_response = Response(self.connection)
Expand Down
71 changes: 40 additions & 31 deletions test/session_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,36 +89,36 @@ def test_can_run_statement_that_returns_multiple_records(self):

def test_can_use_with_to_auto_close_session(self):
with GraphDatabase.driver("bolt://localhost").session() as session:
records = session.run("RETURN 1")
assert len(records) == 1
for record in records:
result = session.run("RETURN 1")
assert len(result) == 1
for record in result:
assert record[0] == 1

def test_can_return_node(self):
with GraphDatabase.driver("bolt://localhost").session() as session:
records = session.run("MERGE (a:Person {name:'Alice'}) RETURN a")
assert len(records) == 1
for record in records:
result = session.run("MERGE (a:Person {name:'Alice'}) RETURN a")
assert len(result) == 1
for record in result:
alice = record[0]
assert isinstance(alice, Node)
assert alice.labels == {"Person"}
assert alice.properties == {"name": "Alice"}

def test_can_return_relationship(self):
with GraphDatabase.driver("bolt://localhost").session() as session:
records = session.run("MERGE ()-[r:KNOWS {since:1999}]->() RETURN r")
assert len(records) == 1
for record in records:
result = session.run("MERGE ()-[r:KNOWS {since:1999}]->() RETURN r")
assert len(result) == 1
for record in result:
rel = record[0]
assert isinstance(rel, Relationship)
assert rel.type == "KNOWS"
assert rel.properties == {"since": 1999}

def test_can_return_path(self):
with GraphDatabase.driver("bolt://localhost").session() as session:
records = session.run("MERGE p=({name:'Alice'})-[:KNOWS]->({name:'Bob'}) RETURN p")
assert len(records) == 1
for record in records:
result = session.run("MERGE p=({name:'Alice'})-[:KNOWS]->({name:'Bob'}) RETURN p")
assert len(result) == 1
for record in result:
path = record[0]
assert isinstance(path, Path)
assert path.start.properties == {"name": "Alice"}
Expand All @@ -138,9 +138,18 @@ def test_can_handle_cypher_error(self):

def test_record_equality(self):
with GraphDatabase.driver("bolt://localhost").session() as session:
records = session.run("unwind([1, 1]) AS a RETURN a")
assert records[0] == records[1]
assert records[0] != "this is not a record"
result = session.run("unwind([1, 1]) AS a RETURN a")
assert result[0] == result[1]
assert result[0] != "this is not a record"

def test_can_obtain_summary_info(self):
with GraphDatabase.driver("bolt://localhost").session() as session:
result = session.run("CREATE (n) RETURN n")
summary = result.summarize()
assert summary.statement == "CREATE (n) RETURN n"
assert summary.parameters == {}
assert summary.statement_type == "rw"
assert summary.statistics.nodes_created == 1


class TransactionTestCase(TestCase):
Expand All @@ -150,8 +159,8 @@ def test_can_commit_transaction(self):
tx = session.new_transaction()

# Create a node
records = tx.run("CREATE (a) RETURN id(a)")
node_id = records[0][0]
result = tx.run("CREATE (a) RETURN id(a)")
node_id = result[0][0]
assert isinstance(node_id, int)

# Update a property
Expand All @@ -161,18 +170,18 @@ def test_can_commit_transaction(self):
tx.commit()

# Check the property value
records = session.run("MATCH (a) WHERE id(a) = {n} "
result = session.run("MATCH (a) WHERE id(a) = {n} "
"RETURN a.foo", {"n": node_id})
foo = records[0][0]
foo = result[0][0]
assert foo == "bar"

def test_can_rollback_transaction(self):
with GraphDatabase.driver("bolt://localhost").session() as session:
tx = session.new_transaction()

# Create a node
records = tx.run("CREATE (a) RETURN id(a)")
node_id = records[0][0]
result = tx.run("CREATE (a) RETURN id(a)")
node_id = result[0][0]
assert isinstance(node_id, int)

# Update a property
Expand All @@ -182,17 +191,17 @@ def test_can_rollback_transaction(self):
tx.rollback()

# Check the property value
records = session.run("MATCH (a) WHERE id(a) = {n} "
result = session.run("MATCH (a) WHERE id(a) = {n} "
"RETURN a.foo", {"n": node_id})
assert len(records) == 0
assert len(result) == 0

def test_can_commit_transaction_using_with_block(self):
with GraphDatabase.driver("bolt://localhost").session() as session:
with session.new_transaction() as tx:

# Create a node
records = tx.run("CREATE (a) RETURN id(a)")
node_id = records[0][0]
result = tx.run("CREATE (a) RETURN id(a)")
node_id = result[0][0]
assert isinstance(node_id, int)

# Update a property
Expand All @@ -202,28 +211,28 @@ def test_can_commit_transaction_using_with_block(self):
tx.success = True

# Check the property value
records = session.run("MATCH (a) WHERE id(a) = {n} "
result = session.run("MATCH (a) WHERE id(a) = {n} "
"RETURN a.foo", {"n": node_id})
foo = records[0][0]
foo = result[0][0]
assert foo == "bar"

def test_can_rollback_transaction_using_with_block(self):
with GraphDatabase.driver("bolt://localhost").session() as session:
with session.new_transaction() as tx:

# Create a node
records = tx.run("CREATE (a) RETURN id(a)")
node_id = records[0][0]
result = tx.run("CREATE (a) RETURN id(a)")
node_id = result[0][0]
assert isinstance(node_id, int)

# Update a property
tx.run("MATCH (a) WHERE id(a) = {n} "
"SET a.foo = {foo}", {"n": node_id, "foo": "bar"})

# Check the property value
records = session.run("MATCH (a) WHERE id(a) = {n} "
result = session.run("MATCH (a) WHERE id(a) = {n} "
"RETURN a.foo", {"n": node_id})
assert len(records) == 0
assert len(result) == 0


if __name__ == "__main__":
Expand Down