diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..b84e3e0dd --- /dev/null +++ b/docs/README.md @@ -0,0 +1,12 @@ +==================== +Sphinx Documentation +==================== + +``` +pip install -r requirements.txt +``` + +``` +make -C docs html +``` + diff --git a/docs/README.rst b/docs/README.rst deleted file mode 100644 index e9c3a7aa3..000000000 --- a/docs/README.rst +++ /dev/null @@ -1,10 +0,0 @@ -==================== -Sphinx Documentation -==================== - -From the root of this repository... - -.. code:: bash - - pip install -r docs_requirements.txt - make -C docs html diff --git a/docs/requirements.txt b/docs/requirements.txt index ccbd0b094..118d63a74 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ neotime sphinx +sphinx-rtd-theme diff --git a/docs/source/aio.rst b/docs/source/aio.rst deleted file mode 100644 index 290341d75..000000000 --- a/docs/source/aio.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. automodule:: neo4j.aio - :members: diff --git a/docs/source/conf.py b/docs/source/conf.py index 2b0e53e2e..f4165e09b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -16,6 +16,7 @@ import sys import os import shlex +import sphinx_rtd_theme # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -38,6 +39,7 @@ 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', 'sphinx.ext.intersphinx', + "sphinx_rtd_theme", ] # Add any paths that contain templates here, relative to this directory. @@ -118,7 +120,8 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'nature' +# html_theme = 'nature' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -302,6 +305,5 @@ def setup(app): intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), - 'neobolt': ('https://neobolt.readthedocs.io/en/latest/', None), 'neotime': ('https://neotime.readthedocs.io/en/latest/', None), } diff --git a/docs/source/index.rst b/docs/source/index.rst index d912359bc..b680148f8 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,13 +1,107 @@ -************************************** -Neo4j Bolt Driver |version| for Python -************************************** +############################# +Neo4j Python Driver |version| +############################# -The Official Neo4j Driver for Python supports Neo4j 3.2 and above and requires Python version 2.7 or 3.4+. -Note that support for Python 2.7 will be removed in the 2.0 driver. +.. warning:: + This API docs is not production ready! +The Official Neo4j Driver for Python. + +Neo4j versions supported: + +* Neo4j 4.0 - Using the Bolt Protocol Version 4.0 +* Neo4j 3.5 - Using the Bolt Protocol Version 3 + +Python versions supported: + +* Python 3.8 +* Python 3.7 +* Python 3.6 +* Python 3.5 + + +**Note:** Python 2.7 support has been dropped. + +.. note:: + The driver may still work with older versions of python. + + The previous driver `Python Driver 1.7`_ supports older versions of python, + the Neo4j 4.0 will work in fallback mode (using Bolt Protocol Version 3) with that driver. + + +**************** +Breaking Changes +**************** + +Version Scheme Changes +====================== + +The version number have jumped from :code:`1.7` to :code:`Python Driver 4.0` to align with the Neo4j Database version scheme. + + +Namespace Changes +================= + +:code:`import neo4j.v1` have changed namespace to be :code:`import neo4j` + + +Secure Connection +================= + +Neo4j 4.0 is by default configured to use a non secure connection. + +The Driver Configuration argument :code:`encrypted` is by default set to :code:`False`. + +To be able to connect to Neo4j 3.5 set :code:`encrypted=True` to have it configured as the default for that setup. + + +Bookmark Changes +================ + +Bookmarks is now a Bookmark class instead of a string. + + +Exceptions Changes +================== + +The exceptions in :code:`neo4j.exceptions` have been updated and there is internal exceptions starting with the naming :code:`Bolt` that should be propagated into the exceptions API. + + +URI Changes +=========== + +`bolt+routing` have been renamed to `neo4j` + + +Class Renaming Changes +====================== + +* :code:`BoltStatementResult` is now :code:`Result` +* :code:`StatementResultSummary` is now :code:`ResultSummary` +* :code:`Statement` is now :code:`Query` + + +Argument Renaming Changes +========================= + +* :code:`statement` is now :code:`query` +* :code:`cypher` is now :code:`query` +* :code:`Session.run(cypher, ...` is now :code:`Session.run(query, ...` +* :code:`Transaction.run(statement, ...` is now :code:`Transaction.run(query, ...` +* :code:`StatementResultSummary.statement` is now :code:`ResultSummary.query` +* :code:`StatementResultSummary.statement_type` is now :code:`ResultSummary.query_type` + + +Dependency Changes +================== + +The dependency :code:`neobolt` have been removed. + + +************* Quick Example -============= +************* .. code-block:: python @@ -26,45 +120,50 @@ Quick Example session.read_transaction(print_friends_of, "Alice") -.. note:: - - While imports from ``neo4j.v1`` still work, these will be removed in the 2.0 driver. - It is therefore recommended to change all imports from ``neo4j.v1`` to ``neo4j``. - - +************ Installation -============ +************ To install the latest stable driver release, use: .. code:: bash - pip install neo4j - -.. note:: - - The driver is currently released under two package names on `PyPI `_: ``neo4j`` and ``neo4j-driver``. - Installing from ``neo4j`` is recommended since ``neo4j-driver`` will be removed in a future release. + python -m pip install neo4j +***************** API Documentation -================= +***************** .. toctree:: :maxdepth: 1 - aio + driver + errors + results + transactions + usage_patterns + types/core + types/graph + types/spatial + types/temporal +***************** Other Information -================= +***************** -* `Neo4j Manual`_ +* `Neo4j Documentation`_ +* `The Neo4j Drivers Manual`_ * `Neo4j Quick Reference Card`_ * `Example Project`_ * `Driver Wiki`_ (includes change logs) +* `Migration Guide - Upgrade Neo4j drivers`_ -.. _`Neo4j Manual`: https://neo4j.com/docs/ +.. _`Python Driver 1.7`: https://neo4j.com/docs/api/python-driver/1.7/ +.. _`Neo4j Documentation`: https://neo4j.com/docs/ +.. _`The Neo4j Drivers Manual`: https://neo4j.com/docs/driver-manual/current/ .. _`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 +.. _`Migration Guide - Upgrade Neo4j drivers`: https://neo4j.com/docs/migration-guide/4.0/upgrade-driver/ diff --git a/docs/source/results.rst b/docs/source/results.rst index a3341887c..b93f5201e 100644 --- a/docs/source/results.rst +++ b/docs/source/results.rst @@ -2,14 +2,16 @@ Consuming Results ***************** -Every time Cypher is executed, a :class:`neo4j.Result` is returned. +Every time a query is executed, a :class:`.Result` is returned. + This provides a handle to the result of the query, giving access to the records within it as well as the result metadata. Each result consists of header metadata, zero or more :class:`.Record` objects and footer metadata (the summary). Results also contain a buffer that automatically stores unconsumed records when results are consumed out of order. -A :class:`neo4j.Result` is attached to an active connection, through a :class:`.Session`, until all its content has been buffered or consumed. -.. class:: neo4j.Result +A :class:`.Result` is attached to an active connection, through a :class:`.Session`, until all its content has been buffered or consumed. + +.. class:: .Result .. describe:: iter(result) @@ -40,7 +42,7 @@ A :class:`neo4j.Result` is attached to an active connection, through a :class:`. .. automethod:: data -.. class:: neo4j.Record +.. class:: .Record A :class:`.Record` is an immutable ordered collection of key-value pairs. It is generally closer to a :py:class:`namedtuple` than to a @@ -103,8 +105,8 @@ A :class:`neo4j.Result` is attached to an active connection, through a :class:`. Summary Details --------------- -.. autoclass:: neo4j.ResultSummary +.. autoclass:: .ResultSummary :members: -.. autoclass:: neo4j.SummaryCounters +.. autoclass:: .SummaryCounters :members: diff --git a/docs/source/transactions.rst b/docs/source/transactions.rst index 489cba8b2..c89f72dea 100644 --- a/docs/source/transactions.rst +++ b/docs/source/transactions.rst @@ -25,7 +25,7 @@ For example:: To construct a :class:`.Session` use the :meth:`.Driver.session` method. -.. class:: neo4j.Session +.. class:: .Session .. automethod:: close @@ -75,7 +75,7 @@ Explicit transactions support multiple statements and must be created with an ex This creates a new :class:`.Transaction` object that can be used to run Cypher. It also gives applications the ability to directly control `commit` and `rollback` activity. -.. class:: neo4j.Transaction +.. class:: .Transaction .. automethod:: run @@ -127,7 +127,7 @@ Returning a live result object would prevent the driver from correctly managing To exert more control over how a transaction function is carried out, the :func:`.unit_of_work` decorator can be used. -.. autofunction:: neo4j.unit_of_work +.. autofunction:: neo4j.work.simple.unit_of_work Access modes @@ -140,7 +140,7 @@ Note that this mode is simply a default and not a constraint. This means that transaction functions within a session can override the access mode passed to that session on construction. .. note:: - The driver does not parse Cypher statements and cannot determine whether a statement tagged as `read` or `write` is tagged correctly. - Since the access mode is not passed to the server, this can allow a `write` statement to be executed in a `read` call on a single instance. + The driver does not parse Cypher queries and cannot determine whether the access mode should be :code:`ACCESS_READ` or :code:`ACCESS_WRITE`. + Since the access mode is not passed to the server, this can allow a :code:`ACCESS_WRITE` statement to be executed for a :code:`ACCESS_READ` call on a single instance. Clustered environments are not susceptible to this loophole as cluster roles prevent it. This behaviour should not be relied upon as the loophole may be closed in a future release. diff --git a/docs/source/types/graph.rst b/docs/source/types/graph.rst index f2a11a82b..0cd7ddbc8 100644 --- a/docs/source/types/graph.rst +++ b/docs/source/types/graph.rst @@ -18,10 +18,10 @@ Relationship :class:`.Relationship` Path :class:`.Path` ============= ====================== -.. class:: neo4j.types.graph.Graph +.. class:: neo4j.graph.Graph A local, self-contained graph object that acts as a container for :class:`.Node` and :class:`.Relationship` instances. - This is typically obtained via the :meth:`neo4j.Result.graph` method. + This is typically obtained via the :meth:`.Result.graph` method. .. autoattribute:: nodes @@ -30,7 +30,7 @@ Path :class:`.Path` .. automethod:: relationship_type -.. class:: neo4j.types.graph.Node +.. class:: neo4j.graph.Node .. describe:: node == other @@ -76,7 +76,7 @@ Path :class:`.Path` .. automethod:: items -.. class:: neo4j.types.graph.Relationship +.. class:: neo4j.graph.Relationship .. describe:: relationship == other @@ -133,7 +133,7 @@ Path :class:`.Path` .. automethod:: items -.. class:: neo4j.types.graph.Path +.. class:: neo4j.graph.Path .. describe:: path == other diff --git a/docs/source/types/spatial.rst b/docs/source/types/spatial.rst index b1c8313a4..c5f950fce 100644 --- a/docs/source/types/spatial.rst +++ b/docs/source/types/spatial.rst @@ -8,13 +8,13 @@ Cypher Type Python Type Point :class:`.Point` ============= ====================== -.. autoclass:: neo4j.types.spatial.Point +.. autoclass:: neo4j.spatial.Point :members: -.. autoclass:: neo4j.types.spatial.CartesianPoint +.. autoclass:: neo4j.spatial.CartesianPoint :members: :inherited-members: -.. autoclass:: neo4j.types.spatial.WGS84Point +.. autoclass:: neo4j.spatial.WGS84Point :members: :inherited-members: diff --git a/docs/source/usage_patterns.rst b/docs/source/usage_patterns.rst new file mode 100644 index 000000000..461206380 --- /dev/null +++ b/docs/source/usage_patterns.rst @@ -0,0 +1,273 @@ +############## +Usage Patterns +############## + +.. warning:: + This section is experimental! Breaking changes will occur! + + +****************** +Simple Application +****************** + +.. code-block:: python + + from neo4j import GraphDatabase + + + def print_friends_of(tx, name): + query = "MATCH (a:Person)-[:KNOWS]->(f) WHERE a.name = {name} RETURN f.name" + + result = tx.run(query, name=name) + + for record in result: + print(record["f.name"]) + + + if __name__ == "main": + + uri = "bolt://localhost:7687" + driver = GraphDatabase.driver(uri, auth=("neo4j", "password")) + + with driver.session() as session: + session.read_transaction(print_friends_of, "Alice") + + driver.close() + + +********************************** +Driver Initialization Work Pattern +********************************** + +.. code-block:: python + + from neo4j import GraphDatabase + from neo4j.exceptions import ServiceUnavailable + + uri = "bolt://localhost:7687" + + driver_config = { + "encrypted": False, + "trust": None, + "user_agent": "example", + "max_connection_lifetime": 1000, + "max_connection_pool_size": 100, + "connection_acquisition_timeout": 10, + "connection_timeout": 1, + "keep_alive": False, + "max_retry_time": 10, + "resolver": None, + } + + try: + driver = GraphDatabase.driver(uri, auth=("neo4j", "password"), **driver_config) + driver.close() + except ServiceUnavailable as e: + print(e) + + +Driver Initialization With Block Work Pattern +============================================= + +.. Investigate the example 6 pattern for error handling + https://www.python.org/dev/peps/pep-0343/#examples + + +.. code-block:: python + + from neo4j import GraphDatabase + + with GraphDatabase.driver(uri, auth=("neo4j", "password"), **driver_config) as driver: + try: + session = driver.session() + session.close() + except ServiceUnavailable as e: + print(e) + +*********************************** +Session Initialization Work Pattern +*********************************** + +.. code-block:: python + + from neo4j import ( + ACCESS_READ, + ACCESS_WRITE, + ) + + session_config = { + "fetch_size": 100, + "database": "default", + "bookmarks": ["bookmark-1",], + "access_mode": ACCESS_WRITE, + "acquire_timeout": 60.0, + "max_retry_time": 30.0, + "initial_retry_delay": 1.0, + "retry_delay_multiplier": 2.0, + "retry_delay_jitter_factor": 0.2, + } + + try: + session = driver.session(access_mode=None, **session_config) + session.close() + except ServiceUnavailable as e: + print(e) + + +Session Initialization With Block Work Pattern +============================================== + +.. Investigate the example 6 pattern for error handling + https://www.python.org/dev/peps/pep-0343/#examples + + +.. code-block:: python + + from neo4j.exceptions import ServiceUnavailable + + query = "RETURN 1 AS x" + + with driver.session(access_mode=None, **session_config) as session: + try: + result = session.run(query) + for record in result: + print(record["x"]) + except ServiceUnavailable as e: + print(e) + + +******************************* +Session Autocommit Work Pattern +******************************* + +.. code-block:: python + + statement = "RETURN $tag AS $name" + + kwparameters = {"name": "test", "tag": 123} + + session.run(query) + + session.run(query, parameters=None, **kwparameters) + + session.run(query, parameters={"name": "test", "tag": 123}) + + session.run(query, parameters={"name": "test", "tag": 123}, **kwparameters) + + session.run(query, name="test", "tag"=123) + + +**************************************** +Session Managed Transaction Work Pattern +**************************************** + +.. code-block:: python + + def test_work(tx, *args, **kwargs): + query = "RETURN $tag AS $name" + + kwparameters = {"name": "test", "tag": 123} + + tx.run(query) + + tx.run(query, parameters=None, **kwparameters) + + tx.run(query, parameters={"name": "test", "tag": 123}) + + tx.run(query, parameters={"name": "test", "tag": 123}, **kwparameters) + + tx.run(query, name="test", "tag"=123) + + + session.read_transaction(test_work) + + session.read_transaction(test_work, *args, **kwargs) + + session.read_transaction(test_work, **kwargs) + + + session.write_transaction(test_work) + + session.write_transaction(test_work, *args, **kwargs) + + session.write_transaction(test_work, **kwargs) + + +unit_of_work +============ + +.. code-block:: python + + from neo4j import unit_of_work + + + @unit_of_work(timeout=10) + def test_work(tx, *args, **kwargs): + query = "RETURN $tag AS $name" + + result = tx.run(query) + # The result needs to be consumed + + session.read_transaction(test_work) + + +.. code-block:: python + + from neo4j import unit_of_work + + + @unit_of_work(metadata={"hello": 123}) + def test_work(tx, *args, **kwargs): + query = "RETURN $tag AS $name" + + result = tx.run(query) + # The result needs to be consumed + + session.read_transaction(test_work) + + +.. code-block:: python + + from neo4j import unit_of_work + + + @unit_of_work(timeout=10, metadata={"hello": 123}) + def test_work(tx, *args, **kwargs): + query = "RETURN $tag AS $name" + + result = tx.run(query) + # The result needs to be consumed + + session.read_transaction(test_work) + + +The Query Object Work Pattern +============================= + +.. code-block:: python + + from neo4j import Query + + + def test_work(tx, *args, **kwargs): + query = Query("RETURN 1 AS x, timeout=10, metadata={"hello": 123}) + + result = tx.run(query) + # The result needs to be consumed + + session.read_transaction(test_work) + + + + +******************************* +Transaction Object Work Pattern +******************************* + +.. code-block:: python + + query = Query("RETURN 1 AS x, timeout=10, metadata={"hello": 123}) + + tx = session.begin_transaction(bookmark=None, metadata=None, timeout=None) + tx.run(query) + tx.commit()