From d68e965a386f031b0c29934862105be5fbea341e Mon Sep 17 00:00:00 2001 From: Nigel Small Date: Wed, 14 Dec 2016 20:54:47 +0000 Subject: [PATCH 1/4] Removed unnecessary argument --- neo4j/v1/session.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/neo4j/v1/session.py b/neo4j/v1/session.py index a59323d9f..e6bc7fb8d 100644 --- a/neo4j/v1/session.py +++ b/neo4j/v1/session.py @@ -110,7 +110,7 @@ def driver(uri, **config): class SecurityPlan(object): @classmethod - def build(cls, address, **config): + def build(cls, **config): encrypted = config.get("encrypted", None) if encrypted is None: encrypted = _encryption_default() @@ -188,7 +188,7 @@ class DirectDriver(Driver): def __init__(self, address, **config): self.address = address - self.security_plan = security_plan = SecurityPlan.build(address, **config) + self.security_plan = security_plan = SecurityPlan.build(**config) self.encrypted = security_plan.encrypted pool = ConnectionPool(lambda a: connect(a, security_plan.ssl_context, **config)) Driver.__init__(self, pool) @@ -202,7 +202,7 @@ class RoutingDriver(Driver): """ def __init__(self, address, **config): - self.security_plan = security_plan = SecurityPlan.build(address, **config) + self.security_plan = security_plan = SecurityPlan.build(**config) self.encrypted = security_plan.encrypted if not security_plan.routing_compatible: # this error message is case-specific as there is only one incompatible From c53840457d6c11c5785dc6d7c82b35871c3667f7 Mon Sep 17 00:00:00 2001 From: Nigel Small Date: Wed, 14 Dec 2016 21:08:48 +0000 Subject: [PATCH 2/4] Moved ssl_compat to compat.ssl --- neo4j/v1/bolt.py | 5 ++--- neo4j/v1/{ssl_compat.py => compat/ssl.py} | 2 ++ neo4j/v1/constants.py | 2 +- neo4j/v1/session.py | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) rename neo4j/v1/{ssl_compat.py => compat/ssl.py} (96%) diff --git a/neo4j/v1/bolt.py b/neo4j/v1/bolt.py index f85e9e19d..a3d46203b 100644 --- a/neo4j/v1/bolt.py +++ b/neo4j/v1/bolt.py @@ -25,7 +25,6 @@ the `session` module provides the main user-facing abstractions. """ - from __future__ import division from base64 import b64encode @@ -36,13 +35,13 @@ from os.path import dirname, isfile from select import select from socket import create_connection, SHUT_RDWR, error as SocketError -from struct import pack as struct_pack, unpack as struct_unpack, unpack_from as struct_unpack_from +from struct import pack as struct_pack, unpack as struct_unpack from threading import RLock +from .compat.ssl import SSL_AVAILABLE, HAS_SNI, SSLError from .constants import DEFAULT_USER_AGENT, KNOWN_HOSTS, MAGIC_PREAMBLE, TRUST_DEFAULT, TRUST_ON_FIRST_USE from .exceptions import ProtocolError, Unauthorized, ServiceUnavailable from .packstream import Packer, Unpacker -from .ssl_compat import SSL_AVAILABLE, HAS_SNI, SSLError # Signature bytes for each message type diff --git a/neo4j/v1/ssl_compat.py b/neo4j/v1/compat/ssl.py similarity index 96% rename from neo4j/v1/ssl_compat.py rename to neo4j/v1/compat/ssl.py index 767a2cb98..cbd8d3639 100644 --- a/neo4j/v1/ssl_compat.py +++ b/neo4j/v1/compat/ssl.py @@ -18,6 +18,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import absolute_import + try: from ssl import SSLContext, PROTOCOL_SSLv23, OP_NO_SSLv2, CERT_REQUIRED, HAS_SNI, SSLError except ImportError: diff --git a/neo4j/v1/constants.py b/neo4j/v1/constants.py index b6ce0afb9..31b205a51 100644 --- a/neo4j/v1/constants.py +++ b/neo4j/v1/constants.py @@ -21,8 +21,8 @@ from os.path import expanduser, join +from .compat.ssl import SSL_AVAILABLE from ..meta import version -from .ssl_compat import SSL_AVAILABLE DEFAULT_PORT = 7687 diff --git a/neo4j/v1/session.py b/neo4j/v1/session.py index e6bc7fb8d..95ef16abc 100644 --- a/neo4j/v1/session.py +++ b/neo4j/v1/session.py @@ -32,13 +32,13 @@ from .bolt import connect, Response, RUN, PULL_ALL, ConnectionPool from .compat import integer, string, urlparse +from .compat.ssl import SSL_AVAILABLE, SSLContext, PROTOCOL_SSLv23, OP_NO_SSLv2, CERT_REQUIRED from .constants import DEFAULT_PORT, ENCRYPTION_DEFAULT, TRUST_DEFAULT, TRUST_SIGNED_CERTIFICATES, \ TRUST_ON_FIRST_USE, READ_ACCESS, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES, \ TRUST_ALL_CERTIFICATES, TRUST_CUSTOM_CA_SIGNED_CERTIFICATES from .exceptions import CypherError, ProtocolError, ResultError, TransactionError, \ ServiceUnavailable, SessionExpired from .routing import RoutingConnectionPool -from .ssl_compat import SSL_AVAILABLE, SSLContext, PROTOCOL_SSLv23, OP_NO_SSLv2, CERT_REQUIRED from .summary import ResultSummary from .types import hydrated From 2752a177acc24403136984d14728e4102a3ab941 Mon Sep 17 00:00:00 2001 From: Nigel Small Date: Wed, 14 Dec 2016 22:41:06 +0000 Subject: [PATCH 3/4] New files --- docs/source/driver.rst | 46 +++++++++++++++++++++++ docs/source/index.rst | 82 +++++++---------------------------------- docs/source/session.rst | 51 +++++++++++++++++++++++++ docs/source/types.rst | 16 ++++++++ neo4j/v1/types.py | 29 +++++++++------ test/test_types.py | 8 ++-- 6 files changed, 149 insertions(+), 83 deletions(-) create mode 100644 docs/source/driver.rst create mode 100644 docs/source/session.rst create mode 100644 docs/source/types.rst diff --git a/docs/source/driver.rst b/docs/source/driver.rst new file mode 100644 index 000000000..6a9901e70 --- /dev/null +++ b/docs/source/driver.rst @@ -0,0 +1,46 @@ +************** +Driver Objects +************** + +A `Driver` object holds the detail of a Neo4j database including server URIs, credentials and other configuration. +It also manages a pool of connections which are used to power :class:`.Session` instances. + +The scheme of the URI passed to the `Driver` constructor determines the type of `Driver` object constructed. +Two types are currently available: the :class:`.DirectDriver` and the :class:`.RoutingDriver`. +These are described in more detail below. + + +.. autoclass:: neo4j.v1.GraphDatabase + :members: + +.. autoclass:: neo4j.v1.DirectDriver + :members: + :inherited-members: + +.. autoclass:: neo4j.v1.RoutingDriver + :members: + :inherited-members: + +.. autoclass:: neo4j.v1.AuthToken + :members: + +.. autofunction:: neo4j.v1.basic_auth + +.. autofunction:: neo4j.v1.custom_auth + + +Encryption Settings +------------------- +.. py:attribute:: neo4j.v1.ENCRYPTION_OFF +.. py:attribute:: neo4j.v1.ENCRYPTION_ON +.. py:attribute:: neo4j.v1.ENCRYPTION_DEFAULT + + +Trust Settings +-------------- +.. py:attribute:: neo4j.v1.TRUST_ON_FIRST_USE +.. py:attribute:: neo4j.v1.TRUST_SIGNED_CERTIFICATES +.. py:attribute:: neo4j.v1.TRUST_ALL_CERTIFICATES +.. py:attribute:: neo4j.v1.TRUST_CUSTOM_CA_SIGNED_CERTIFICATES +.. py:attribute:: neo4j.v1.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES +.. py:attribute:: neo4j.v1.TRUST_DEFAULT diff --git a/docs/source/index.rst b/docs/source/index.rst index 3fd269d95..a2d1f5dfc 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,73 +1,6 @@ -============================ +**************************** Neo4j Bolt Driver for Python -============================ - -.. toctree:: - :maxdepth: 2 - - -Session API -=========== - -.. autoclass:: neo4j.v1.GraphDatabase - :members: - -.. autoclass:: neo4j.v1.Driver - :members: - -.. autoclass:: neo4j.v1.Session - :members: - -.. autoclass:: neo4j.v1.Transaction - :members: - -.. autoclass:: neo4j.v1.Record - :members: - -.. autoclass:: neo4j.v1.StatementResult - :members: - - -Encryption Settings -------------------- -.. py:attribute:: neo4j.v1.ENCRYPTION_OFF -.. py:attribute:: neo4j.v1.ENCRYPTION_ON -.. py:attribute:: neo4j.v1.ENCRYPTION_NON_LOCAL -.. py:attribute:: neo4j.v1.ENCRYPTION_DEFAULT - - -Trust Settings --------------- -.. py:attribute:: neo4j.v1.TRUST_ON_FIRST_USE -.. py:attribute:: neo4j.v1.TRUST_SIGNED_CERTIFICATES -.. py:attribute:: neo4j.v1.TRUST_DEFAULT - - -Query Summary Details ---------------------- - -.. autoclass:: neo4j.v1.summary.ResultSummary - :members: - -.. autoclass:: neo4j.v1.summary.SummaryCounters - :members: - - -Exceptions -========== - -.. autoclass:: neo4j.v1.ProtocolError - :members: - -.. autoclass:: neo4j.v1.CypherError - :members: - -.. autoclass:: neo4j.v1.ResultError - :members: - - -Example -======= +**************************** .. code-block:: python @@ -92,6 +25,17 @@ Example driver.close() +Contents +======== + +.. toctree:: + :maxdepth: 1 + + driver + session + types + + Indices and tables ================== diff --git a/docs/source/session.rst b/docs/source/session.rst new file mode 100644 index 000000000..c17c506ec --- /dev/null +++ b/docs/source/session.rst @@ -0,0 +1,51 @@ +*************** +Cypher Sessions +*************** + + +.. autoclass:: neo4j.v1.Session + :members: + +.. autoclass:: neo4j.v1.Transaction + :members: + +.. autoclass:: neo4j.v1.StatementResult + :members: + +.. autoclass:: neo4j.v1.Record + :members: + + +Summary Details +--------------- + +.. autoclass:: neo4j.v1.summary.ResultSummary + :members: + +.. autoclass:: neo4j.v1.summary.SummaryCounters + :members: + + +Exceptions +---------- + +.. autoclass:: neo4j.v1.ProtocolError + :members: + +.. autoclass:: neo4j.v1.Unauthorized + :members: + +.. autoclass:: neo4j.v1.CypherError + :members: + +.. autoclass:: neo4j.v1.TransactionError + :members: + +.. autoclass:: neo4j.v1.ResultError + :members: + +.. autoclass:: neo4j.v1.ServiceUnavailable + :members: + +.. autoclass:: neo4j.v1.SessionExpired + :members: diff --git a/docs/source/types.rst b/docs/source/types.rst new file mode 100644 index 000000000..cf1749671 --- /dev/null +++ b/docs/source/types.rst @@ -0,0 +1,16 @@ +*********** +Type System +*********** + + +.. autoclass:: neo4j.v1.Node + :members: + :inherited-members: + +.. autoclass:: neo4j.v1.Relationship + :members: + :inherited-members: + +.. autoclass:: neo4j.v1.Path + :members: + :inherited-members: diff --git a/neo4j/v1/types.py b/neo4j/v1/types.py index 21d19a036..8b686204a 100644 --- a/neo4j/v1/types.py +++ b/neo4j/v1/types.py @@ -53,25 +53,33 @@ def __hash__(self): def __len__(self): return len(self.properties) - def __getitem__(self, key): - return self.properties.get(key) + def __getitem__(self, name): + return self.properties.get(name) - def __contains__(self, key): - return key in self.properties + def __contains__(self, name): + return name in self.properties def __iter__(self): return iter(self.properties) - def get(self, key, default=None): - return self.properties.get(key, default) + def get(self, name, default=None): + """ Get a property value by name, optionally with a default. + """ + return self.properties.get(name, default) def keys(self): + """ Return an iterable of all property names. + """ return self.properties.keys() def values(self): + """ Return an iterable of all property values. + """ return self.properties.values() def items(self): + """ Return an iterable of all property name-value pairs. + """ return self.properties.items() @@ -108,7 +116,11 @@ def __init__(self, type, properties=None, **kwproperties): class Relationship(BaseRelationship): """ Self-contained graph relationship. """ + + #: The start node of this relationship start = None + + #: The end node of this relationship end = None @classmethod @@ -128,11 +140,6 @@ def __repr__(self): return "" % \ (self.id, self.start, self.end, self.type, self.properties) - def unbind(self): - inst = UnboundRelationship(self.type, self.properties) - inst.id = self.id - return inst - class UnboundRelationship(BaseRelationship): """ Self-contained graph relationship without endpoints. diff --git a/test/test_types.py b/test/test_types.py index 121f79d4f..18afed38f 100644 --- a/test/test_types.py +++ b/test/test_types.py @@ -127,9 +127,11 @@ def test_can_hydrate_path(self): carol = Node.hydrate(3, {"Person"}, {"name": "Carol", "age": 55}) alice_knows_bob = Relationship(alice.id, bob.id, "KNOWS", {"since": 1999}) carol_dislikes_bob = Relationship(carol.id, bob.id, "DISLIKES") - path = Path.hydrate([alice, bob, carol], - [alice_knows_bob.unbind(), carol_dislikes_bob.unbind()], - [1, 1, -2, 2]) + rels = [UnboundRelationship(alice_knows_bob.type, alice_knows_bob.properties), + UnboundRelationship(carol_dislikes_bob.type, carol_dislikes_bob.properties)] + rels[0].id = alice_knows_bob.id + rels[1].id = carol_dislikes_bob.id + path = Path.hydrate([alice, bob, carol], rels, [1, 1, -2, 2]) assert path.start == alice assert path.end == carol assert path.nodes == (alice, bob, carol) From fdae3e41803d8edb6c52a98e02dd1a39756ee88c Mon Sep 17 00:00:00 2001 From: Nigel Small Date: Wed, 14 Dec 2016 23:14:56 +0000 Subject: [PATCH 4/4] Updated index/readme --- README.rst | 75 +++++++++++++++++++------------------------ docs/source/index.rst | 61 ++++++++++++++++++++++++----------- neo4j/v1/session.py | 4 +++ test/test_session.py | 12 ++++--- 4 files changed, 87 insertions(+), 65 deletions(-) diff --git a/README.rst b/README.rst index 8d88676ba..4dc77a149 100644 --- a/README.rst +++ b/README.rst @@ -1,66 +1,57 @@ -============================ +**************************** Neo4j Bolt Driver for Python -============================ +**************************** - -Installation -============ - -To install the latest stable version, use: - -.. code:: bash - - pip install neo4j-driver - -For the most up-to-date version (possibly unstable), use: - -.. code:: bash - - pip install git+https://github.com/neo4j/neo4j-python-driver.git#egg=neo4j-driver +The Official Neo4j Driver for Python supports Neo4j 3.0 and above and Python versions 2.7, 3.4 and 3.5. -Example Usage +Quick Example ============= -.. code:: python +.. code-block:: python from neo4j.v1 import GraphDatabase, basic_auth - driver = GraphDatabase.driver("bolt://localhost:7687", auth=basic_auth("neo4j", "password")) + uri = "bolt://localhost:7687" + auth_token = basic_auth("neo4j", "password") + driver = GraphDatabase.driver(uri, auth=auth_token) - with driver.session() as session: + def print_friends_of(name): + with driver.session() as session: + with session.begin_transaction() as tx: + for record in tx.run("MATCH (a:Person)-[:KNOWS]->(f) " + "WHERE a.name = {name} " + "RETURN f.name", name=name): + print(record["f.name"]) - with session.begin_transaction() as write_tx: - write_tx.run("CREATE (a:Person {name:{name},age:{age}})", name="Alice", age=33) - write_tx.run("CREATE (a:Person {name:{name},age:{age}})", name="Bob", age=44) + print_friends_of("Alice") - with session.begin_transaction() as read_tx: - result = read_tx.run("MATCH (a:Person) RETURN a.name AS name, a.age AS age") - for record in result: - print("%s is %d years old" % (record["name"], record["age"])) - driver.close() - - -Command Line +Installation ============ +To install the latest stable version, use: + .. code:: bash - python -m neo4j "CREATE (a:Person {name:'Alice'}) RETURN a, labels(a), a.name" + pip install neo4j-driver + +For the most up-to-date version (possibly unstable), use: +.. code:: bash -Documentation -============= + pip install git+https://github.com/neo4j/neo4j-python-driver.git#egg=neo4j-driver -For more information such as manual, driver API documentations, changelogs, please find them in the wiki of this repo. -* `Driver Wiki`_ +Other Information +================= + * `Neo4j Manual`_ -* `Neo4j Refcard`_ -* `Sample Project Using Driver`_ +* `Neo4j Quick Reference Card`_ +* `Example Project`_ +* `Driver Wiki`_ (includes change logs) -.. _`Sample Project Using Driver`: https://github.com/neo4j-examples/movies-python-bolt -.. _`Driver Wiki`: https://github.com/neo4j/neo4j-python-driver/wiki .. _`Neo4j Manual`: https://neo4j.com/docs/ -.. _`Neo4j Refcard`: https://neo4j.com/docs/cypher-refcard/current/ \ No newline at end of file +.. _`Neo4j Quick Reference Card`: https://neo4j.com/docs/cypher-refcard/current/ +.. _`Example Project`: https://github.com/neo4j-examples/movies-python-bolt +.. _`Driver Wiki`: https://github.com/neo4j/neo4j-python-driver/wiki diff --git a/docs/source/index.rst b/docs/source/index.rst index a2d1f5dfc..094a53147 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -2,31 +2,49 @@ Neo4j Bolt Driver for Python **************************** +The Official Neo4j Driver for Python supports Neo4j 3.0 and above and Python versions 2.7, 3.4 and 3.5. + + +Quick Example +============= + .. code-block:: python from neo4j.v1 import GraphDatabase, basic_auth - driver = GraphDatabase.driver("bolt://localhost:7687", auth=basic_auth("neo4j", "password")) + uri = "bolt://localhost:7687" + auth_token = basic_auth("neo4j", "password") + driver = GraphDatabase.driver(uri, auth=auth_token) + + def print_friends_of(name): + with driver.session() as session: + with session.begin_transaction() as tx: + for record in tx.run("MATCH (a:Person)-[:KNOWS]->(f) " + "WHERE a.name = {name} " + "RETURN f.name", name=name): + print(record["f.name"]) + + print_friends_of("Alice") + + +Installation +============ + +To install the latest stable version, use: - with driver.session() as session: +.. code:: bash - with session.begin_transaction() as tx: - session.run("MERGE (a:Person {name:'Alice'})") + pip install neo4j-driver - friends = ["Bob", "Carol", "Dave", "Eve", "Frank"] - with session.begin_transaction() as tx: - for friend in friends: - tx.run("MATCH (a:Person {name:'Alice'}) " - "MERGE (a)-[:KNOWS]->(x:Person {name:{n}})", {"n": friend}) +For the most up-to-date version (possibly unstable), use: - for record in session.run("MATCH (a:Person {name:'Alice'})-[:KNOWS]->(friend) RETURN friend"): - print('Alice says, "hello, %s"' % record["friend"]["name"]) +.. code:: bash - driver.close() + pip install git+https://github.com/neo4j/neo4j-python-driver.git#egg=neo4j-driver -Contents -======== +API Documentation +================= .. toctree:: :maxdepth: 1 @@ -36,10 +54,15 @@ Contents types -Indices and tables -================== +Other Information +================= -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` +* `Neo4j Manual`_ +* `Neo4j Quick Reference Card`_ +* `Example Project`_ +* `Driver Wiki`_ (includes change logs) +.. _`Neo4j Manual`: https://neo4j.com/docs/ +.. _`Neo4j Quick Reference Card`: https://neo4j.com/docs/cypher-refcard/current/ +.. _`Example Project`: https://github.com/neo4j-examples/movies-python-bolt +.. _`Driver Wiki`: https://github.com/neo4j/neo4j-python-driver/wiki diff --git a/neo4j/v1/session.py b/neo4j/v1/session.py index 95ef16abc..0b6dce12f 100644 --- a/neo4j/v1/session.py +++ b/neo4j/v1/session.py @@ -162,6 +162,9 @@ class Driver(object): def __init__(self, pool): self.pool = pool + def __del__(self): + self.close() + def __enter__(self): return self @@ -179,6 +182,7 @@ def session(self, access_mode=None): def close(self): if self.pool: self.pool.close() + self.pool = None class DirectDriver(Driver): diff --git a/test/test_session.py b/test/test_session.py index e3b9024ce..587b9d8ce 100644 --- a/test/test_session.py +++ b/test/test_session.py @@ -354,8 +354,12 @@ def test_record_repr(self): class ExplicitTransactionTestCase(ServerTestCase): + def setUp(self): + self.driver = GraphDatabase.driver(BOLT_URI, auth=AUTH_TOKEN) + def test_can_commit_transaction(self): - with GraphDatabase.driver(BOLT_URI, auth=AUTH_TOKEN).session() as session: + + with self.driver.session() as session: tx = session.begin_transaction() # Create a node @@ -378,7 +382,7 @@ def test_can_commit_transaction(self): assert value == "bar" def test_can_rollback_transaction(self): - with GraphDatabase.driver(BOLT_URI, auth=AUTH_TOKEN).session() as session: + with self.driver.session() as session: tx = session.begin_transaction() # Create a node @@ -399,7 +403,7 @@ def test_can_rollback_transaction(self): assert len(list(result)) == 0 def test_can_commit_transaction_using_with_block(self): - with GraphDatabase.driver(BOLT_URI, auth=AUTH_TOKEN).session() as session: + with self.driver.session() as session: with session.begin_transaction() as tx: # Create a node result = tx.run("CREATE (a) RETURN id(a)") @@ -421,7 +425,7 @@ def test_can_commit_transaction_using_with_block(self): assert value == "bar" def test_can_rollback_transaction_using_with_block(self): - with GraphDatabase.driver(BOLT_URI, auth=AUTH_TOKEN).session() as session: + with self.driver.session() as session: with session.begin_transaction() as tx: # Create a node result = tx.run("CREATE (a) RETURN id(a)")