Skip to content

Commit 12d2891

Browse files
committed
Merge pull request #35 from neo4j/renaming
Refactoring for new naming and syncing property methods with .NET
2 parents f56a0ee + 70c6400 commit 12d2891

File tree

8 files changed

+111
-64
lines changed

8 files changed

+111
-64
lines changed

docs/source/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Session API
3232
.. autoclass:: neo4j.v1.ResultSummary
3333
:members:
3434

35-
.. autoclass:: neo4j.v1.StatementStatistics
35+
.. autoclass:: neo4j.v1.Counters
3636
:members:
3737

3838

examples/test_examples.py

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ def test_minimal_working_example(self):
4343

4444
session.run("CREATE (neo:Person {name:'Neo', age:23})")
4545

46-
cursor = session.run("MATCH (p:Person) WHERE p.name = 'Neo' RETURN p.age")
47-
while cursor.next():
48-
print("Neo is %d years old." % cursor["p.age"])
46+
result = session.run("MATCH (p:Person) WHERE p.name = 'Neo' RETURN p.age")
47+
while result.next():
48+
print("Neo is %d years old." % result["p.age"])
4949

5050
session.close()
5151
# end::minimal-example[]
@@ -104,34 +104,34 @@ def test_result_cursor(self):
104104
session = driver.session()
105105
# tag::result-cursor[]
106106
search_term = "hammer"
107-
cursor = session.run("MATCH (tool:Tool) WHERE tool.name CONTAINS {term} "
107+
result = session.run("MATCH (tool:Tool) WHERE tool.name CONTAINS {term} "
108108
"RETURN tool.name", {"term": search_term})
109109
print("List of tools called %r:" % search_term)
110-
while cursor.next():
111-
print(cursor["tool.name"])
110+
while result.next():
111+
print(result["tool.name"])
112112
# end::result-cursor[]
113113
session.close()
114114

115115
def test_cursor_nesting(self):
116116
driver = GraphDatabase.driver("bolt://localhost")
117117
session = driver.session()
118118
# tag::retain-result-query[]
119-
cursor = session.run("MATCH (person:Person) WHERE person.dept = {dept} "
119+
result = session.run("MATCH (person:Person) WHERE person.dept = {dept} "
120120
"RETURN id(person) AS minion", {"dept": "IT"})
121-
while cursor.next():
121+
while result.next():
122122
session.run("MATCH (person) WHERE id(person) = {id} "
123123
"MATCH (boss:Person) WHERE boss.name = {boss} "
124-
"CREATE (person)-[:REPORTS_TO]->(boss)", {"id": cursor["minion"], "boss": "Bob"})
124+
"CREATE (person)-[:REPORTS_TO]->(boss)", {"id": result["minion"], "boss": "Bob"})
125125
# end::retain-result-query[]
126126
session.close()
127127

128128
def test_result_retention(self):
129129
driver = GraphDatabase.driver("bolt://localhost")
130130
session = driver.session()
131131
# tag::retain-result-process[]
132-
cursor = session.run("MATCH (person:Person) WHERE person.dept = {dept} "
132+
result = session.run("MATCH (person:Person) WHERE person.dept = {dept} "
133133
"RETURN id(person) AS minion", {"dept": "IT"})
134-
minion_records = list(cursor.stream())
134+
minion_records = list(result.stream())
135135

136136
for record in minion_records:
137137
session.run("MATCH (person) WHERE id(person) = {id} "
@@ -148,10 +148,10 @@ def test_transaction_commit(self):
148148
tx.run("CREATE (p:Person {name: 'The One'})")
149149
tx.commit()
150150
# end::transaction-commit[]
151-
cursor = session.run("MATCH (p:Person {name: 'The One'}) RETURN count(p)")
152-
assert cursor.next()
153-
assert cursor["count(p)"] == 1
154-
assert cursor.at_end()
151+
result = session.run("MATCH (p:Person {name: 'The One'}) RETURN count(p)")
152+
assert result.next()
153+
assert result["count(p)"] == 1
154+
assert result.at_end
155155
session.close()
156156

157157
def test_transaction_rollback(self):
@@ -162,30 +162,33 @@ def test_transaction_rollback(self):
162162
tx.run("CREATE (p:Person {name: 'The One'})")
163163
tx.rollback()
164164
# end::transaction-rollback[]
165-
cursor = session.run("MATCH (p:Person {name: 'The One'}) RETURN count(p)")
166-
assert cursor.next()
167-
assert cursor["count(p)"] == 0
168-
assert cursor.at_end()
165+
result = session.run("MATCH (p:Person {name: 'The One'}) RETURN count(p)")
166+
assert result.next()
167+
assert result["count(p)"] == 0
168+
assert result.at_end
169169
session.close()
170170

171171
def test_result_summary_query_profile(self):
172172
driver = GraphDatabase.driver("bolt://localhost")
173173
session = driver.session()
174174
# tag::result-summary-query-profile[]
175-
cursor = session.run("PROFILE MATCH (p:Person {name: {name}}) "
175+
result = session.run("PROFILE MATCH (p:Person {name: {name}}) "
176176
"RETURN id(p)", {"name": "The One"})
177-
summary = cursor.summarize()
178-
print(summary.statement_type)
179-
print(summary.profile)
177+
while result.next():
178+
pass # skip the records to get to the summary
179+
print(result.summary.statement_type)
180+
print(result.summary.profile)
180181
# end::result-summary-query-profile[]
181182
session.close()
182183

183184
def test_result_summary_notifications(self):
184185
driver = GraphDatabase.driver("bolt://localhost")
185186
session = driver.session()
186187
# tag::result-summary-notifications[]
187-
summary = session.run("EXPLAIN MATCH (a), (b) RETURN a,b").summarize()
188-
for notification in summary.notifications:
188+
result = session.run("EXPLAIN MATCH (a), (b) RETURN a,b")
189+
while result.next():
190+
pass # skip the records to get to the summary
191+
for notification in result.summary.notifications:
189192
print(notification)
190193
# end::result-summary-notifications[]
191194
session.close()

neo4j/__main__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ def main():
7575
stdout.write("%s\r\n" % "\t".join(map(repr, record)))
7676
if has_results:
7777
stdout.write("\r\n")
78-
if args.summarize:
79-
summary = cursor.summarize()
78+
if args.summary:
79+
summary = cursor.summary
8080
stdout.write("Statement : %r\r\n" % summary.statement)
8181
stdout.write("Parameters : %r\r\n" % summary.parameters)
8282
stdout.write("Statement Type : %r\r\n" % summary.statement_type)

neo4j/v1/exceptions.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,10 @@ def __init__(self, data):
3838
for key, value in data.items():
3939
if not key.startswith("_"):
4040
setattr(self, key, value)
41+
42+
43+
class ResultError(Exception):
44+
""" Raised when the cursor encounters a problem.
45+
"""
46+
47+
pass

neo4j/v1/session.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class which can be used to obtain `Driver` instances that are used for
3232

3333
from .compat import integer, string, urlparse
3434
from .connection import connect, Response, RUN, PULL_ALL
35-
from .exceptions import CypherError
35+
from .exceptions import CypherError, ResultError
3636
from .typesystem import hydrated
3737

3838

@@ -170,11 +170,13 @@ def record(self):
170170
"""
171171
return self._current
172172

173+
@property
173174
def position(self):
174175
""" Return the current cursor position.
175176
"""
176177
return self._position
177178

179+
@property
178180
def at_end(self):
179181
""" Return ``True`` if at the end of the record stream, ``False``
180182
otherwise.
@@ -185,7 +187,7 @@ def at_end(self):
185187
return True
186188
else:
187189
self._connection.fetch()
188-
return self.at_end()
190+
return self.at_end
189191

190192
def stream(self):
191193
""" Yield all subsequent records.
@@ -216,13 +218,19 @@ def get(self, item, default=None):
216218
except (IndexError, KeyError):
217219
return default
218220

219-
def summarize(self):
220-
""" Consume the remainder of this result and produce a summary.
221+
@property
222+
def summary(self):
223+
""" Return the summary from the trailing metadata. Note that this is
224+
only available once the entire result stream has been consumed.
225+
Attempting to access the summary before then will raise an error.
221226
222227
:rtype: ResultSummary
228+
:raises ResultError: if the entire result has not yet been consumed
223229
"""
224-
self._consume()
225-
return self._summary
230+
if self._consumed:
231+
return self._summary
232+
else:
233+
raise ResultError("Summary not available until the entire result has been consumed")
226234

227235
def _consume(self):
228236
# Consume the remainder of this result, triggering all appropriate callback functions.
@@ -262,8 +270,8 @@ class ResultSummary(object):
262270
#: The type of statement (``'r'`` = read-only, ``'rw'`` = read/write).
263271
statement_type = None
264272

265-
#: A set of statistical information held in a :class:`.StatementStatistics` instance.
266-
statistics = None
273+
#: A set of statistical information held in a :class:`.Counters` instance.
274+
counters = None
267275

268276
#: A :class:`.Plan` instance
269277
plan = None
@@ -281,7 +289,7 @@ def __init__(self, statement, parameters, **metadata):
281289
self.statement = statement
282290
self.parameters = parameters
283291
self.statement_type = metadata.get("type")
284-
self.statistics = StatementStatistics(metadata.get("stats", {}))
292+
self.counters = Counters(metadata.get("stats", {}))
285293
if "plan" in metadata:
286294
self.plan = make_plan(metadata["plan"])
287295
if "profile" in metadata:
@@ -296,7 +304,7 @@ def __init__(self, statement, parameters, **metadata):
296304
notification["description"], position))
297305

298306

299-
class StatementStatistics(object):
307+
class Counters(object):
300308
""" Set of statistics from a Cypher statement execution.
301309
"""
302310

neokit

runtests.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ echo "Running tests with $(python --version)"
6262
pip install --upgrade -r ${DRIVER_HOME}/test_requirements.txt
6363
echo ""
6464
TEST_RUNNER="coverage run -m ${UNITTEST} discover -vfs ${TEST}"
65+
EXAMPLES_RUNNER="coverage run -m ${UNITTEST} discover -vfs examples"
6566
BEHAVE_RUNNER="behave --tags=-db --tags=-in_dev test/tck"
6667
if [ ${RUNNING} -eq 1 ]
6768
then

test/test_session.py

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from unittest import TestCase
2323

2424
from mock import patch
25+
from neo4j.v1.exceptions import ResultError
2526
from neo4j.v1.session import GraphDatabase, CypherError, Record, record
2627
from neo4j.v1.typesystem import Node, Relationship, Path
2728

@@ -85,7 +86,9 @@ def test_sessions_are_not_reused_if_still_in_use(self):
8586
def test_can_run_simple_statement(self):
8687
session = GraphDatabase.driver("bolt://localhost").session()
8788
count = 0
88-
for record in session.run("RETURN 1 AS n").stream():
89+
cursor = session.run("RETURN 1 AS n")
90+
assert cursor.position == -1
91+
for record in cursor.stream():
8992
assert record[0] == 1
9093
assert record["n"] == 1
9194
with self.assertRaises(KeyError):
@@ -97,6 +100,7 @@ def test_can_run_simple_statement(self):
97100
_ = record[object()]
98101
assert repr(record)
99102
assert len(record) == 1
103+
assert cursor.position == count
100104
count += 1
101105
session.close()
102106
assert count == 1
@@ -191,25 +195,59 @@ def test_can_handle_cypher_error(self):
191195
with self.assertRaises(CypherError):
192196
session.run("X").close()
193197

194-
def test_can_obtain_summary_info(self):
198+
def test_keys_are_available_before_and_after_stream(self):
199+
with GraphDatabase.driver("bolt://localhost").session() as session:
200+
cursor = session.run("UNWIND range(1, 10) AS n RETURN n")
201+
assert list(cursor.keys()) == ["n"]
202+
_ = list(cursor.stream())
203+
assert list(cursor.keys()) == ["n"]
204+
205+
def test_keys_with_an_error(self):
206+
with GraphDatabase.driver("bolt://localhost").session() as session:
207+
cursor = session.run("X")
208+
with self.assertRaises(CypherError):
209+
_ = list(cursor.keys())
210+
211+
212+
class SummaryTestCase(TestCase):
213+
214+
def test_can_obtain_summary_after_consuming_result(self):
195215
with GraphDatabase.driver("bolt://localhost").session() as session:
196216
cursor = session.run("CREATE (n) RETURN n")
197-
summary = cursor.summarize()
217+
list(cursor.stream())
218+
summary = cursor.summary
198219
assert summary.statement == "CREATE (n) RETURN n"
199220
assert summary.parameters == {}
200221
assert summary.statement_type == "rw"
201-
assert summary.statistics.nodes_created == 1
222+
assert summary.counters.nodes_created == 1
223+
224+
def test_cannot_obtain_summary_without_consuming_result(self):
225+
with GraphDatabase.driver("bolt://localhost").session() as session:
226+
cursor = session.run("CREATE (n) RETURN n")
227+
with self.assertRaises(ResultError):
228+
_ = cursor.summary
229+
230+
# def test_can_obtain_summary_immediately_if_empty_result(self):
231+
# with GraphDatabase.driver("bolt://localhost").session() as session:
232+
# cursor = session.run("CREATE (n)")
233+
# summary = cursor.summary
234+
# assert summary.statement == "CREATE (n)"
235+
# assert summary.parameters == {}
236+
# assert summary.statement_type == "rw"
237+
# assert summary.counters.nodes_created == 1
202238

203239
def test_no_plan_info(self):
204240
with GraphDatabase.driver("bolt://localhost").session() as session:
205241
cursor = session.run("CREATE (n) RETURN n")
206-
assert cursor.summarize().plan is None
207-
assert cursor.summarize().profile is None
242+
list(cursor.stream())
243+
assert cursor.summary.plan is None
244+
assert cursor.summary.profile is None
208245

209246
def test_can_obtain_plan_info(self):
210247
with GraphDatabase.driver("bolt://localhost").session() as session:
211248
cursor = session.run("EXPLAIN CREATE (n) RETURN n")
212-
plan = cursor.summarize().plan
249+
list(cursor.stream())
250+
plan = cursor.summary.plan
213251
assert plan.operator_type == "ProduceResults"
214252
assert plan.identifiers == ["n"]
215253
assert plan.arguments == {"planner": "COST", "EstimatedRows": 1.0, "version": "CYPHER 3.0",
@@ -220,7 +258,8 @@ def test_can_obtain_plan_info(self):
220258
def test_can_obtain_profile_info(self):
221259
with GraphDatabase.driver("bolt://localhost").session() as session:
222260
cursor = session.run("PROFILE CREATE (n) RETURN n")
223-
profile = cursor.summarize().profile
261+
list(cursor.stream())
262+
profile = cursor.summary.profile
224263
assert profile.db_hits == 0
225264
assert profile.rows == 1
226265
assert profile.operator_type == "ProduceResults"
@@ -232,14 +271,16 @@ def test_can_obtain_profile_info(self):
232271

233272
def test_no_notification_info(self):
234273
with GraphDatabase.driver("bolt://localhost").session() as session:
235-
result = session.run("CREATE (n) RETURN n")
236-
notifications = result.summarize().notifications
274+
cursor = session.run("CREATE (n) RETURN n")
275+
list(cursor.stream())
276+
notifications = cursor.summary.notifications
237277
assert notifications == []
238278

239279
def test_can_obtain_notification_info(self):
240280
with GraphDatabase.driver("bolt://localhost").session() as session:
241-
result = session.run("EXPLAIN MATCH (n), (m) RETURN n, m")
242-
notifications = result.summarize().notifications
281+
cursor = session.run("EXPLAIN MATCH (n), (m) RETURN n, m")
282+
list(cursor.stream())
283+
notifications = cursor.summary.notifications
243284

244285
assert len(notifications) == 1
245286
notification = notifications[0]
@@ -261,19 +302,6 @@ def test_can_obtain_notification_info(self):
261302
assert position.line == 1
262303
assert position.column == 1
263304

264-
def test_keys_are_available_before_and_after_stream(self):
265-
with GraphDatabase.driver("bolt://localhost").session() as session:
266-
cursor = session.run("UNWIND range(1, 10) AS n RETURN n")
267-
assert list(cursor.keys()) == ["n"]
268-
_ = list(cursor.stream())
269-
assert list(cursor.keys()) == ["n"]
270-
271-
def test_keys_with_an_error(self):
272-
with GraphDatabase.driver("bolt://localhost").session() as session:
273-
cursor = session.run("X")
274-
with self.assertRaises(CypherError):
275-
_ = list(cursor.keys())
276-
277305

278306
class ResetTestCase(TestCase):
279307

0 commit comments

Comments
 (0)