Skip to content

Commit 8be54e7

Browse files
committed
Merge branch '1.0' into 1.0-tls
Conflicts: neo4j/v1/session.py test/test_session.py
2 parents f71eb47 + 12d2891 commit 8be54e7

14 files changed

+384
-400
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: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,17 @@
1919
# limitations under the License.
2020

2121

22-
from unittest import TestCase
22+
from test.util import ServerTestCase
2323

2424
# tag::minimal-example-import[]
2525
from neo4j.v1 import GraphDatabase
2626
# end::minimal-example-import[]
2727

2828

29-
class FreshDatabaseTestCase(TestCase):
29+
class FreshDatabaseTestCase(ServerTestCase):
3030

3131
def setUp(self):
32+
ServerTestCase.setUp(self)
3233
session = GraphDatabase.driver("bolt://localhost").session()
3334
session.run("MATCH (n) DETACH DELETE n")
3435
session.close()
@@ -43,9 +44,9 @@ def test_minimal_working_example(self):
4344

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

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"])
47+
result = session.run("MATCH (p:Person) WHERE p.name = 'Neo' RETURN p.age")
48+
while result.next():
49+
print("Neo is %d years old." % result["p.age"])
4950

5051
session.close()
5152
# end::minimal-example[]
@@ -104,34 +105,34 @@ def test_result_cursor(self):
104105
session = driver.session()
105106
# tag::result-cursor[]
106107
search_term = "hammer"
107-
cursor = session.run("MATCH (tool:Tool) WHERE tool.name CONTAINS {term} "
108+
result = session.run("MATCH (tool:Tool) WHERE tool.name CONTAINS {term} "
108109
"RETURN tool.name", {"term": search_term})
109110
print("List of tools called %r:" % search_term)
110-
while cursor.next():
111-
print(cursor["tool.name"])
111+
while result.next():
112+
print(result["tool.name"])
112113
# end::result-cursor[]
113114
session.close()
114115

115116
def test_cursor_nesting(self):
116117
driver = GraphDatabase.driver("bolt://localhost")
117118
session = driver.session()
118119
# tag::retain-result-query[]
119-
cursor = session.run("MATCH (person:Person) WHERE person.dept = {dept} "
120+
result = session.run("MATCH (person:Person) WHERE person.dept = {dept} "
120121
"RETURN id(person) AS minion", {"dept": "IT"})
121-
while cursor.next():
122+
while result.next():
122123
session.run("MATCH (person) WHERE id(person) = {id} "
123124
"MATCH (boss:Person) WHERE boss.name = {boss} "
124-
"CREATE (person)-[:REPORTS_TO]->(boss)", {"id": cursor["minion"], "boss": "Bob"})
125+
"CREATE (person)-[:REPORTS_TO]->(boss)", {"id": result["minion"], "boss": "Bob"})
125126
# end::retain-result-query[]
126127
session.close()
127128

128129
def test_result_retention(self):
129130
driver = GraphDatabase.driver("bolt://localhost")
130131
session = driver.session()
131132
# tag::retain-result-process[]
132-
cursor = session.run("MATCH (person:Person) WHERE person.dept = {dept} "
133+
result = session.run("MATCH (person:Person) WHERE person.dept = {dept} "
133134
"RETURN id(person) AS minion", {"dept": "IT"})
134-
minion_records = list(cursor.stream())
135+
minion_records = list(result.stream())
135136

136137
for record in minion_records:
137138
session.run("MATCH (person) WHERE id(person) = {id} "
@@ -148,10 +149,10 @@ def test_transaction_commit(self):
148149
tx.run("CREATE (p:Person {name: 'The One'})")
149150
tx.commit()
150151
# 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()
152+
result = session.run("MATCH (p:Person {name: 'The One'}) RETURN count(p)")
153+
assert result.next()
154+
assert result["count(p)"] == 1
155+
assert result.at_end
155156
session.close()
156157

157158
def test_transaction_rollback(self):
@@ -162,30 +163,33 @@ def test_transaction_rollback(self):
162163
tx.run("CREATE (p:Person {name: 'The One'})")
163164
tx.rollback()
164165
# 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()
166+
result = session.run("MATCH (p:Person {name: 'The One'}) RETURN count(p)")
167+
assert result.next()
168+
assert result["count(p)"] == 0
169+
assert result.at_end
169170
session.close()
170171

171172
def test_result_summary_query_profile(self):
172173
driver = GraphDatabase.driver("bolt://localhost")
173174
session = driver.session()
174175
# tag::result-summary-query-profile[]
175-
cursor = session.run("PROFILE MATCH (p:Person {name: {name}}) "
176+
result = session.run("PROFILE MATCH (p:Person {name: {name}}) "
176177
"RETURN id(p)", {"name": "The One"})
177-
summary = cursor.summarize()
178-
print(summary.statement_type)
179-
print(summary.profile)
178+
while result.next():
179+
pass # skip the records to get to the summary
180+
print(result.summary.statement_type)
181+
print(result.summary.profile)
180182
# end::result-summary-query-profile[]
181183
session.close()
182184

183185
def test_result_summary_notifications(self):
184186
driver = GraphDatabase.driver("bolt://localhost")
185187
session = driver.session()
186188
# tag::result-summary-notifications[]
187-
summary = session.run("EXPLAIN MATCH (a), (b) RETURN a,b").summarize()
188-
for notification in summary.notifications:
189+
result = session.run("EXPLAIN MATCH (a), (b) RETURN a,b")
190+
while result.next():
191+
pass # skip the records to get to the summary
192+
for notification in result.summary.notifications:
189193
print(notification)
190194
# end::result-summary-notifications[]
191195
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/connection.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ def on_failure(metadata):
224224
self.append(INIT, (self.user_agent,), response=response)
225225
self.send()
226226
while not response.complete:
227-
self.fetch_next()
227+
self.fetch()
228228

229229
def __del__(self):
230230
self.close()
@@ -258,9 +258,9 @@ def on_failure(metadata):
258258

259259
self.append(RESET, response=response)
260260
self.send()
261-
fetch_next = self.fetch_next
261+
fetch = self.fetch
262262
while not response.complete:
263-
fetch_next()
263+
fetch()
264264

265265
def send(self):
266266
""" Send all queued messages to the server.
@@ -271,7 +271,7 @@ def send(self):
271271
raise ProtocolError("Cannot write to a defunct connection")
272272
self.channel.send()
273273

274-
def fetch_next(self):
274+
def fetch(self):
275275
""" Receive exactly one message from the server.
276276
"""
277277
if self.closed:
@@ -342,9 +342,8 @@ def match_or_trust(self, host, der_encoded_certificate):
342342
with open(self.path) as f_in:
343343
for line in f_in:
344344
known_host, _, known_cert = line.strip().partition(":")
345+
known_cert = known_cert.encode("utf-8")
345346
if host == known_host:
346-
print("Received: %s" % base64_encoded_certificate)
347-
print("Known: %s" % known_cert)
348347
return base64_encoded_certificate == known_cert
349348
# First use (no hosts match)
350349
try:

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: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class which can be used to obtain `Driver` instances that are used for
3434
from .compat import integer, string, urlparse
3535
from .connection import connect, Response, RUN, PULL_ALL
3636
from .constants import SECURITY_NONE, SECURITY_VERIFIED, SECURITY_DEFAULT
37-
from .exceptions import CypherError
37+
from .exceptions import CypherError, ResultError
3838
from .typesystem import hydrated
3939

4040

@@ -144,7 +144,7 @@ def __init__(self, connection, statement, parameters):
144144
self._keys = None
145145
self._connection = connection
146146
self._current = None
147-
self._next = deque()
147+
self._record_buffer = deque()
148148
self._position = -1
149149
self._summary = None
150150
self._consumed = False
@@ -166,38 +166,40 @@ def next(self):
166166
""" Advance to the next record, if available, and return a boolean
167167
to indicate whether or not the cursor has moved.
168168
"""
169-
if self._next:
170-
values = self._next.popleft()
169+
if self._record_buffer:
170+
values = self._record_buffer.popleft()
171171
self._current = Record(self.keys(), tuple(map(hydrated, values)))
172172
self._position += 1
173173
return True
174174
elif self._consumed:
175175
return False
176176
else:
177-
self._connection.fetch_next()
177+
self._connection.fetch()
178178
return self.next()
179179

180180
def record(self):
181181
""" Return the current record.
182182
"""
183183
return self._current
184184

185+
@property
185186
def position(self):
186187
""" Return the current cursor position.
187188
"""
188189
return self._position
189190

191+
@property
190192
def at_end(self):
191193
""" Return ``True`` if at the end of the record stream, ``False``
192194
otherwise.
193195
"""
194-
if self._next:
196+
if self._record_buffer:
195197
return False
196198
elif self._consumed:
197199
return True
198200
else:
199-
self._connection.fetch_next()
200-
return self.at_end()
201+
self._connection.fetch()
202+
return self.at_end
201203

202204
def stream(self):
203205
""" Yield all subsequent records.
@@ -216,7 +218,7 @@ def keys(self):
216218
"""
217219
# Fetch messages until we have the header or a failure
218220
while self._keys is None and not self._consumed:
219-
self._connection.fetch_next()
221+
self._connection.fetch()
220222
return self._keys
221223

222224
def get(self, item, default=None):
@@ -228,27 +230,33 @@ def get(self, item, default=None):
228230
except (IndexError, KeyError):
229231
return default
230232

231-
def summarize(self):
232-
""" Consume the remainder of this result and produce a summary.
233+
@property
234+
def summary(self):
235+
""" Return the summary from the trailing metadata. Note that this is
236+
only available once the entire result stream has been consumed.
237+
Attempting to access the summary before then will raise an error.
233238
234239
:rtype: ResultSummary
240+
:raises ResultError: if the entire result has not yet been consumed
235241
"""
236-
self._consume()
237-
return self._summary
242+
if self._consumed:
243+
return self._summary
244+
else:
245+
raise ResultError("Summary not available until the entire result has been consumed")
238246

239247
def _consume(self):
240248
# Consume the remainder of this result, triggering all appropriate callback functions.
241-
fetch_next = self._connection.fetch_next
249+
fetch = self._connection.fetch
242250
while not self._consumed:
243-
fetch_next()
251+
fetch()
244252

245253
def _on_header(self, metadata):
246254
# Called on receipt of the result header.
247255
self._keys = metadata["fields"]
248256

249257
def _on_record(self, values):
250258
# Called on receipt of each result record.
251-
self._next.append(values)
259+
self._record_buffer.append(values)
252260

253261
def _on_footer(self, metadata):
254262
# Called on receipt of the result footer.
@@ -274,8 +282,8 @@ class ResultSummary(object):
274282
#: The type of statement (``'r'`` = read-only, ``'rw'`` = read/write).
275283
statement_type = None
276284

277-
#: A set of statistical information held in a :class:`.StatementStatistics` instance.
278-
statistics = None
285+
#: A set of statistical information held in a :class:`.Counters` instance.
286+
counters = None
279287

280288
#: A :class:`.Plan` instance
281289
plan = None
@@ -293,7 +301,7 @@ def __init__(self, statement, parameters, **metadata):
293301
self.statement = statement
294302
self.parameters = parameters
295303
self.statement_type = metadata.get("type")
296-
self.statistics = StatementStatistics(metadata.get("stats", {}))
304+
self.counters = Counters(metadata.get("stats", {}))
297305
if "plan" in metadata:
298306
self.plan = make_plan(metadata["plan"])
299307
if "profile" in metadata:
@@ -308,7 +316,7 @@ def __init__(self, statement, parameters, **metadata):
308316
notification["description"], position))
309317

310318

311-
class StatementStatistics(object):
319+
class Counters(object):
312320
""" Set of statistics from a Cypher statement execution.
313321
"""
314322

runtests.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ 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-
BEHAVE_RUNNER="behave --tags=-db,-in_dev test/tck"
65+
EXAMPLES_RUNNER="coverage run -m ${UNITTEST} discover -vfs examples"
66+
BEHAVE_RUNNER="behave --tags=-db --tags=-in_dev test/tck"
6667
if [ ${RUNNING} -eq 1 ]
6768
then
6869
${TEST_RUNNER}

test/tck/configure_feature_files.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,4 @@ def _download_tar(url, file_name):
5656
tar.close()
5757
except ImportError:
5858
from urllib import request
59-
request.urlretrieve(url, file_name)
59+
request.urlretrieve(url, file_name)

test/tck/environment.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,15 @@ def before_all(context):
2727

2828
def before_feature(context, feature):
2929
# Workaround. Behave has a different way of tagging than cucumber
30-
if "reset_database" in feature.tags:
31-
for scenario in feature.scenarios:
32-
scenario.tags.append("reset_database")
33-
30+
for scenario in feature.scenarios:
31+
scenario.tags += feature.tags
3432

3533
def before_scenario(context, scenario):
3634
if "reset_database" in scenario.tags:
37-
tck_util.send_string("MATCH (n) DETACH DELETE n")
35+
tck_util.send_string("MATCH (n) DETACH DELETE n")
36+
37+
38+
def after_scenario(context, scenario):
39+
if scenario.status != "passed":
40+
raise Exception("%s did not pass" %scenario)
41+

0 commit comments

Comments
 (0)