Skip to content

Commit a7d3ae4

Browse files
committed
Merge branch '5.0' into bolt-utc-datetimes-refactoring
2 parents 9c4a8b8 + 1becfc5 commit a7d3ae4

File tree

10 files changed

+180
-49
lines changed

10 files changed

+180
-49
lines changed

CHANGELOG.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,9 @@
7676
It now raises a `ResultConsumedError`.
7777
- New method `Result.closed()` can be used to check for this condition if
7878
necessary.
79-
- `driver.verify_connectivity()`
80-
- All keyword arguments have been deprecated (they were experimental).
81-
They are now ignored and will be removed in a future release.
82-
- The undocumented return value has been removed. If you need information
83-
about the remote server, use `driver.get_server_info()` instead.
79+
- The undocumented return value of `driver.verify_connectivity()` has been
80+
removed. If you need information about the remote server, use
81+
`driver.get_server_info()` instead.
8482
- Transaction functions (a.k.a. managed transactions):
8583
The first argument of transaction functions is now a `ManagedTransaction`
8684
object. It behaves exactly like a regular `Transaction` object, except it

docs/source/api.rst

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -666,16 +666,21 @@ The default access mode.
666666

667667
A session can be given a default access mode on construction.
668668

669-
This applies only in clustered environments and determines whether transactions carried out within that session should be routed to a ``read`` or ``write`` server by default.
669+
This applies only in clustered environments and determines whether transactions
670+
carried out within that session should be routed to a ``read`` or ``write``
671+
server by default.
670672

671-
Transactions (see :ref:`managed-transactions-ref`) within a session can override the access mode passed to that session on construction.
673+
Transactions (see :ref:`managed-transactions-ref`) within a session override the
674+
access mode passed to that session on construction.
672675

673676
.. note::
674-
The driver does not parse Cypher queries and cannot determine whether the access mode should be ``neo4j.ACCESS_WRITE`` or ``neo4j.ACCESS_READ``.
675-
Since the access mode is not passed to the server, this can allow a ``neo4j.ACCESS_WRITE`` statement to be executed for a ``neo4j.ACCESS_READ`` call on a single instance.
676-
Clustered environments are not susceptible to this loophole as cluster roles prevent it.
677-
This behaviour should not be relied upon as the loophole may be closed in a future release.
678-
677+
The driver does not parse Cypher queries and cannot determine whether the
678+
access mode should be ``neo4j.ACCESS_WRITE`` or ``neo4j.ACCESS_READ``.
679+
This setting is only meant to enable the driver to perform correct routing,
680+
*not* for enforcing access control. This means that, depending on the server
681+
version and settings, the server or cluster might allow a write-statement to
682+
be executed even when ``neo4j.ACCESS_READ`` is chosen. This behaviour should
683+
not be relied upon as it can change with the server.
679684

680685
:Type: ``neo4j.WRITE_ACCESS``, ``neo4j.READ_ACCESS``
681686
:Default: ``neo4j.WRITE_ACCESS``

docs/source/index.rst

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ To deactivate the current active virtual environment, use:
9191
Quick Example
9292
*************
9393

94-
Creating nodes.
94+
Creating nodes and relationships.
9595

9696
.. code-block:: python
9797
@@ -100,15 +100,17 @@ Creating nodes.
100100
uri = "neo4j://localhost:7687"
101101
driver = GraphDatabase.driver(uri, auth=("neo4j", "password"))
102102
103+
def create_person(tx, name):
104+
tx.run("CREATE (a:Person {name: $name})", name=name)
105+
103106
def create_friend_of(tx, name, friend):
104107
tx.run("MATCH (a:Person) WHERE a.name = $name "
105108
"CREATE (a)-[:KNOWS]->(:Person {name: $friend})",
106109
name=name, friend=friend)
107110
108111
with driver.session() as session:
112+
session.write_transaction(create_person, "Alice")
109113
session.write_transaction(create_friend_of, "Alice", "Bob")
110-
111-
with driver.session() as session:
112114
session.write_transaction(create_friend_of, "Alice", "Carl")
113115
114116
driver.close()
@@ -126,8 +128,8 @@ Finding nodes.
126128
def get_friends_of(tx, name):
127129
friends = []
128130
result = tx.run("MATCH (a:Person)-[:KNOWS]->(f) "
129-
"WHERE a.name = $name "
130-
"RETURN f.name AS friend", name=name)
131+
"WHERE a.name = $name "
132+
"RETURN f.name AS friend", name=name)
131133
for record in result:
132134
friends.append(record["friend"])
133135
return friends

neo4j/_async/driver.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from .._meta import (
2929
deprecation_warn,
3030
experimental,
31+
experimental_warn,
3132
unclosed_resource_warn,
3233
)
3334
from ..addressing import Address
@@ -309,22 +310,34 @@ async def verify_connectivity(self, **config):
309310
Even if this method raises an exception, the driver still needs to
310311
be closed via :meth:`close` to free up all resources.
311312
313+
:param config: accepts the same configuration key-word arguments as
314+
:meth:`session`.
315+
316+
.. warning::
317+
All configuration key-word arguments are experimental.
318+
They might be changed or removed in any future version without
319+
prior notice.
320+
312321
:raises DriverError: if the driver cannot connect to the remote.
313322
Use the exception to further understand the cause of the
314323
connectivity problem.
315324
316-
.. versionchanged:: 5.0 the config parameters will be removed in
317-
version 6 0. It has no effect starting with version 5.0.
325+
.. versionchanged:: 5.0
326+
The undocumented return value has been removed.
327+
If you need information about the remote server, use
328+
:meth:`get_server_info` instead.
318329
"""
319330
if config:
320-
deprecation_warn(
321-
"verify_connectivity() will not accept any configuration "
322-
"parameters starting with version 6.0."
331+
experimental_warn(
332+
"All configuration key-word arguments to "
333+
"verify_connectivity() are experimental. They might be "
334+
"changed or removed in any future version without prior "
335+
"notice."
323336
)
337+
async with self.session(**config) as session:
338+
await session._get_server_info()
324339

325-
await self.get_server_info()
326-
327-
async def get_server_info(self):
340+
async def get_server_info(self, **config):
328341
"""Get information about the connected Neo4j server.
329342
330343
Try to establish a working read connection to the remote server or a
@@ -338,6 +351,14 @@ async def get_server_info(self):
338351
Even if this method raises an exception, the driver still needs to
339352
be closed via :meth:`close` to free up all resources.
340353
354+
:param config: accepts the same configuration key-word arguments as
355+
:meth:`session`.
356+
357+
.. warning::
358+
All configuration key-word arguments are experimental.
359+
They might be changed or removed in any future version without
360+
prior notice.
361+
341362
:rtype: ServerInfo
342363
343364
:raises DriverError: if the driver cannot connect to the remote.
@@ -346,7 +367,14 @@ async def get_server_info(self):
346367
347368
.. versionadded:: 5.0
348369
"""
349-
async with self.session() as session:
370+
if config:
371+
experimental_warn(
372+
"All configuration key-word arguments to "
373+
"verify_connectivity() are experimental. They might be "
374+
"changed or removed in any future version without prior "
375+
"notice."
376+
)
377+
async with self.session(**config) as session:
350378
return await session._get_server_info()
351379

352380
@experimental("Feature support query, based on Bolt protocol version and Neo4j server version will change in the future.")

neo4j/_async/work/session.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,11 @@ async def _run_transaction(
440440

441441
async def read_transaction(self, transaction_function, *args, **kwargs):
442442
"""Execute a unit of work in a managed read transaction.
443+
444+
.. note::
445+
This does not necessarily imply access control, see the session
446+
configuration option :ref:`default-access-mode-ref`.
447+
443448
This transaction will automatically be committed unless an exception is thrown during query execution or by the user code.
444449
Note, that this function perform retries and that the supplied `transaction_function` might get invoked more than once.
445450
@@ -490,6 +495,11 @@ async def get_two_tx(tx):
490495

491496
async def write_transaction(self, transaction_function, *args, **kwargs):
492497
"""Execute a unit of work in a managed write transaction.
498+
499+
.. note::
500+
This does not necessarily imply access control, see the session
501+
configuration option :ref:`default-access-mode-ref`.
502+
493503
This transaction will automatically be committed unless an exception is thrown during query execution or by the user code.
494504
Note, that this function perform retries and that the supplied `transaction_function` might get invoked more than once.
495505

neo4j/_meta.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ class ExperimentalWarning(Warning):
7777
"""
7878

7979

80+
def experimental_warn(message, stack_level=1):
81+
warn(message, category=ExperimentalWarning, stacklevel=stack_level + 1)
82+
83+
8084
def experimental(message):
8185
""" Decorator for tagging experimental functions and methods.
8286
@@ -92,16 +96,14 @@ def decorator(f):
9296
if asyncio.iscoroutinefunction(f):
9397
@wraps(f)
9498
async def inner(*args, **kwargs):
95-
from warnings import warn
96-
warn(message, category=ExperimentalWarning, stacklevel=2)
99+
experimental_warn(message, stack_level=2)
97100
return await f(*args, **kwargs)
98101

99102
return inner
100103
else:
101104
@wraps(f)
102105
def inner(*args, **kwargs):
103-
from warnings import warn
104-
warn(message, category=ExperimentalWarning, stacklevel=2)
106+
experimental_warn(message, stack_level=2)
105107
return f(*args, **kwargs)
106108

107109
return inner

neo4j/_sync/driver.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from .._meta import (
2929
deprecation_warn,
3030
experimental,
31+
experimental_warn,
3132
unclosed_resource_warn,
3233
)
3334
from ..addressing import Address
@@ -309,22 +310,34 @@ def verify_connectivity(self, **config):
309310
Even if this method raises an exception, the driver still needs to
310311
be closed via :meth:`close` to free up all resources.
311312
313+
:param config: accepts the same configuration key-word arguments as
314+
:meth:`session`.
315+
316+
.. warning::
317+
All configuration key-word arguments are experimental.
318+
They might be changed or removed in any future version without
319+
prior notice.
320+
312321
:raises DriverError: if the driver cannot connect to the remote.
313322
Use the exception to further understand the cause of the
314323
connectivity problem.
315324
316-
.. versionchanged:: 5.0 the config parameters will be removed in
317-
version 6 0. It has no effect starting with version 5.0.
325+
.. versionchanged:: 5.0
326+
The undocumented return value has been removed.
327+
If you need information about the remote server, use
328+
:meth:`get_server_info` instead.
318329
"""
319330
if config:
320-
deprecation_warn(
321-
"verify_connectivity() will not accept any configuration "
322-
"parameters starting with version 6.0."
331+
experimental_warn(
332+
"All configuration key-word arguments to "
333+
"verify_connectivity() are experimental. They might be "
334+
"changed or removed in any future version without prior "
335+
"notice."
323336
)
337+
with self.session(**config) as session:
338+
session._get_server_info()
324339

325-
self.get_server_info()
326-
327-
def get_server_info(self):
340+
def get_server_info(self, **config):
328341
"""Get information about the connected Neo4j server.
329342
330343
Try to establish a working read connection to the remote server or a
@@ -338,6 +351,14 @@ def get_server_info(self):
338351
Even if this method raises an exception, the driver still needs to
339352
be closed via :meth:`close` to free up all resources.
340353
354+
:param config: accepts the same configuration key-word arguments as
355+
:meth:`session`.
356+
357+
.. warning::
358+
All configuration key-word arguments are experimental.
359+
They might be changed or removed in any future version without
360+
prior notice.
361+
341362
:rtype: ServerInfo
342363
343364
:raises DriverError: if the driver cannot connect to the remote.
@@ -346,7 +367,14 @@ def get_server_info(self):
346367
347368
.. versionadded:: 5.0
348369
"""
349-
with self.session() as session:
370+
if config:
371+
experimental_warn(
372+
"All configuration key-word arguments to "
373+
"verify_connectivity() are experimental. They might be "
374+
"changed or removed in any future version without prior "
375+
"notice."
376+
)
377+
with self.session(**config) as session:
350378
return session._get_server_info()
351379

352380
@experimental("Feature support query, based on Bolt protocol version and Neo4j server version will change in the future.")

neo4j/_sync/work/session.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,11 @@ def _run_transaction(
440440

441441
def read_transaction(self, transaction_function, *args, **kwargs):
442442
"""Execute a unit of work in a managed read transaction.
443+
444+
.. note::
445+
This does not necessarily imply access control, see the session
446+
configuration option :ref:`default-access-mode-ref`.
447+
443448
This transaction will automatically be committed unless an exception is thrown during query execution or by the user code.
444449
Note, that this function perform retries and that the supplied `transaction_function` might get invoked more than once.
445450
@@ -490,6 +495,11 @@ def get_two_tx(tx):
490495

491496
def write_transaction(self, transaction_function, *args, **kwargs):
492497
"""Execute a unit of work in a managed write transaction.
498+
499+
.. note::
500+
This does not necessarily imply access control, see the session
501+
configuration option :ref:`default-access-mode-ref`.
502+
493503
This transaction will automatically be committed unless an exception is thrown during query execution or by the user code.
494504
Note, that this function perform retries and that the supplied `transaction_function` might get invoked more than once.
495505

tests/unit/async_/test_driver.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,18 +251,42 @@ async def test_verify_connectivity(uri, mocker):
251251
"neo4j://127.0.0.1:9000",
252252
))
253253
@pytest.mark.parametrize("kwargs", (
254-
{"access_mode": WRITE_ACCESS},
255-
{"access_mode": READ_ACCESS},
254+
{"default_access_mode": WRITE_ACCESS},
255+
{"default_access_mode": READ_ACCESS},
256256
{"fetch_size": 69},
257257
))
258258
@mark_async_test
259-
async def test_verify_connectivity_parameters_are_deprecated(uri, kwargs,
260-
mocker):
259+
async def test_verify_connectivity_parameters_are_deprecated(
260+
uri, kwargs, mocker
261+
):
261262
driver = create_driver(uri)
262263
mocker.patch.object(driver, "_pool", autospec=True)
263264

264265
try:
265-
with pytest.warns(DeprecationWarning, match="configuration"):
266+
with pytest.warns(ExperimentalWarning, match="configuration"):
266267
await driver.verify_connectivity(**kwargs)
267268
finally:
268269
await driver.close()
270+
271+
272+
@pytest.mark.parametrize("uri", (
273+
"bolt://127.0.0.1:9000",
274+
"neo4j://127.0.0.1:9000",
275+
))
276+
@pytest.mark.parametrize("kwargs", (
277+
{"default_access_mode": WRITE_ACCESS},
278+
{"default_access_mode": READ_ACCESS},
279+
{"fetch_size": 69},
280+
))
281+
@mark_async_test
282+
async def test_get_server_info_parameters_are_experimental(
283+
uri, kwargs, mocker
284+
):
285+
driver = create_driver(uri)
286+
mocker.patch.object(driver, "_pool", autospec=True)
287+
288+
try:
289+
with pytest.warns(ExperimentalWarning, match="configuration"):
290+
await driver.get_server_info(**kwargs)
291+
finally:
292+
await driver.close()

0 commit comments

Comments
 (0)