diff --git a/README.rst b/README.rst index cf9b5c1f1..412941e61 100644 --- a/README.rst +++ b/README.rst @@ -28,10 +28,10 @@ Example Usage driver = GraphDatabase.driver("bolt://localhost") session = driver.session() session.run("CREATE (a:Person {name:'Bob'})") - cursor = session.run("MATCH (a:Person) RETURN a.name AS name") - while cursor.next() - print(cursor["name"]) - cursor.close() + result = session.run("MATCH (a:Person) RETURN a.name AS name") + for record in result: + print(record["name"]) + result.close() session.close() diff --git a/docs/source/index.rst b/docs/source/index.rst index 71588bdae..12fc43ed4 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -26,7 +26,7 @@ Session API .. autofunction:: neo4j.v1.record -.. autoclass:: neo4j.v1.ResultCursor +.. autoclass:: neo4j.v1.StatementResult :members: .. autoclass:: neo4j.v1.ResultSummary diff --git a/examples/test_examples.py b/examples/test_examples.py index 70d261637..d26c773e2 100644 --- a/examples/test_examples.py +++ b/examples/test_examples.py @@ -53,8 +53,8 @@ def test_minimal_working_example(self): session.run("CREATE (a:Person {name:'Arthur', title:'King'})", ) result = session.run("MATCH (a:Person) WHERE a.name = 'Arthur' RETURN a.name AS name, a.title AS title") - while result.next(): - print("%s %s" % (result["title"], result["name"])) + for record in result: + print("%s %s" % (record["title"], record["name"])) session.close() # end::minimal-example[] @@ -96,16 +96,18 @@ def test_statement(self): driver = GraphDatabase.driver("bolt://localhost", auth=auth_token) session = driver.session() # tag::statement[] - session.run("CREATE (person:Person {name: {name}})", {"name": "Arthur"}).close() + result = session.run("CREATE (person:Person {name: {name}})", {"name": "Arthur"}) # end::statement[] + result.discard() session.close() def test_statement_without_parameters(self): driver = GraphDatabase.driver("bolt://localhost", auth=auth_token) session = driver.session() # tag::statement-without-parameters[] - session.run("CREATE (person:Person {name: 'Arthur'})").close() + result = session.run("CREATE (person:Person {name: 'Arthur'})") # end::statement-without-parameters[] + result.discard() session.close() def test_result_cursor(self): @@ -116,8 +118,8 @@ def test_result_cursor(self): result = session.run("MATCH (tool:Tool) WHERE tool.name CONTAINS {term} " "RETURN tool.name", {"term": search_term}) print("List of tools called %r:" % search_term) - while result.next(): - print(result["tool.name"]) + for record in result: + print(record["tool.name"]) # end::result-cursor[] session.close() @@ -127,10 +129,10 @@ def test_cursor_nesting(self): # tag::retain-result-query[] result = session.run("MATCH (knight:Person:Knight) WHERE knight.castle = {castle} " "RETURN id(knight) AS knight_id", {"castle": "Camelot"}) - while result.next(): + for record in result: session.run("MATCH (knight) WHERE id(knight) = {id} " "MATCH (king:Person) WHERE king.name = {king} " - "CREATE (knight)-[:DEFENDS]->(king)", {"id": result["knight_id"], "king": "Arthur"}) + "CREATE (knight)-[:DEFENDS]->(king)", {"id": record["knight_id"], "king": "Arthur"}) # end::retain-result-query[] session.close() @@ -140,9 +142,8 @@ def test_result_retention(self): # tag::retain-result-process[] result = session.run("MATCH (knight:Person:Knight) WHERE knight.castle = {castle} " "RETURN id(knight) AS knight_id", {"castle": "Camelot"}) - id_records = list(result.stream()) - - for record in id_records: + retained_result = list(result) + for record in retained_result: session.run("MATCH (knight) WHERE id(knight) = {id} " "MATCH (king:Person) WHERE king.name = {king} " "CREATE (knight)-[:DEFENDS]->(king)", {"id": record["knight_id"], "king": "Arthur"}) @@ -158,9 +159,8 @@ def test_transaction_commit(self): tx.commit() # end::transaction-commit[] result = session.run("MATCH (p:Person {name: 'Guinevere'}) RETURN count(p)") - assert result.next() - assert result["count(p)"] == 1 - assert result.at_end + record = next(result) + assert record["count(p)"] == 1 session.close() def test_transaction_rollback(self): @@ -172,9 +172,8 @@ def test_transaction_rollback(self): tx.rollback() # end::transaction-rollback[] result = session.run("MATCH (p:Person {name: 'Merlin'}) RETURN count(p)") - assert result.next() - assert result["count(p)"] == 0 - assert result.at_end + record = next(result) + assert record["count(p)"] == 0 session.close() def test_result_summary_query_profile(self): @@ -183,8 +182,7 @@ def test_result_summary_query_profile(self): # tag::result-summary-query-profile[] result = session.run("PROFILE MATCH (p:Person {name: {name}}) " "RETURN id(p)", {"name": "Arthur"}) - while result.next(): - pass # skip the records to get to the summary + list(result) # skip the records to get to the summary print(result.summary.statement_type) print(result.summary.profile) # end::result-summary-query-profile[] @@ -195,8 +193,7 @@ def test_result_summary_notifications(self): session = driver.session() # tag::result-summary-notifications[] result = session.run("EXPLAIN MATCH (king), (queen) RETURN king, queen") - while result.next(): - pass # skip the records to get to the summary + list(result) # skip the records to get to the summary for notification in result.summary.notifications: print(notification) # end::result-summary-notifications[] diff --git a/neo4j/__main__.py b/neo4j/__main__.py index 57896c052..2d2036587 100644 --- a/neo4j/__main__.py +++ b/neo4j/__main__.py @@ -27,19 +27,21 @@ from sys import stdout, stderr from .util import Watcher -from .v1.session import GraphDatabase, CypherError +from .v1.session import GraphDatabase, CypherError, basic_auth def main(): parser = ArgumentParser(description="Execute one or more Cypher statements using Bolt.") parser.add_argument("statement", nargs="+") - parser.add_argument("-u", "--url", default="bolt://localhost", metavar="CONNECTION_URL") + parser.add_argument("-k", "--keys", action="store_true") + parser.add_argument("-P", "--password") parser.add_argument("-p", "--parameter", action="append", metavar="NAME=VALUE") parser.add_argument("-q", "--quiet", action="store_true") - parser.add_argument("-s", "--secure", action="store_true") + parser.add_argument("-U", "--user", default="neo4j") + parser.add_argument("-u", "--url", default="bolt://localhost", metavar="CONNECTION_URL") parser.add_argument("-v", "--verbose", action="count") parser.add_argument("-x", "--times", type=int, default=1) - parser.add_argument("-z", "--summarize", action="store_true") + parser.add_argument("-z", "--summary", action="store_true") args = parser.parse_args() if args.verbose: @@ -57,30 +59,26 @@ def main(): except ValueError: parameters[name] = value - driver = GraphDatabase.driver(args.url, secure=args.secure) + driver = GraphDatabase.driver(args.url, auth=basic_auth(args.user, args.password)) session = driver.session() for _ in range(args.times): for statement in args.statement: try: - cursor = session.run(statement, parameters) + 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(cursor.stream()): - 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.keys: + stdout.write("%s\r\n" % "\t".join(result.keys())) + for i, record in enumerate(result): + stdout.write("%s\r\n" % "\t".join(map(repr, record.values()))) if args.summary: - summary = cursor.summary + summary = result.summary 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("Counters : %r\r\n" % summary.counters) stdout.write("\r\n") session.close() diff --git a/neo4j/v1/__init__.py b/neo4j/v1/__init__.py index 1a1b454b3..5c2754557 100644 --- a/neo4j/v1/__init__.py +++ b/neo4j/v1/__init__.py @@ -20,4 +20,4 @@ from .constants import * from .session import * -from .typesystem import * +from .types import * diff --git a/neo4j/v1/exceptions.py b/neo4j/v1/exceptions.py index 42bbdc5fc..8f18e31b5 100644 --- a/neo4j/v1/exceptions.py +++ b/neo4j/v1/exceptions.py @@ -38,10 +38,3 @@ def __init__(self, data): for key, value in data.items(): if not key.startswith("_"): setattr(self, key, value) - - -class ResultError(Exception): - """ Raised when the cursor encounters a problem. - """ - - pass diff --git a/neo4j/v1/session.py b/neo4j/v1/session.py index c72478d85..b7ef3221c 100644 --- a/neo4j/v1/session.py +++ b/neo4j/v1/session.py @@ -34,8 +34,8 @@ class which can be used to obtain `Driver` instances that are used for from .compat import integer, string, urlparse from .connection import connect, Response, RUN, PULL_ALL from .constants import ENCRYPTED_DEFAULT, TRUST_DEFAULT, TRUST_SIGNED_CERTIFICATES -from .exceptions import CypherError, ResultError -from .typesystem import hydrated +from .exceptions import CypherError +from .types import hydrated DEFAULT_MAX_POOL_SIZE = 50 @@ -148,150 +148,98 @@ def recycle(self, session): pool.appendleft(session) -class ResultCursor(object): +class StatementResult(object): """ A handler for the result of Cypher statement execution. """ - #: The statement that was executed to produce this result. + #: The statement text that was executed to produce this result. statement = None #: Dictionary of parameters passed with the statement. parameters = None - def __init__(self, connection, statement, parameters): - super(ResultCursor, self).__init__() - self.statement = statement - self.parameters = parameters + #: The result summary (only available after the result has + #: been fully consumed) + summary = None + + def __init__(self, connection, run_response, pull_all_response): + super(StatementResult, self).__init__() + + # The Connection instance behind this cursor. + self.connection = connection + + # The keys for the records in the result stream. These are + # lazily populated on request. self._keys = None - self._connection = connection - self._current = None - self._record_buffer = deque() - self._position = -1 - self._summary = None + + # Buffer for incoming records to be queued before yielding. If + # the result is used immediately, this buffer will be ignored. + self._buffer = deque() + + # Flag to indicate whether the entire stream has been consumed + # from the network (but not necessarily yielded). self._consumed = False - def is_open(self): - """ Return ``True`` if this cursor is still open, ``False`` otherwise. - """ - return bool(self._connection) + def on_header(metadata): + # Called on receipt of the result header. + self._keys = metadata["fields"] - def close(self): - """ Consume the remainder of this result and detach the connection - from this cursor. - """ - if self._connection and not self._connection.closed: - self._consume() - self._connection = None + def on_record(values): + # Called on receipt of each result record. + self._buffer.append(values) - def next(self): - """ Advance to the next record, if available, and return a boolean - to indicate whether or not the cursor has moved. - """ - if self._record_buffer: - values = self._record_buffer.popleft() - self._current = Record(self.keys(), tuple(map(hydrated, values))) - self._position += 1 - return True - elif self._consumed: - return False - else: - self._connection.fetch() - return self.next() + def on_footer(metadata): + # Called on receipt of the result footer. + self.summary = ResultSummary(self.statement, self.parameters, **metadata) + self._consumed = True - def record(self): - """ Return the current record. - """ - return self._current + def on_failure(metadata): + # Called on execution failure. + self._consumed = True + raise CypherError(metadata) - @property - def position(self): - """ Return the current cursor position. - """ - return self._position + run_response.on_success = on_header + run_response.on_failure = on_failure - @property - def at_end(self): - """ Return ``True`` if at the end of the record stream, ``False`` - otherwise. - """ - if self._record_buffer: - return False - elif self._consumed: - return True - else: - self._connection.fetch() - return self.at_end + pull_all_response.on_record = on_record + pull_all_response.on_success = on_footer + pull_all_response.on_failure = on_failure - def stream(self): - """ Yield all subsequent records. - """ - while self.next(): - yield self.record() + def __iter__(self): + return self - def __getitem__(self, item): - current = self._current - if current is None: - raise TypeError("No current record") - return current[item] + def __next__(self): + if self._buffer: + values = self._buffer.popleft() + return Record(self.keys(), tuple(map(hydrated, values))) + elif self._consumed: + raise StopIteration() + else: + while not self._buffer and not self._consumed: + self.connection.fetch() + return self.__next__() def keys(self): """ Return the keys for the records. """ # Fetch messages until we have the header or a failure while self._keys is None and not self._consumed: - self._connection.fetch() + self.connection.fetch() return self._keys - def get(self, item, default=None): - current = self._current - if current is None: - raise TypeError("No current record") - try: - return current[item] - except (IndexError, KeyError): - return default - - @property - def summary(self): - """ Return the summary from the trailing metadata. Note that this is - only available once the entire result stream has been consumed. - Attempting to access the summary before then will raise an error. - - :rtype: ResultSummary - :raises ResultError: if the entire result has not yet been consumed + def discard(self): + """ Consume the remainder of this result and detach the connection + from this cursor. """ - if self._consumed: - return self._summary - else: - raise ResultError("Summary not available until the entire result has been consumed") - - def _consume(self): - # Consume the remainder of this result, triggering all appropriate callback functions. - fetch = self._connection.fetch - while not self._consumed: - fetch() - - def _on_header(self, metadata): - # Called on receipt of the result header. - self._keys = metadata["fields"] - - def _on_record(self, values): - # Called on receipt of each result record. - self._record_buffer.append(values) - - def _on_footer(self, metadata): - # Called on receipt of the result footer. - self._summary = ResultSummary(self.statement, self.parameters, **metadata) - self._consumed = True - - def _on_failure(self, metadata): - # Called on execution failure. - self._consumed = True - raise CypherError(metadata) + if self.connection and not self.connection.closed: + fetch = self.connection.fetch + while not self._consumed: + fetch() + self.connection = None class ResultSummary(object): - """ A summary of execution returned with a :class:`.ResultCursor` object. + """ A summary of execution returned with a :class:`.StatementResult` object. """ #: The statement that was executed to produce this result. @@ -460,7 +408,7 @@ def __init__(self, driver): self.driver = driver self.connection = connect(driver.host, driver.port, driver.ssl_context, **driver.config) self.transaction = None - self.last_cursor = None + self.last_result = None def __del__(self): try: @@ -489,7 +437,7 @@ def run(self, statement, parameters=None): :param statement: Cypher statement to execute :param parameters: dictionary of parameters :return: Cypher result - :rtype: :class:`.ResultCursor` + :rtype: :class:`.StatementResult` """ # Ensure the statement is a Unicode value @@ -506,29 +454,24 @@ def run(self, statement, parameters=None): params[key] = value parameters = params - cursor = ResultCursor(self.connection, statement, parameters) - run_response = Response(self.connection) - run_response.on_success = cursor._on_header - run_response.on_failure = cursor._on_failure - pull_all_response = Response(self.connection) - pull_all_response.on_record = cursor._on_record - pull_all_response.on_success = cursor._on_footer - pull_all_response.on_failure = cursor._on_failure + cursor = StatementResult(self.connection, run_response, pull_all_response) + cursor.statement = statement + cursor.parameters = parameters self.connection.append(RUN, (statement, parameters), response=run_response) self.connection.append(PULL_ALL, response=pull_all_response) self.connection.send() - self.last_cursor = cursor + self.last_result = cursor return cursor def close(self): """ Recycle this session through the driver it came from. """ - if self.last_cursor: - self.last_cursor.close() + if self.last_result: + self.last_result.discard() self.driver.recycle(self) def begin_transaction(self): @@ -686,20 +629,3 @@ def __eq__(self, other): def __ne__(self, other): return not self.__eq__(other) - - -def record(obj): - """ Obtain an immutable record for the given object - (either by calling obj.__record__() or by copying out the record data) - """ - try: - return obj.__record__() - except AttributeError: - keys = obj.keys() - values = [] - for key in keys: - values.append(obj[key]) - return Record(keys, values) - - - diff --git a/neo4j/v1/typesystem.py b/neo4j/v1/types.py similarity index 100% rename from neo4j/v1/typesystem.py rename to neo4j/v1/types.py diff --git a/test/tck/tck_util.py b/test/tck/tck_util.py index 69ec02b2c..2f078097a 100644 --- a/test/tck/tck_util.py +++ b/test/tck/tck_util.py @@ -28,16 +28,16 @@ def send_string(text): session = driver.session() - cursor = session.run(text) + result = session.run(text) session.close() - return list(cursor.stream()) + return list(result) def send_parameters(statement, parameters): session = driver.session() - cursor = session.run(statement, parameters) + result = session.run(statement, parameters) session.close() - return list(cursor.stream()) + return list(result) try: diff --git a/test/test_session.py b/test/test_session.py index 14b851fe6..b3c739900 100644 --- a/test/test_session.py +++ b/test/test_session.py @@ -24,9 +24,9 @@ from mock import patch from neo4j.v1.constants import TRUST_ON_FIRST_USE -from neo4j.v1.exceptions import CypherError, ResultError -from neo4j.v1.session import GraphDatabase, basic_auth, Record, record -from neo4j.v1.typesystem import Node, Relationship, Path +from neo4j.v1.exceptions import CypherError +from neo4j.v1.session import GraphDatabase, basic_auth, Record +from neo4j.v1.types import Node, Relationship, Path from test.util import ServerTestCase @@ -121,24 +121,13 @@ def test_tofu_session_trusts_certificate_after_first_use(self): assert connection.der_encoded_server_certificate == certificate session.close() - # TODO: Find a way to run this test - # def test_verified_session_uses_secure_socket(self): - # driver = GraphDatabase.driver("bolt://localhost", security=SECURITY_VERIFIED) - # session = driver.session() - # connection = session.connection - # assert isinstance(connection.channel.socket, SSLSocket) - # assert connection.der_encoded_server_certificate is not None - # session.close() - class RunTestCase(ServerTestCase): def test_can_run_simple_statement(self): session = GraphDatabase.driver("bolt://localhost", auth=auth_token).session() - count = 0 - cursor = session.run("RETURN 1 AS n") - assert cursor.position == -1 - for record in cursor.stream(): + result = session.run("RETURN 1 AS n") + for record in result: assert record[0] == 1 assert record["n"] == 1 with self.assertRaises(KeyError): @@ -150,15 +139,12 @@ def test_can_run_simple_statement(self): _ = record[object()] assert repr(record) assert len(record) == 1 - assert cursor.position == count - count += 1 session.close() - assert count == 1 def test_can_run_simple_statement_with_params(self): session = GraphDatabase.driver("bolt://localhost", auth=auth_token).session() count = 0 - for record in session.run("RETURN {x} AS n", {"x": {"abc": ["d", "e", "f"]}}).stream(): + for record in session.run("RETURN {x} AS n", {"x": {"abc": ["d", "e", "f"]}}): assert record[0] == {"abc": ["d", "e", "f"]} assert record["n"] == {"abc": ["d", "e", "f"]} assert repr(record) @@ -170,17 +156,17 @@ def test_can_run_simple_statement_with_params(self): def test_fails_on_bad_syntax(self): session = GraphDatabase.driver("bolt://localhost", auth=auth_token).session() with self.assertRaises(CypherError): - session.run("X").close() + session.run("X").discard() def test_fails_on_missing_parameter(self): session = GraphDatabase.driver("bolt://localhost", auth=auth_token).session() with self.assertRaises(CypherError): - session.run("RETURN {x}").close() + session.run("RETURN {x}").discard() def test_can_run_simple_statement_from_bytes_string(self): session = GraphDatabase.driver("bolt://localhost", auth=auth_token).session() count = 0 - for record in session.run(b"RETURN 1 AS n").stream(): + for record in session.run(b"RETURN 1 AS n"): assert record[0] == 1 assert record["n"] == 1 assert repr(record) @@ -192,7 +178,7 @@ def test_can_run_simple_statement_from_bytes_string(self): def test_can_run_statement_that_returns_multiple_records(self): session = GraphDatabase.driver("bolt://localhost", auth=auth_token).session() count = 0 - for record in session.run("unwind(range(1, 10)) AS z RETURN z").stream(): + for record in session.run("unwind(range(1, 10)) AS z RETURN z"): assert 1 <= record[0] <= 10 count += 1 session.close() @@ -200,14 +186,14 @@ 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", auth=auth_token).session() as session: - record_list = list(session.run("RETURN 1").stream()) + record_list = list(session.run("RETURN 1")) assert len(record_list) == 1 for record in record_list: assert record[0] == 1 def test_can_return_node(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: - record_list = list(session.run("MERGE (a:Person {name:'Alice'}) RETURN a").stream()) + record_list = list(session.run("MERGE (a:Person {name:'Alice'}) RETURN a")) assert len(record_list) == 1 for record in record_list: alice = record[0] @@ -217,8 +203,7 @@ def test_can_return_node(self): def test_can_return_relationship(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: - reocrd_list = list(session.run("MERGE ()-[r:KNOWS {since:1999}]->() " - "RETURN r").stream()) + reocrd_list = list(session.run("MERGE ()-[r:KNOWS {since:1999}]->() RETURN r")) assert len(reocrd_list) == 1 for record in reocrd_list: rel = record[0] @@ -228,8 +213,7 @@ def test_can_return_relationship(self): def test_can_return_path(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: - record_list = list(session.run("MERGE p=({name:'Alice'})-[:KNOWS]->({name:'Bob'}) " - "RETURN p").stream()) + record_list = list(session.run("MERGE p=({name:'Alice'})-[:KNOWS]->({name:'Bob'}) RETURN p")) assert len(record_list) == 1 for record in record_list: path = record[0] @@ -243,29 +227,29 @@ def test_can_return_path(self): def test_can_handle_cypher_error(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: with self.assertRaises(CypherError): - session.run("X").close() + session.run("X").discard() def test_keys_are_available_before_and_after_stream(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: - cursor = session.run("UNWIND range(1, 10) AS n RETURN n") - assert list(cursor.keys()) == ["n"] - _ = list(cursor.stream()) - assert list(cursor.keys()) == ["n"] + result = session.run("UNWIND range(1, 10) AS n RETURN n") + assert list(result.keys()) == ["n"] + list(result) + assert list(result.keys()) == ["n"] def test_keys_with_an_error(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: - cursor = session.run("X") + result = session.run("X") with self.assertRaises(CypherError): - _ = list(cursor.keys()) + list(result.keys()) class SummaryTestCase(ServerTestCase): def test_can_obtain_summary_after_consuming_result(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: - cursor = session.run("CREATE (n) RETURN n") - list(cursor.stream()) - summary = cursor.summary + result = session.run("CREATE (n) RETURN n") + list(result) + summary = result.summary assert summary.statement == "CREATE (n) RETURN n" assert summary.parameters == {} assert summary.statement_type == "rw" @@ -273,31 +257,21 @@ def test_can_obtain_summary_after_consuming_result(self): def test_cannot_obtain_summary_without_consuming_result(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: - cursor = session.run("CREATE (n) RETURN n") - with self.assertRaises(ResultError): - _ = cursor.summary - - # def test_can_obtain_summary_immediately_if_empty_result(self): - # with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: - # cursor = session.run("CREATE (n)") - # summary = cursor.summary - # assert summary.statement == "CREATE (n)" - # assert summary.parameters == {} - # assert summary.statement_type == "rw" - # assert summary.counters.nodes_created == 1 + result = session.run("CREATE (n) RETURN n") + assert result.summary is None def test_no_plan_info(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: - cursor = session.run("CREATE (n) RETURN n") - list(cursor.stream()) - assert cursor.summary.plan is None - assert cursor.summary.profile is None + result = session.run("CREATE (n) RETURN n") + list(result) # consume the result + assert result.summary.plan is None + assert result.summary.profile is None def test_can_obtain_plan_info(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: - cursor = session.run("EXPLAIN CREATE (n) RETURN n") - list(cursor.stream()) - plan = cursor.summary.plan + result = session.run("EXPLAIN CREATE (n) RETURN n") + list(result) # consume the result + plan = result.summary.plan assert plan.operator_type == "ProduceResults" assert plan.identifiers == ["n"] assert plan.arguments == {"planner": "COST", "EstimatedRows": 1.0, "version": "CYPHER 3.0", @@ -307,9 +281,9 @@ def test_can_obtain_plan_info(self): def test_can_obtain_profile_info(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: - cursor = session.run("PROFILE CREATE (n) RETURN n") - list(cursor.stream()) - profile = cursor.summary.profile + result = session.run("PROFILE CREATE (n) RETURN n") + list(result) # consume the result + profile = result.summary.profile assert profile.db_hits == 0 assert profile.rows == 1 assert profile.operator_type == "ProduceResults" @@ -321,16 +295,16 @@ def test_can_obtain_profile_info(self): def test_no_notification_info(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: - cursor = session.run("CREATE (n) RETURN n") - list(cursor.stream()) - notifications = cursor.summary.notifications + result = session.run("CREATE (n) RETURN n") + list(result) # consume the result + notifications = result.summary.notifications assert notifications == [] def test_can_obtain_notification_info(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: - cursor = session.run("EXPLAIN MATCH (n), (m) RETURN n, m") - list(cursor.stream()) - notifications = cursor.summary.notifications + result = session.run("EXPLAIN MATCH (n), (m) RETURN n, m") + list(result) # consume the result + notifications = result.summary.notifications assert len(notifications) == 1 notification = notifications[0] @@ -358,11 +332,11 @@ class ResetTestCase(ServerTestCase): def test_automatic_reset_after_failure(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: try: - session.run("X").close() + session.run("X").discard() except CypherError: - cursor = session.run("RETURN 1") - assert cursor.next() - assert cursor[0] == 1 + result = session.run("RETURN 1") + record = next(result) + assert record[0] == 1 else: assert False, "A Cypher error should have occurred" @@ -372,7 +346,7 @@ def test_defunct(self): assert not session.connection.defunct with patch.object(ChunkChannel, "chunk_reader", side_effect=ProtocolError()): with self.assertRaises(ProtocolError): - session.run("RETURN 1").close() + session.run("RETURN 1").discard() assert session.connection.defunct assert session.connection.closed @@ -423,10 +397,6 @@ def test_record_iter(self): a_record = Record(["name", "empire"], ["Nigel", "The British Empire"]) assert list(a_record.__iter__()) == ["name", "empire"] - def test_record_record(self): - a_record = Record(["name", "empire"], ["Nigel", "The British Empire"]) - assert record(a_record) is a_record - def test_record_copy(self): original = Record(["name", "empire"], ["Nigel", "The British Empire"]) duplicate = original.copy() @@ -458,9 +428,9 @@ def test_can_commit_transaction(self): tx = session.begin_transaction() # Create a node - cursor = tx.run("CREATE (a) RETURN id(a)") - assert cursor.next() - node_id = cursor[0] + result = tx.run("CREATE (a) RETURN id(a)") + record = next(result) + node_id = record[0] assert isinstance(node_id, int) # Update a property @@ -470,20 +440,20 @@ def test_can_commit_transaction(self): tx.commit() # Check the property value - cursor = session.run("MATCH (a) WHERE id(a) = {n} " + result = session.run("MATCH (a) WHERE id(a) = {n} " "RETURN a.foo", {"n": node_id}) - assert cursor.next() - foo = cursor[0] - assert foo == "bar" + record = next(result) + value = record[0] + assert value == "bar" def test_can_rollback_transaction(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: tx = session.begin_transaction() # Create a node - cursor = tx.run("CREATE (a) RETURN id(a)") - assert cursor.next() - node_id = cursor[0] + result = tx.run("CREATE (a) RETURN id(a)") + record = next(result) + node_id = record[0] assert isinstance(node_id, int) # Update a property @@ -493,17 +463,17 @@ def test_can_rollback_transaction(self): tx.rollback() # Check the property value - cursor = 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(list(cursor.stream())) == 0 + assert len(list(result)) == 0 def test_can_commit_transaction_using_with_block(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: with session.begin_transaction() as tx: # Create a node - cursor = tx.run("CREATE (a) RETURN id(a)") - assert cursor.next() - node_id = cursor[0] + result = tx.run("CREATE (a) RETURN id(a)") + record = next(result) + node_id = record[0] assert isinstance(node_id, int) # Update a property @@ -513,19 +483,19 @@ def test_can_commit_transaction_using_with_block(self): tx.success = True # Check the property value - cursor = session.run("MATCH (a) WHERE id(a) = {n} " + result = session.run("MATCH (a) WHERE id(a) = {n} " "RETURN a.foo", {"n": node_id}) - assert cursor.next() - foo = cursor[0] - assert foo == "bar" + record = next(result) + value = record[0] + assert value == "bar" def test_can_rollback_transaction_using_with_block(self): with GraphDatabase.driver("bolt://localhost", auth=auth_token).session() as session: with session.begin_transaction() as tx: # Create a node - cursor = tx.run("CREATE (a) RETURN id(a)") - assert cursor.next() - node_id = cursor[0] + result = tx.run("CREATE (a) RETURN id(a)") + record = next(result) + node_id = record[0] assert isinstance(node_id, int) # Update a property @@ -533,6 +503,6 @@ def test_can_rollback_transaction_using_with_block(self): "SET a.foo = {foo}", {"n": node_id, "foo": "bar"}) # Check the property value - cursor = 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(list(cursor.stream())) == 0 + assert len(list(result)) == 0 diff --git a/test/test_typesystem.py b/test/test_types.py similarity index 98% rename from test/test_typesystem.py rename to test/test_types.py index a52ca7def..7810ad199 100644 --- a/test/test_typesystem.py +++ b/test/test_types.py @@ -23,7 +23,7 @@ from neo4j.v1.packstream import Structure -from neo4j.v1.typesystem import Node, Relationship, UnboundRelationship, Path, hydrated +from neo4j.v1.types import Node, Relationship, UnboundRelationship, Path, hydrated class NodeTestCase(TestCase):