From ec1c2235e03ffd603b88fe4f3edefdfa9f87ed50 Mon Sep 17 00:00:00 2001 From: Zhen Date: Thu, 27 Apr 2017 14:57:49 +0200 Subject: [PATCH 1/4] Revert "Merge pull request #161 from zhenlineo/1.2-without-examples" This reverts commit 64cbfa595b453348611f5e4840289bd12144dc9f, reversing changes made to 6574439c303c2f4dd77ad44ca10634cc183661ef. --- neo4j/v1/api.py | 6 +- neo4j/v1/session.py | 7 +- test/examples/__main__.py | 25 +++ .../autocommit_transaction_example.py | 34 ++++ test/examples/base_application.py | 28 +++ test/examples/basic_auth_example.py | 36 ++++ .../config_connection_timeout_example.py | 33 ++++ .../examples/config_max_retry_time_example.py | 33 ++++ test/examples/config_trust_example.py | 33 ++++ test/examples/config_unencrypted_example.py | 32 +++ test/examples/custom_auth_example.py | 32 +++ test/examples/cypher_error_example.py | 42 ++++ test/examples/driver_lifecycle_example.py | 32 +++ test/examples/hello_world_example.py | 46 +++++ .../read_write_transaction_example.py | 43 ++++ test/examples/result_consume_example.py | 43 ++++ test/examples/result_retain_example.py | 54 ++++++ test/examples/service_unavailable_example.py | 38 ++++ test/examples/session_example.py | 33 ++++ test/examples/test_examples.py | 183 ++++++++++++++++++ test/examples/transaction_function_example.py | 39 ++++ 21 files changed, 847 insertions(+), 5 deletions(-) create mode 100644 test/examples/__main__.py create mode 100644 test/examples/autocommit_transaction_example.py create mode 100644 test/examples/base_application.py create mode 100644 test/examples/basic_auth_example.py create mode 100644 test/examples/config_connection_timeout_example.py create mode 100644 test/examples/config_max_retry_time_example.py create mode 100644 test/examples/config_trust_example.py create mode 100644 test/examples/config_unencrypted_example.py create mode 100644 test/examples/custom_auth_example.py create mode 100644 test/examples/cypher_error_example.py create mode 100644 test/examples/driver_lifecycle_example.py create mode 100644 test/examples/hello_world_example.py create mode 100644 test/examples/read_write_transaction_example.py create mode 100644 test/examples/result_consume_example.py create mode 100644 test/examples/result_retain_example.py create mode 100644 test/examples/service_unavailable_example.py create mode 100644 test/examples/session_example.py create mode 100644 test/examples/test_examples.py create mode 100644 test/examples/transaction_function_example.py diff --git a/neo4j/v1/api.py b/neo4j/v1/api.py index b90453625..0dd78e9a2 100644 --- a/neo4j/v1/api.py +++ b/neo4j/v1/api.py @@ -22,7 +22,7 @@ from collections import deque from random import random from threading import RLock -from time import clock, sleep +from time import time, sleep from warnings import warn from neo4j.bolt import ProtocolError, ServiceUnavailable @@ -422,7 +422,7 @@ def _run_transaction(self, access_mode, unit_of_work, *args, **kwargs): RETRY_DELAY_MULTIPLIER, RETRY_DELAY_JITTER_FACTOR) last_error = None - t0 = t1 = clock() + t0 = t1 = time() while t1 - t0 <= self._max_retry_time: try: self._create_transaction() @@ -438,7 +438,7 @@ def _run_transaction(self, access_mode, unit_of_work, *args, **kwargs): else: raise error sleep(next(retry_delay)) - t1 = clock() + t1 = time() raise last_error def read_transaction(self, unit_of_work, *args, **kwargs): diff --git a/neo4j/v1/session.py b/neo4j/v1/session.py index 855f9860a..b3cbd1111 100644 --- a/neo4j/v1/session.py +++ b/neo4j/v1/session.py @@ -47,8 +47,11 @@ def __run__(self, statement, parameters): result.statement = statement result.parameters = parameters - self._connection.append(RUN, (statement, parameters), response=run_response) - self._connection.append(PULL_ALL, response=pull_all_response) + try: + self._connection.append(RUN, (statement, parameters), response=run_response) + self._connection.append(PULL_ALL, response=pull_all_response) + except AttributeError: + pass return result diff --git a/test/examples/__main__.py b/test/examples/__main__.py new file mode 100644 index 000000000..9487b7a0e --- /dev/null +++ b/test/examples/__main__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +if __name__ == "__main__": + from os.path import dirname + from test.tools import run_tests + run_tests(dirname(__file__)) diff --git a/test/examples/autocommit_transaction_example.py b/test/examples/autocommit_transaction_example.py new file mode 100644 index 000000000..71525d078 --- /dev/null +++ b/test/examples/autocommit_transaction_example.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::autocommit-transaction-import[] +from neo4j.v1 import Session; +from base_application import BaseApplication +# end::autocommit-transaction-import[] + +class AutocommitTransactionExample(BaseApplication): + def __init__(self, uri, user, password): + super(AutocommitTransactionExample, self).__init__(uri, user, password) + + # tag::autocommit-transaction[] + def add_person(self, name): + session = self._driver.session() + session.run( "CREATE (a:Person {name: $name})", {"name": name} ) + # end::autocommit-transaction[] diff --git a/test/examples/base_application.py b/test/examples/base_application.py new file mode 100644 index 000000000..5d0acab85 --- /dev/null +++ b/test/examples/base_application.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from neo4j.v1 import GraphDatabase + +class BaseApplication(object): + def __init__(self, uri, user, password): + self._driver = GraphDatabase.driver( uri, auth=( user, password ) ) + + def close(self): + self._driver.close(); diff --git a/test/examples/basic_auth_example.py b/test/examples/basic_auth_example.py new file mode 100644 index 000000000..98e08689d --- /dev/null +++ b/test/examples/basic_auth_example.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::basic-auth-import[] +from neo4j.v1 import GraphDatabase +# end::basic-auth-import[] + +class BasicAuthExample: + # tag::basic-auth[] + def __init__(self, uri, user, password): + self._driver = GraphDatabase.driver(uri, auth=(user, password)) + # end::basic-auth[] + + def close(self): + self._driver.close() + + def can_connect(self): + record_list = list(self._driver.session().run("RETURN 1")) + return int(record_list[0][0]) == 1 diff --git a/test/examples/config_connection_timeout_example.py b/test/examples/config_connection_timeout_example.py new file mode 100644 index 000000000..1e6395214 --- /dev/null +++ b/test/examples/config_connection_timeout_example.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::config-connection-timeout-import[] +from neo4j.v1 import GraphDatabase +# end::config-connection-timeout-import[] + +class ConfigConnectionTimeoutExample: + # tag::config-connection-timeout[] + def __init__(self, uri, user, password): + self._driver = GraphDatabase.driver( uri, auth=( user, password ), + Config.build().withConnectionTimeout( 15, SECONDS ).toConfig() ) + # end::config-connection-timeout[] + + def close(self): + self._driver.close(); diff --git a/test/examples/config_max_retry_time_example.py b/test/examples/config_max_retry_time_example.py new file mode 100644 index 000000000..a9b82593c --- /dev/null +++ b/test/examples/config_max_retry_time_example.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::config-max-retry-time-import[] +from neo4j.v1 import GraphDatabase +# end::config-max-retry-time-import[] + +class ConfigMaxRetryTimeExample: + # tag::config-max-retry-time[] + def __init__(self, uri, user, password): + self._driver = GraphDatabase.driver(uri, auth=(user, password), + Config.build().withMaxTransactionRetryTime( 15, SECONDS ).toConfig() ) + # end::config-max-retry-time[] + + def close(self): + self._driver.close(); diff --git a/test/examples/config_trust_example.py b/test/examples/config_trust_example.py new file mode 100644 index 000000000..9fa4ee457 --- /dev/null +++ b/test/examples/config_trust_example.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::config-trust-import[] +from neo4j.v1 import GraphDatabase +# end::config-trust-import[] + +class ConfigTrustExample: + # tag::config-trust[] + def __init__(self, uri, user, password): + self._driver = GraphDatabase.driver(uri, auth=(user, password), + Config.build().withTrustStrategy( Config.TrustStrategy.trustSystemCertificates() ).toConfig() ) + # end::config-trust[] + + def close(self): + self._driver.close(); diff --git a/test/examples/config_unencrypted_example.py b/test/examples/config_unencrypted_example.py new file mode 100644 index 000000000..f888e90de --- /dev/null +++ b/test/examples/config_unencrypted_example.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::config-unencrypted-import[] +from neo4j.v1 import GraphDatabase +# end::config-unencrypted-import[] + +class ConfigUnencryptedExample: + # tag::config-unencrypted[] + def __init__(self, uri, user, password): + self._driver = GraphDatabase.driver(uri, auth=(user, password), encrypted=False) + # end::config-unencrypted[] + + def close(self): + self._driver.close(); diff --git a/test/examples/custom_auth_example.py b/test/examples/custom_auth_example.py new file mode 100644 index 000000000..ac46bb05e --- /dev/null +++ b/test/examples/custom_auth_example.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::custom-auth-import[] +from neo4j.v1 import GraphDatabase; +# end::custom-auth-import[] + +class CustomAuthExample: + # tag::custom-auth[] + def __init__(self, uri, principal, credentials, realm, scheme, parameters): + self._driver = GraphDatabase.driver( uri, auth=(principal, credentials, realm, scheme, parameters)) + # end::custom-auth[] + + def close(self): + self._driver.close() diff --git a/test/examples/cypher_error_example.py b/test/examples/cypher_error_example.py new file mode 100644 index 000000000..ea75c00f0 --- /dev/null +++ b/test/examples/cypher_error_example.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::cypher-error-import[] +from neo4j.v1 import GraphDatabase, ClientError +from base_application import BaseApplication +# end::cypher-error-import[] + +class CypherErrorExample(BaseApplication): + def __init__(self, uri, user, password): + super(CypherErrorExample, self).__init__(uri, user, password) + + # tag::cypher-error[] + def get_employee_number(self, name): + session = self._driver.session() + session.read_transaction(lambda tx: self.select_employee(tx, name)) + + def select_employee(self, tx, name): + try: + record_list = list(tx.run("SELECT * FROM Employees WHERE name = $name", {"name": name})) + return int(record_list[0]["employee_number"]) + except ClientError as e: + print(e.message) + return -1 + # end::cypher-error[] diff --git a/test/examples/driver_lifecycle_example.py b/test/examples/driver_lifecycle_example.py new file mode 100644 index 000000000..cc1506488 --- /dev/null +++ b/test/examples/driver_lifecycle_example.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::driver-lifecycle-import[] +from neo4j.v1 import GraphDatabase +# end::driver-lifecycle-import[] + +# tag::driver-lifecycle[] +class DriverLifecycleExample: + def __init__(self, uri, user, password): + self._driver = GraphDatabase.driver(uri, auth=(user, password)) + + def close(self): + self._driver.close(); +# end::driver-lifecycle[] diff --git a/test/examples/hello_world_example.py b/test/examples/hello_world_example.py new file mode 100644 index 000000000..d9e3df8be --- /dev/null +++ b/test/examples/hello_world_example.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::hello-world-import[] +from neo4j.v1 import GraphDatabase +from base_application import BaseApplication +# end::hello-world-import[] + +# tag::hello-world[] +class HelloWorldExample(BaseApplication): + def __init__(self, uri, user, password): + super(HelloWorldExample, self).__init__(uri, user, password) + + def _create_and_return_greeting(self, tx, message): + record_list = list(tx.run("CREATE (a:Greeting) " + + "SET a.message = $message " + + "RETURN a.message + ', from node ' + id(a)", + {"message": message})) + return str(record_list[0][0]) + + def print_greeting(self, message): + with self._driver.session() as session: + greeting = session.write_transaction(lambda tx: self._create_and_return_greeting(tx, message)) + print(greeting) +# end::hello-world[] + +# tag::hello-world-output[] +# hello, world, from node 1234 +# end::hello-world-output[] diff --git a/test/examples/read_write_transaction_example.py b/test/examples/read_write_transaction_example.py new file mode 100644 index 000000000..ec5bef984 --- /dev/null +++ b/test/examples/read_write_transaction_example.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::read-write-transaction-import[] +from neo4j.v1 import GraphDatabase +from base_application import BaseApplication +# end::read-write-transaction-import[] + +class ReadWriteTransactionExample(BaseApplication): + def __init__(self, uri, user, password): + super(ReadWriteTransactionExample, self).__init__(uri, user, password) + + # tag::read-write-transaction[] + def add_person(self, name): + with self._driver.session() as session: + session.write_transaction(lambda tx: self.create_person_node(tx, name)) + return session.read_transaction(lambda tx: self.match_person_node(tx, name)) + + def create_person_node(self, tx, name): + tx.run("CREATE (a:Person {name: $name})", {"name": name }) + return None + + def match_person_node(self, tx, name): + record_list = list(tx.run("MATCH (a:Person {name: $name}) RETURN count(a)", {"name": name })) + return int(record_list[0][0]) + # end::read-write-transaction[] diff --git a/test/examples/result_consume_example.py b/test/examples/result_consume_example.py new file mode 100644 index 000000000..02ffc2487 --- /dev/null +++ b/test/examples/result_consume_example.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::result-consume-import[] +from neo4j.v1 import GraphDatabase +from base_application import BaseApplication +# end::result-consume-import[] + +class ResultConsumeExample(BaseApplication): + def __init__(self, uri, user, password): + super(ResultConsumeExample, self).__init__(uri, user, password) + + # tag::result-consume[] + def get_people(self): + with self._driver.session() as session: + return session.read_transaction(self.match_person_nodes) + + def match_person_nodes(self, tx): + names = [] + results = tx.run("MATCH (a:Person) RETURN a.name ORDER BY a.name") + + for result in results: + names.append(result["a.name"]) + + return names + # end::result-consume[] diff --git a/test/examples/result_retain_example.py b/test/examples/result_retain_example.py new file mode 100644 index 000000000..ddf375357 --- /dev/null +++ b/test/examples/result_retain_example.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::result-retain-import[] +from neo4j.v1 import GraphDatabase +from base_application import BaseApplication +# end::result-retain-import[] + +class ResultRetainExample(BaseApplication): + def __init__(self, uri, user, password): + super(ResultRetainExample, self).__init__(uri, user, password) + + # tag::result-retain[] + def add_employees(self, company_name): + with self._driver.session() as session: + employees = 0 + persons = session.read_transaction(self.match_person_nodes) + + for person in persons: + num = session.write_transaction(self.add_employee_to_company(person, company_name)) + employees = employees + num + + return employees + + def add_employee_to_company(self, person, company_name): + def do_transaction(tx): + tx.run("MATCH (emp:Person {name: $person_name}) " + + "MERGE (com:Company {name: $company_name}) " + + "MERGE (emp)-[:WORKS_FOR]->(com)", + {"person_name": person["name"], + "company_name": company_name}) + return 1 + return do_transaction + + def match_person_nodes(self, tx): + return list(tx.run("MATCH (a:Person) RETURN a.name AS name")) + # end::result-retain[] diff --git a/test/examples/service_unavailable_example.py b/test/examples/service_unavailable_example.py new file mode 100644 index 000000000..7dba85f08 --- /dev/null +++ b/test/examples/service_unavailable_example.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::service-unavailable-import[] +from neo4j.v1 import GraphDatabase, ServiceUnavailable +from base_application import BaseApplication +# end::service-unavailable-import[] + +class ServiceUnavailableExample(BaseApplication): + def __init__(self, uri, user, password): + super(ServiceUnavailableExample, self).__init__(uri, user, password) + + # tag::service-unavailable[] + def addItem(self): + session = self._driver.session() + try: + session.write_transaction(lambda tx: tx.run("CREATE (a:Item)")) + return True + except ServiceUnavailable as e: + return False + # end::service-unavailable[] diff --git a/test/examples/session_example.py b/test/examples/session_example.py new file mode 100644 index 000000000..612416cd6 --- /dev/null +++ b/test/examples/session_example.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::session-import[] +from base_application import BaseApplication +# end::session-import[] + +class SessionExample(BaseApplication): + def __init__(self, uri, user, password): + super(SessionExample, self).__init__(uri, user, password) + + # tag::session[] + def do_work(self): + session = self._driver.session() + # TODO: something with the Session + # end::session[] diff --git a/test/examples/test_examples.py b/test/examples/test_examples.py new file mode 100644 index 000000000..564ab95b5 --- /dev/null +++ b/test/examples/test_examples.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from test.integration.tools import IntegrationTestCase + + +## Python2 doesn't have contextlib.redirect_stdout() +# http://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/ +import sys +from contextlib import contextmanager +@contextmanager +def stdout_redirector(stream): + old_stdout = sys.stdout + sys.stdout = stream + try: + yield + finally: + sys.stdout = old_stdout + +def get_string_io(): + if sys.version_info[0] < 3: + from StringIO import StringIO + else: + from io import StringIO + return StringIO() + +class ExamplesTest(IntegrationTestCase): + + def setUp(self): + self.clean() + + def test_autocommit_transaction_example(self): + from autocommit_transaction_example import AutocommitTransactionExample + + example = AutocommitTransactionExample(self.bolt_uri, self.user, self.password) + example.add_person('Alice') + + self.assertTrue(self.person_count('Alice') > 0) + + def test_basic_auth_example(self): + from basic_auth_example import BasicAuthExample + + example = BasicAuthExample(self.bolt_uri, self.user, self.password) + + self.assertTrue(example.can_connect()) + + def test_config_unencrypted_example(self): + from config_unencrypted_example import ConfigUnencryptedExample + + example = ConfigUnencryptedExample(self.bolt_uri, self.user, self.password) + + self.assertIsInstance(example, ConfigUnencryptedExample) + + def test_cypher_error_example(self): + from cypher_error_example import CypherErrorExample + + f = get_string_io() + with stdout_redirector(f): + example = CypherErrorExample(self.bolt_uri, self.user, self.password) + try: + example.get_employee_number('Alice') + except: + pass + + self.assertTrue(f.getvalue().startswith( + """Invalid input 'L': expected 't/T' (line 1, column 3 (offset: 2)) +"SELECT * FROM Employees WHERE name = $name" + ^""")) + + def test_driver_lifecycle_example(self): + from driver_lifecycle_example import DriverLifecycleExample + + example = DriverLifecycleExample(self.bolt_uri, self.user, self.password) + example.close() + + self.assertIsInstance(example, DriverLifecycleExample) + + def test_hello_world_example(self): + from hello_world_example import HelloWorldExample + + f = get_string_io() + with stdout_redirector(f): + example = HelloWorldExample(self.bolt_uri, self.user, self.password) + example.print_greeting("hello, world") + example.close() + + self.assertTrue(f.getvalue().startswith("hello, world, from node")) + + def test_read_write_transaction_example(self): + from read_write_transaction_example import ReadWriteTransactionExample + + example = ReadWriteTransactionExample(self.bolt_uri, self.user, self.password) + node_count = example.add_person('Alice') + + self.assertTrue(node_count > 0) + + def test_result_consume_example(self): + from result_consume_example import ResultConsumeExample + + self.write("CREATE (a:Person {name: 'Alice'})") + self.write("CREATE (a:Person {name: 'Bob'})") + example = ResultConsumeExample(self.bolt_uri, self.user, self.password) + people = list(example.get_people()) + + self.assertEqual(['Alice', 'Bob'], people) + + def test_result_retain_example(self): + from result_retain_example import ResultRetainExample + + self.write("CREATE (a:Person {name: 'Alice'})") + self.write("CREATE (a:Person {name: 'Bob'})") + example = ResultRetainExample(self.bolt_uri, self.user, self.password) + example.add_employees('Acme') + employee_count = self.read("MATCH (emp:Person)-[:WORKS_FOR]->(com:Company) WHERE com.name = 'Acme' RETURN count(emp)").single()[0] + + self.assertEqual(employee_count, 2) + + def test_session_example(self): + from session_example import SessionExample + + example = SessionExample(self.bolt_uri, self.user, self.password) + example.do_work() + + self.assertIsInstance(example, SessionExample) + + def test_transaction_function_example(self): + from transaction_function_example import TransactionFunctionExample + + example = TransactionFunctionExample(self.bolt_uri, self.user, self.password) + example.add_person("Alice") + + self.assertEqual(self.person_count("Alice"), 1) + + + def read(self, statement): + from neo4j.v1 import GraphDatabase + with GraphDatabase.driver(self.bolt_uri, auth=self.auth_token) as driver: + with driver.session() as session: + return session.read_transaction(lambda tx: tx.run(statement)) + + def write(self, statement): + from neo4j.v1 import GraphDatabase + with GraphDatabase.driver(self.bolt_uri, auth=self.auth_token) as driver: + with driver.session() as session: + return session.write_transaction(lambda tx: tx.run(statement)) + + def clean(self): + self.write("MATCH (a) DETACH DELETE a") + + def person_count(self, name): + from neo4j.v1 import GraphDatabase + with GraphDatabase.driver(self.bolt_uri, auth=self.auth_token) as driver: + with driver.session() as session: + record_list = list(session.run("MATCH (a:Person {name: $name}) RETURN count(a)", {"name": name})) + return len(record_list) + + +class ServiceUnavailableTest(IntegrationTestCase): + + def test_service_unavailable_example(self): + from service_unavailable_example import ServiceUnavailableExample + + example = ServiceUnavailableExample(self.bolt_uri, self.user, self.password) + self.__class__._stop_server() + + self.assertFalse(example.addItem()) diff --git a/test/examples/transaction_function_example.py b/test/examples/transaction_function_example.py new file mode 100644 index 000000000..29893a2db --- /dev/null +++ b/test/examples/transaction_function_example.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) 2002-2017 "Neo Technology," +# Network Engine for Objects in Lund AB [http://neotechnology.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# tag::transaction-function-import[] +from neo4j.v1 import GraphDatabase +from base_application import BaseApplication +# end::transaction-function-import[] + +class TransactionFunctionExample(BaseApplication): + def __init__(self, uri, user, password): + super(TransactionFunctionExample, self).__init__(uri, user, password) + + # tag::transaction-function[] + def add_person(self, name): + with self._driver.session() as session: + session.write_transaction(lambda tx: self.create_person_node(tx, name)) + + def create_person_node(self, tx, name): + tx.run("CREATE (a:Person {name: $name})", {"name": name}) + return 1 + # end::transaction-function[] + From a09de9a5e7bf27e6b4bde1b5731610007033cbe4 Mon Sep 17 00:00:00 2001 From: Zhen Date: Thu, 27 Apr 2017 11:25:25 +0200 Subject: [PATCH 2/4] Fix the import problem on 3.4 Removed some test assumption that is no longer true for server 3.2 to avoid test error --- .../autocommit_transaction_example.py | 2 +- test/examples/cypher_error_example.py | 2 +- test/examples/hello_world_example.py | 2 +- .../read_write_transaction_example.py | 2 +- test/examples/result_consume_example.py | 2 +- test/examples/result_retain_example.py | 2 +- test/examples/service_unavailable_example.py | 2 +- test/examples/session_example.py | 2 +- test/examples/test_examples.py | 24 +- test/examples/transaction_function_example.py | 2 +- test/integration/test_examples.py | 232 ------------------ test/integration/test_session.py | 6 - 12 files changed, 21 insertions(+), 259 deletions(-) delete mode 100644 test/integration/test_examples.py diff --git a/test/examples/autocommit_transaction_example.py b/test/examples/autocommit_transaction_example.py index 71525d078..57248908a 100644 --- a/test/examples/autocommit_transaction_example.py +++ b/test/examples/autocommit_transaction_example.py @@ -20,7 +20,7 @@ # tag::autocommit-transaction-import[] from neo4j.v1 import Session; -from base_application import BaseApplication +from test.examples.base_application import BaseApplication # end::autocommit-transaction-import[] class AutocommitTransactionExample(BaseApplication): diff --git a/test/examples/cypher_error_example.py b/test/examples/cypher_error_example.py index ea75c00f0..6408d7017 100644 --- a/test/examples/cypher_error_example.py +++ b/test/examples/cypher_error_example.py @@ -20,7 +20,7 @@ # tag::cypher-error-import[] from neo4j.v1 import GraphDatabase, ClientError -from base_application import BaseApplication +from test.examples.base_application import BaseApplication # end::cypher-error-import[] class CypherErrorExample(BaseApplication): diff --git a/test/examples/hello_world_example.py b/test/examples/hello_world_example.py index d9e3df8be..dd5aab554 100644 --- a/test/examples/hello_world_example.py +++ b/test/examples/hello_world_example.py @@ -20,7 +20,7 @@ # tag::hello-world-import[] from neo4j.v1 import GraphDatabase -from base_application import BaseApplication +from test.examples.base_application import BaseApplication # end::hello-world-import[] # tag::hello-world[] diff --git a/test/examples/read_write_transaction_example.py b/test/examples/read_write_transaction_example.py index ec5bef984..fc67dc0cb 100644 --- a/test/examples/read_write_transaction_example.py +++ b/test/examples/read_write_transaction_example.py @@ -20,7 +20,7 @@ # tag::read-write-transaction-import[] from neo4j.v1 import GraphDatabase -from base_application import BaseApplication +from test.examples.base_application import BaseApplication # end::read-write-transaction-import[] class ReadWriteTransactionExample(BaseApplication): diff --git a/test/examples/result_consume_example.py b/test/examples/result_consume_example.py index 02ffc2487..2387e1dc5 100644 --- a/test/examples/result_consume_example.py +++ b/test/examples/result_consume_example.py @@ -20,7 +20,7 @@ # tag::result-consume-import[] from neo4j.v1 import GraphDatabase -from base_application import BaseApplication +from test.examples.base_application import BaseApplication # end::result-consume-import[] class ResultConsumeExample(BaseApplication): diff --git a/test/examples/result_retain_example.py b/test/examples/result_retain_example.py index ddf375357..9943e6c14 100644 --- a/test/examples/result_retain_example.py +++ b/test/examples/result_retain_example.py @@ -20,7 +20,7 @@ # tag::result-retain-import[] from neo4j.v1 import GraphDatabase -from base_application import BaseApplication +from test.examples.base_application import BaseApplication # end::result-retain-import[] class ResultRetainExample(BaseApplication): diff --git a/test/examples/service_unavailable_example.py b/test/examples/service_unavailable_example.py index 7dba85f08..4733edddd 100644 --- a/test/examples/service_unavailable_example.py +++ b/test/examples/service_unavailable_example.py @@ -20,7 +20,7 @@ # tag::service-unavailable-import[] from neo4j.v1 import GraphDatabase, ServiceUnavailable -from base_application import BaseApplication +from test.examples.base_application import BaseApplication # end::service-unavailable-import[] class ServiceUnavailableExample(BaseApplication): diff --git a/test/examples/session_example.py b/test/examples/session_example.py index 612416cd6..508c19062 100644 --- a/test/examples/session_example.py +++ b/test/examples/session_example.py @@ -19,7 +19,7 @@ # limitations under the License. # tag::session-import[] -from base_application import BaseApplication +from test.examples.base_application import BaseApplication # end::session-import[] class SessionExample(BaseApplication): diff --git a/test/examples/test_examples.py b/test/examples/test_examples.py index 564ab95b5..ce2202e8b 100644 --- a/test/examples/test_examples.py +++ b/test/examples/test_examples.py @@ -47,7 +47,7 @@ def setUp(self): self.clean() def test_autocommit_transaction_example(self): - from autocommit_transaction_example import AutocommitTransactionExample + from test.examples.autocommit_transaction_example import AutocommitTransactionExample example = AutocommitTransactionExample(self.bolt_uri, self.user, self.password) example.add_person('Alice') @@ -55,21 +55,21 @@ def test_autocommit_transaction_example(self): self.assertTrue(self.person_count('Alice') > 0) def test_basic_auth_example(self): - from basic_auth_example import BasicAuthExample + from test.examples.basic_auth_example import BasicAuthExample example = BasicAuthExample(self.bolt_uri, self.user, self.password) self.assertTrue(example.can_connect()) def test_config_unencrypted_example(self): - from config_unencrypted_example import ConfigUnencryptedExample + from test.examples.config_unencrypted_example import ConfigUnencryptedExample example = ConfigUnencryptedExample(self.bolt_uri, self.user, self.password) self.assertIsInstance(example, ConfigUnencryptedExample) def test_cypher_error_example(self): - from cypher_error_example import CypherErrorExample + from test.examples.cypher_error_example import CypherErrorExample f = get_string_io() with stdout_redirector(f): @@ -85,7 +85,7 @@ def test_cypher_error_example(self): ^""")) def test_driver_lifecycle_example(self): - from driver_lifecycle_example import DriverLifecycleExample + from test.examples.driver_lifecycle_example import DriverLifecycleExample example = DriverLifecycleExample(self.bolt_uri, self.user, self.password) example.close() @@ -93,7 +93,7 @@ def test_driver_lifecycle_example(self): self.assertIsInstance(example, DriverLifecycleExample) def test_hello_world_example(self): - from hello_world_example import HelloWorldExample + from test.examples.hello_world_example import HelloWorldExample f = get_string_io() with stdout_redirector(f): @@ -104,7 +104,7 @@ def test_hello_world_example(self): self.assertTrue(f.getvalue().startswith("hello, world, from node")) def test_read_write_transaction_example(self): - from read_write_transaction_example import ReadWriteTransactionExample + from test.examples.read_write_transaction_example import ReadWriteTransactionExample example = ReadWriteTransactionExample(self.bolt_uri, self.user, self.password) node_count = example.add_person('Alice') @@ -112,7 +112,7 @@ def test_read_write_transaction_example(self): self.assertTrue(node_count > 0) def test_result_consume_example(self): - from result_consume_example import ResultConsumeExample + from test.examples.result_consume_example import ResultConsumeExample self.write("CREATE (a:Person {name: 'Alice'})") self.write("CREATE (a:Person {name: 'Bob'})") @@ -122,7 +122,7 @@ def test_result_consume_example(self): self.assertEqual(['Alice', 'Bob'], people) def test_result_retain_example(self): - from result_retain_example import ResultRetainExample + from test.examples.result_retain_example import ResultRetainExample self.write("CREATE (a:Person {name: 'Alice'})") self.write("CREATE (a:Person {name: 'Bob'})") @@ -133,7 +133,7 @@ def test_result_retain_example(self): self.assertEqual(employee_count, 2) def test_session_example(self): - from session_example import SessionExample + from test.examples.session_example import SessionExample example = SessionExample(self.bolt_uri, self.user, self.password) example.do_work() @@ -141,7 +141,7 @@ def test_session_example(self): self.assertIsInstance(example, SessionExample) def test_transaction_function_example(self): - from transaction_function_example import TransactionFunctionExample + from test.examples.transaction_function_example import TransactionFunctionExample example = TransactionFunctionExample(self.bolt_uri, self.user, self.password) example.add_person("Alice") @@ -175,7 +175,7 @@ def person_count(self, name): class ServiceUnavailableTest(IntegrationTestCase): def test_service_unavailable_example(self): - from service_unavailable_example import ServiceUnavailableExample + from test.examples.service_unavailable_example import ServiceUnavailableExample example = ServiceUnavailableExample(self.bolt_uri, self.user, self.password) self.__class__._stop_server() diff --git a/test/examples/transaction_function_example.py b/test/examples/transaction_function_example.py index 29893a2db..28f90cb98 100644 --- a/test/examples/transaction_function_example.py +++ b/test/examples/transaction_function_example.py @@ -20,7 +20,7 @@ # tag::transaction-function-import[] from neo4j.v1 import GraphDatabase -from base_application import BaseApplication +from test.examples.base_application import BaseApplication # end::transaction-function-import[] class TransactionFunctionExample(BaseApplication): diff --git a/test/integration/test_examples.py b/test/integration/test_examples.py deleted file mode 100644 index cad8a3b30..000000000 --- a/test/integration/test_examples.py +++ /dev/null @@ -1,232 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- - -# Copyright (c) 2002-2017 "Neo Technology," -# Network Engine for Objects in Lund AB [http://neotechnology.com] -# -# This file is part of Neo4j. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from __future__ import print_function - -from unittest import skip, skipUnless - -from neo4j.v1 import TRUST_SIGNED_CERTIFICATES, SSL_AVAILABLE, CypherError -from test.integration.tools import IntegrationTestCase - -# Do not change the contents of this tagged section without good reason* -# tag::minimal-example-import[] -from neo4j.v1 import GraphDatabase as _GraphDatabase -# end::minimal-example-import[] -# (* "good reason" is defined as knowing what you are doing) - - -class GraphDatabase(_GraphDatabase): - - @classmethod - def driver(cls, uri, **config): - auth = (IntegrationTestCase.user, IntegrationTestCase.password) - return super(GraphDatabase, cls).driver(IntegrationTestCase.bolt_uri, auth=auth) - - -# Deliberately shadow the built-in print function to -# mute noise from example code. -def print(*args, **kwargs): - pass - - -class MinimalWorkingExampleTestCase(IntegrationTestCase): - - def test_minimal_working_example(self): - # tag::minimal-example[] - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password")) - session = driver.session() - - 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") - for record in result: - print("%s %s" % (record["title"], record["name"])) - - session.close() - # end::minimal-example[] - - -class ExamplesTestCase(IntegrationTestCase): - - def test_construct_driver(self): - # tag::construct-driver[] - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password")) - # end::construct-driver[] - return driver - - def test_configuration(self): - # tag::configuration[] - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password"), max_pool_size=10) - # end::configuration[] - return driver - - @skipUnless(SSL_AVAILABLE, "Bolt over TLS is not supported by this version of Python") - def test_tls_require_encryption(self): - # tag::tls-require-encryption[] - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password"), encrypted=True) - # end::tls-require-encryption[] - - @skip("testing verified certificates not yet supported ") - def test_tls_signed(self): - # tag::tls-signed[] - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password"), encrypted=True, trust=TRUST_SIGNED_CERTIFICATES) - # end::tls-signed[] - assert driver - - @skipUnless(SSL_AVAILABLE, "Bolt over TLS is not supported by this version of Python") - def test_connect_with_auth_disabled(self): - # tag::connect-with-auth-disabled[] - driver = GraphDatabase.driver("bolt://localhost:7687", encrypted=True) - # end::connect-with-auth-disabled[] - assert driver - - def test_statement(self): - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password")) - session = driver.session() - # tag::statement[] - result = session.run("CREATE (person:Person {name: {name}})", {"name": "Arthur"}) - # end::statement[] - result.consume() - session.close() - - def test_statement_without_parameters(self): - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password")) - session = driver.session() - # tag::statement-without-parameters[] - result = session.run("CREATE (person:Person {name: 'Arthur'})") - # end::statement-without-parameters[] - result.consume() - session.close() - - def test_result_traversal(self): - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password")) - session = driver.session() - # tag::result-traversal[] - search_term = "Sword" - result = session.run("MATCH (weapon:Weapon) WHERE weapon.name CONTAINS {term} " - "RETURN weapon.name", {"term": search_term}) - print("List of weapons called %r:" % search_term) - for record in result: - print(record["weapon.name"]) - # end::result-traversal[] - session.close() - - def test_access_record(self): - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password")) - session = driver.session() - # tag::access-record[] - search_term = "Arthur" - result = session.run("MATCH (weapon:Weapon) WHERE weapon.owner CONTAINS {term} " - "RETURN weapon.name, weapon.material, weapon.size", {"term": search_term}) - print("List of weapons owned by %r:" % search_term) - for record in result: - print(", ".join("%s: %s" % (key, record[key]) for key in record.keys())) - # end::access-record[] - session.close() - - def test_result_retention(self): - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password")) - # tag::retain-result[] - session = driver.session() - result = session.run("MATCH (knight:Person:Knight) WHERE knight.castle = {castle} " - "RETURN knight.name AS name", {"castle": "Camelot"}) - retained_result = list(result) - session.close() - for record in retained_result: - print("%s is a knight of Camelot" % record["name"]) - # end::retain-result[] - assert isinstance(retained_result, list) - - def test_nested_statements(self): - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password")) - session = driver.session() - # tag::nested-statements[] - result = session.run("MATCH (knight:Person:Knight) WHERE knight.castle = {castle} " - "RETURN id(knight) AS knight_id", {"castle": "Camelot"}) - for record in 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"}) - # end::nested-statements[] - session.close() - - def test_transaction_commit(self): - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password")) - session = driver.session() - session.run("MATCH (p:Person {name: 'Guinevere'}) DELETE p") - # tag::transaction-commit[] - with session.begin_transaction() as tx: - tx.run("CREATE (:Person {name: 'Guinevere'})") - tx.success = True - # end::transaction-commit[] - result = session.run("MATCH (p:Person {name: 'Guinevere'}) RETURN count(p)") - record = next(iter(result)) - assert record["count(p)"] == 1 - session.close() - - def test_transaction_rollback(self): - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password")) - session = driver.session() - # tag::transaction-rollback[] - with session.begin_transaction() as tx: - tx.run("CREATE (:Person {name: 'Merlin'})") - tx.success = False - # end::transaction-rollback[] - result = session.run("MATCH (p:Person {name: 'Merlin'}) RETURN count(p)") - record = next(iter(result)) - assert record["count(p)"] == 0 - session.close() - - def test_result_summary_query_profile(self): - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password")) - session = driver.session() - # tag::result-summary-query-profile[] - result = session.run("PROFILE MATCH (p:Person {name: {name}}) " - "RETURN id(p)", {"name": "Arthur"}) - summary = result.consume() - print(summary.statement_type) - print(summary.profile) - # end::result-summary-query-profile[] - session.close() - - def test_result_summary_notifications(self): - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password")) - session = driver.session() - # tag::result-summary-notifications[] - result = session.run("EXPLAIN MATCH (king), (queen) RETURN king, queen") - summary = result.consume() - for notification in summary.notifications: - print(notification) - # end::result-summary-notifications[] - session.close() - - def test_handle_cypher_error(self): - driver = GraphDatabase.driver("bolt://localhost:7687", auth=("user", "password")) - session = driver.session() - with self.assertRaises(RuntimeError): - # tag::handle-cypher-error[] - try: - session.run("This will cause a syntax error").consume() - except CypherError: - raise RuntimeError("Something really bad has happened!") - finally: - session.close() - # end::handle-cypher-error[] diff --git a/test/integration/test_session.py b/test/integration/test_session.py index bff256e41..3df010079 100644 --- a/test/integration/test_session.py +++ b/test/integration/test_session.py @@ -180,9 +180,6 @@ def test_can_obtain_plan_info(self): plan = summary.plan assert plan.operator_type == "ProduceResults" assert plan.identifiers == ["n"] - known_keys = ["planner", "EstimatedRows", "version", "KeyNames", "runtime-impl", - "planner-impl", "runtime"] - assert all(key in plan.arguments for key in known_keys) assert len(plan.children) == 1 def test_can_obtain_profile_info(self): @@ -194,9 +191,6 @@ def test_can_obtain_profile_info(self): assert profile.rows == 1 assert profile.operator_type == "ProduceResults" assert profile.identifiers == ["n"] - known_keys = ["planner", "EstimatedRows", "version", "KeyNames", "runtime-impl", - "planner-impl", "runtime", "Rows", "DbHits"] - assert all(key in profile.arguments for key in known_keys) assert len(profile.children) == 1 def test_no_notification_info(self): From 6297ea6729d84b912dd3819557d6f5ea099f3520 Mon Sep 17 00:00:00 2001 From: Zhen Date: Thu, 27 Apr 2017 11:31:09 +0200 Subject: [PATCH 3/4] Refine the session example to really do some work --- test/examples/session_example.py | 4 ++-- test/examples/test_examples.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/examples/session_example.py b/test/examples/session_example.py index 508c19062..cf42257fb 100644 --- a/test/examples/session_example.py +++ b/test/examples/session_example.py @@ -27,7 +27,7 @@ def __init__(self, uri, user, password): super(SessionExample, self).__init__(uri, user, password) # tag::session[] - def do_work(self): + def add_person(self, name): session = self._driver.session() - # TODO: something with the Session + session.run("CREATE (a:Person {name: $name})", {"name": name}) # end::session[] diff --git a/test/examples/test_examples.py b/test/examples/test_examples.py index ce2202e8b..3b4edc9b1 100644 --- a/test/examples/test_examples.py +++ b/test/examples/test_examples.py @@ -136,9 +136,10 @@ def test_session_example(self): from test.examples.session_example import SessionExample example = SessionExample(self.bolt_uri, self.user, self.password) - example.do_work() + example.add_person("Alice") self.assertIsInstance(example, SessionExample) + self.assertEqual(self.person_count("Alice"), 1) def test_transaction_function_example(self): from test.examples.transaction_function_example import TransactionFunctionExample From da58ba2b1dfef093cf5a675766d462f951fc5459 Mon Sep 17 00:00:00 2001 From: Zhen Date: Wed, 3 May 2017 12:33:27 +0200 Subject: [PATCH 4/4] Fixed the infinite running error Removed some old stale files in the resources folder There is an infinite loop in `session._begin_tx` method where a tx is created before a conn is really established. As a result, if the server went down before the session is closed, then in `session._close`, the session will see a tx should be rolled back and blocks infinitly on `tx._roll_back.consume` as no record will ever return. This PR makes sure that in `session._begin_tx`, a tx is created after a conn is established. --- neo4j/bolt/connection.py | 7 ++++++- neo4j/v1/api.py | 2 +- test/examples/base_application.py | 2 +- test/examples/service_unavailable_example.py | 2 +- test/resources/broken_router.script | 9 --------- test/resources/create_a.script | 7 ------- test/resources/disconnect_on_pull_all.script | 6 ------ test/resources/disconnect_on_run.script | 5 ----- test/resources/empty.script | 2 -- test/resources/non_router.script | 9 --------- test/resources/return_1.script | 8 -------- test/resources/router.script | 8 -------- test/resources/router_no_readers.script | 8 -------- test/resources/router_no_routers.script | 8 -------- test/resources/router_no_writers.script | 8 -------- test/resources/router_with_multiple_writers.script | 8 -------- test/resources/rude_router.script | 7 ------- test/resources/silent_router.script | 7 ------- .../scripts/disconnect_after_init.script} | 3 ++- test/stub/test_directdriver.py | 8 ++++++++ 20 files changed, 19 insertions(+), 105 deletions(-) delete mode 100644 test/resources/broken_router.script delete mode 100644 test/resources/create_a.script delete mode 100644 test/resources/disconnect_on_pull_all.script delete mode 100644 test/resources/disconnect_on_run.script delete mode 100644 test/resources/empty.script delete mode 100644 test/resources/non_router.script delete mode 100644 test/resources/return_1.script delete mode 100644 test/resources/router.script delete mode 100644 test/resources/router_no_readers.script delete mode 100644 test/resources/router_no_routers.script delete mode 100644 test/resources/router_no_writers.script delete mode 100644 test/resources/router_with_multiple_writers.script delete mode 100644 test/resources/rude_router.script delete mode 100644 test/resources/silent_router.script rename test/{resources/fail_on_init.script => stub/scripts/disconnect_after_init.script} (54%) diff --git a/neo4j/bolt/connection.py b/neo4j/bolt/connection.py index 3b550ea68..8b8aa3629 100644 --- a/neo4j/bolt/connection.py +++ b/neo4j/bolt/connection.py @@ -458,6 +458,8 @@ def connect(address, ssl_context=None, **config): raise ServiceUnavailable("Failed to establish connection to {!r}".format(address)) else: raise + except ConnectionResetError: + raise ServiceUnavailable("Failed to establish connection to {!r}".format(address)) # Secure the connection if an SSL context has been provided if ssl_context and SSL_AVAILABLE: @@ -500,7 +502,10 @@ def connect(address, ssl_context=None, **config): ready_to_read, _, _ = select((s,), (), (), 0) while not ready_to_read: ready_to_read, _, _ = select((s,), (), (), 0) - data = s.recv(4) + try: + data = s.recv(4) + except ConnectionResetError: + raise ServiceUnavailable("Failed to read any data from server {!r} after connected".format(address)) data_size = len(data) if data_size == 0: # If no data is returned after a successful select diff --git a/neo4j/v1/api.py b/neo4j/v1/api.py index 0dd78e9a2..3c23d154c 100644 --- a/neo4j/v1/api.py +++ b/neo4j/v1/api.py @@ -425,8 +425,8 @@ def _run_transaction(self, access_mode, unit_of_work, *args, **kwargs): t0 = t1 = time() while t1 - t0 <= self._max_retry_time: try: - self._create_transaction() self._connect(access_mode) + self._create_transaction() self.__begin__() with self._transaction as tx: return unit_of_work(tx, *args, **kwargs) diff --git a/test/examples/base_application.py b/test/examples/base_application.py index 5d0acab85..1729154be 100644 --- a/test/examples/base_application.py +++ b/test/examples/base_application.py @@ -25,4 +25,4 @@ def __init__(self, uri, user, password): self._driver = GraphDatabase.driver( uri, auth=( user, password ) ) def close(self): - self._driver.close(); + self._driver.close() diff --git a/test/examples/service_unavailable_example.py b/test/examples/service_unavailable_example.py index 4733edddd..82b9fe7f3 100644 --- a/test/examples/service_unavailable_example.py +++ b/test/examples/service_unavailable_example.py @@ -33,6 +33,6 @@ def addItem(self): try: session.write_transaction(lambda tx: tx.run("CREATE (a:Item)")) return True - except ServiceUnavailable as e: + except ServiceUnavailable: return False # end::service-unavailable[] diff --git a/test/resources/broken_router.script b/test/resources/broken_router.script deleted file mode 100644 index 574d2417d..000000000 --- a/test/resources/broken_router.script +++ /dev/null @@ -1,9 +0,0 @@ -!: AUTO INIT -!: AUTO RESET - -C: RUN "CALL dbms.cluster.routing.getServers" {} - PULL_ALL -S: FAILURE {"code": "Neo.X", "message": "X"} - IGNORED -C: ACK_FAILURE -S: SUCCESS {} diff --git a/test/resources/create_a.script b/test/resources/create_a.script deleted file mode 100644 index a57cd269f..000000000 --- a/test/resources/create_a.script +++ /dev/null @@ -1,7 +0,0 @@ -!: AUTO INIT -!: AUTO RESET - -C: RUN "CREATE (a $x)" {"x": {"name": "Alice"}} - PULL_ALL -S: SUCCESS {"fields": []} - SUCCESS {} diff --git a/test/resources/disconnect_on_pull_all.script b/test/resources/disconnect_on_pull_all.script deleted file mode 100644 index 5865a61df..000000000 --- a/test/resources/disconnect_on_pull_all.script +++ /dev/null @@ -1,6 +0,0 @@ -!: AUTO INIT -!: AUTO RESET - -C: RUN "RETURN $x" {"x": 1} - PULL_ALL -S: diff --git a/test/resources/disconnect_on_run.script b/test/resources/disconnect_on_run.script deleted file mode 100644 index 35c827666..000000000 --- a/test/resources/disconnect_on_run.script +++ /dev/null @@ -1,5 +0,0 @@ -!: AUTO INIT -!: AUTO RESET - -C: RUN "RETURN $x" {"x": 1} -S: diff --git a/test/resources/empty.script b/test/resources/empty.script deleted file mode 100644 index 8cb5e929e..000000000 --- a/test/resources/empty.script +++ /dev/null @@ -1,2 +0,0 @@ -!: AUTO INIT -!: AUTO RESET diff --git a/test/resources/non_router.script b/test/resources/non_router.script deleted file mode 100644 index 26ca2638a..000000000 --- a/test/resources/non_router.script +++ /dev/null @@ -1,9 +0,0 @@ -!: AUTO INIT -!: AUTO RESET - -C: RUN "CALL dbms.cluster.routing.getServers" {} - PULL_ALL -S: FAILURE {"code": "Neo.ClientError.Procedure.ProcedureNotFound", "message": "Not a router"} - IGNORED -C: ACK_FAILURE -S: SUCCESS {} diff --git a/test/resources/return_1.script b/test/resources/return_1.script deleted file mode 100644 index 7d12a1bde..000000000 --- a/test/resources/return_1.script +++ /dev/null @@ -1,8 +0,0 @@ -!: AUTO INIT -!: AUTO RESET - -C: RUN "RETURN $x" {"x": 1} - PULL_ALL -S: SUCCESS {"fields": ["x"]} - RECORD [1] - SUCCESS {} diff --git a/test/resources/router.script b/test/resources/router.script deleted file mode 100644 index 208831ca1..000000000 --- a/test/resources/router.script +++ /dev/null @@ -1,8 +0,0 @@ -!: AUTO INIT -!: AUTO RESET - -C: RUN "CALL dbms.cluster.routing.getServers" {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [300, [{"role":"ROUTE","addresses":["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"]},{"role":"READ","addresses":["127.0.0.1:9004","127.0.0.1:9005"]},{"role":"WRITE","addresses":["127.0.0.1:9006"]}]] - SUCCESS {} diff --git a/test/resources/router_no_readers.script b/test/resources/router_no_readers.script deleted file mode 100644 index 85ab1fca5..000000000 --- a/test/resources/router_no_readers.script +++ /dev/null @@ -1,8 +0,0 @@ -!: AUTO INIT -!: AUTO RESET - -C: RUN "CALL dbms.cluster.routing.getServers" {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [300, [{"role":"ROUTE","addresses":["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"]},{"role":"READ","addresses":[]},{"role":"WRITE","addresses":["127.0.0.1:9006"]}]] - SUCCESS {} diff --git a/test/resources/router_no_routers.script b/test/resources/router_no_routers.script deleted file mode 100644 index 26d4f82c8..000000000 --- a/test/resources/router_no_routers.script +++ /dev/null @@ -1,8 +0,0 @@ -!: AUTO INIT -!: AUTO RESET - -C: RUN "CALL dbms.cluster.routing.getServers" {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [300, [{"role":"ROUTE","addresses":[]},{"role":"READ","addresses":["127.0.0.1:9004","127.0.0.1:9005"]},{"role":"WRITE","addresses":["127.0.0.1:9006"]}]] - SUCCESS {} diff --git a/test/resources/router_no_writers.script b/test/resources/router_no_writers.script deleted file mode 100644 index df3deacac..000000000 --- a/test/resources/router_no_writers.script +++ /dev/null @@ -1,8 +0,0 @@ -!: AUTO INIT -!: AUTO RESET - -C: RUN "CALL dbms.cluster.routing.getServers" {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [300, [{"role":"ROUTE","addresses":["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"]},{"role":"READ","addresses":["127.0.0.1:9004","127.0.0.1:9005"]},{"role":"WRITE","addresses":[]}]] - SUCCESS {} diff --git a/test/resources/router_with_multiple_writers.script b/test/resources/router_with_multiple_writers.script deleted file mode 100644 index e3ae63f78..000000000 --- a/test/resources/router_with_multiple_writers.script +++ /dev/null @@ -1,8 +0,0 @@ -!: AUTO INIT -!: AUTO RESET - -C: RUN "CALL dbms.cluster.routing.getServers" {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [300, [{"role":"ROUTE","addresses":["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"]},{"role":"READ","addresses":["127.0.0.1:9004","127.0.0.1:9005"]},{"role":"WRITE","addresses":["127.0.0.1:9006","127.0.0.1:9007"]}]] - SUCCESS {} diff --git a/test/resources/rude_router.script b/test/resources/rude_router.script deleted file mode 100644 index 5bde7374f..000000000 --- a/test/resources/rude_router.script +++ /dev/null @@ -1,7 +0,0 @@ -!: AUTO INIT -!: AUTO RESET - -C: RUN "CALL dbms.cluster.routing.getServers" {} - PULL_ALL -S: - diff --git a/test/resources/silent_router.script b/test/resources/silent_router.script deleted file mode 100644 index a315ae3a9..000000000 --- a/test/resources/silent_router.script +++ /dev/null @@ -1,7 +0,0 @@ -!: AUTO INIT -!: AUTO RESET - -C: RUN "CALL dbms.cluster.routing.getServers" {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - SUCCESS {} diff --git a/test/resources/fail_on_init.script b/test/stub/scripts/disconnect_after_init.script similarity index 54% rename from test/resources/fail_on_init.script rename to test/stub/scripts/disconnect_after_init.script index 0c8341421..d4d450ca7 100644 --- a/test/resources/fail_on_init.script +++ b/test/stub/scripts/disconnect_after_init.script @@ -1,4 +1,5 @@ !: AUTO INIT !: AUTO RESET -S: +S: SUCCESS {} +S: \ No newline at end of file diff --git a/test/stub/test_directdriver.py b/test/stub/test_directdriver.py index efb63062b..485355edc 100644 --- a/test/stub/test_directdriver.py +++ b/test/stub/test_directdriver.py @@ -48,3 +48,11 @@ def test_direct_disconnect_on_pull_all(self): with self.assertRaises(ServiceUnavailable): with driver.session() as session: session.run("RETURN $x", {"x": 1}).consume() + + def test_direct_session_close_after_server_close(self): + with StubCluster({9001: "disconnect_after_init.script"}): + uri = "bolt://127.0.0.1:9001" + with GraphDatabase.driver(uri, auth=self.auth_token, encrypted=False) as driver: + with driver.session() as session: + with self.assertRaises(ServiceUnavailable): + session.write_transaction(lambda tx: tx.run("CREATE (a:Item)"))