Skip to content

Commit 4d22919

Browse files
authored
Improve keyword query parameters (#836)
* API docs: clarify precedence between keyword and dict query parameters * Fix: parameters of internal functions could clash with keyword parameters. E.g., `db` and `imp_user` could not be used as keyword query parameters causing a not necessarily easy to understand error message. This got fixed by merging keyword and dict parameters at the top-most level. * Add tests for query parameter precedence
1 parent 16893a1 commit 4d22919

File tree

4 files changed

+70
-13
lines changed

4 files changed

+70
-13
lines changed

neo4j/work/result.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,21 +63,19 @@ def _qid(self):
6363
else:
6464
return self._raw_qid
6565

66-
def _tx_ready_run(self, query, parameters, **kwparameters):
66+
def _tx_ready_run(self, query, parameters):
6767
# BEGIN+RUN does not carry any extra on the RUN message.
6868
# BEGIN {extra}
6969
# RUN "query" {parameters} {extra}
70-
self._run(query, parameters, None, None, None, None, **kwparameters)
70+
self._run(query, parameters, None, None, None, None)
7171

72-
def _run(self, query, parameters, db, imp_user, access_mode, bookmarks,
73-
**kwparameters):
72+
def _run(self, query, parameters, db, imp_user, access_mode, bookmarks):
7473
query_text = str(query) # Query or string object
7574
query_metadata = getattr(query, "metadata", None)
7675
query_timeout = getattr(query, "timeout", None)
7776

7877
parameters = DataDehydrator.fix_parameters(
79-
dict(parameters or {}, **kwparameters),
80-
patch_utc="utc" in self._connection.bolt_patches
78+
parameters, patch_utc="utc" in self._connection.bolt_patches
8179
)
8280

8381
self._metadata = {

neo4j/work/simple.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ def run(self, query, parameters=None, **kwparameters):
185185
:type query: str, neo4j.Query
186186
:param parameters: dictionary of parameters
187187
:type parameters: dict
188-
:param kwparameters: additional keyword parameters
188+
:param kwparameters: additional keyword parameters.
189+
These take precedence over parameters passed as ``parameters``.
189190
:returns: a new :class:`neo4j.Result` object
190191
:rtype: :class:`neo4j.Result`
191192
"""
@@ -212,10 +213,11 @@ def run(self, query, parameters=None, **kwparameters):
212213
cx, hydrant, self._config.fetch_size, self._result_closed,
213214
self._result_error
214215
)
216+
parameters = dict(parameters or {}, **kwparameters)
215217
self._autoResult._run(
216218
query, parameters, self._config.database,
217219
self._config.impersonated_user, self._config.default_access_mode,
218-
self._bookmarks, **kwparameters
220+
self._bookmarks
219221
)
220222

221223
return self._autoResult

neo4j/work/transaction.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ def run(self, query, parameters=None, **kwparameters):
105105
:type query: str
106106
:param parameters: dictionary of parameters
107107
:type parameters: dict
108-
:param kwparameters: additional keyword parameters
108+
:param kwparameters: additional keyword parameters.
109+
These take precedence over parameters passed as ``parameters``.
109110
:returns: a new :class:`neo4j.Result` object
110111
:rtype: :class:`neo4j.Result`
111112
:raise TransactionError: if the transaction is already closed
@@ -138,7 +139,8 @@ def run(self, query, parameters=None, **kwparameters):
138139
)
139140
self._results.append(result)
140141

141-
result._tx_ready_run(query, parameters, **kwparameters)
142+
parameters = dict(parameters or {}, **kwparameters)
143+
result._tx_ready_run(query, parameters)
142144

143145
return result
144146

tests/unit/work/test_session.py

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,23 @@
2828
Transaction,
2929
unit_of_work,
3030
)
31+
from neo4j.io import IOPool
3132

3233
from ._fake_connection import FakeConnection
3334

3435

35-
@pytest.fixture()
36+
@pytest.fixture
3637
def pool(mocker):
37-
pool = mocker.MagicMock()
38-
pool.acquire = mocker.MagicMock(side_effect=iter(FakeConnection, 0))
38+
pool = mocker.Mock(spec=IOPool)
39+
assert not hasattr(pool, "acquired_connection_mocks")
40+
pool.acquired_connection_mocks = []
41+
42+
def acquire_side_effect(*_, **__):
43+
connection = FakeConnection()
44+
pool.acquired_connection_mocks.append(connection)
45+
return connection
46+
47+
pool.acquire.side_effect = acquire_side_effect
3948
return pool
4049

4150

@@ -252,3 +261,49 @@ def work(tx):
252261
session.write_transaction(work)
253262
else:
254263
raise ValueError(run_type)
264+
265+
266+
@pytest.mark.parametrize(
267+
("params", "kw_params", "expected_params"),
268+
(
269+
({"x": 1}, {}, {"x": 1}),
270+
({}, {"x": 1}, {"x": 1}),
271+
({"x": 1}, {"y": 2}, {"x": 1, "y": 2}),
272+
({"x": 1}, {"x": 2}, {"x": 2}),
273+
({"x": 1}, {"x": 2}, {"x": 2}),
274+
({"x": 1, "y": 3}, {"x": 2}, {"x": 2, "y": 3}),
275+
({"x": 1}, {"x": 2, "y": 3}, {"x": 2, "y": 3}),
276+
# potentially internally used keyword arguments
277+
({}, {"timeout": 2}, {"timeout": 2}),
278+
({"timeout": 2}, {}, {"timeout": 2}),
279+
({}, {"imp_user": "hans"}, {"imp_user": "hans"}),
280+
({"imp_user": "hans"}, {}, {"imp_user": "hans"}),
281+
({}, {"db": "neo4j"}, {"db": "neo4j"}),
282+
({"db": "neo4j"}, {}, {"db": "neo4j"}),
283+
({}, {"database": "neo4j"}, {"database": "neo4j"}),
284+
({"database": "neo4j"}, {}, {"database": "neo4j"}),
285+
)
286+
)
287+
@pytest.mark.parametrize("run_type", ("auto", "unmanaged", "managed"))
288+
def test_session_run_parameter_precedence(
289+
pool, params, kw_params, expected_params, run_type
290+
):
291+
with Session(pool, SessionConfig()) as session:
292+
if run_type == "auto":
293+
session.run("RETURN $x", params, **kw_params)
294+
elif run_type == "unmanaged":
295+
tx = session.begin_transaction()
296+
tx.run("RETURN $x", params, **kw_params)
297+
elif run_type == "managed":
298+
def work(tx):
299+
tx.run("RETURN $x", params, **kw_params)
300+
session.write_transaction(work)
301+
else:
302+
raise ValueError(run_type)
303+
304+
assert len(pool.acquired_connection_mocks) == 1
305+
connection_mock = pool.acquired_connection_mocks[0]
306+
connection_mock.run.assert_called_once()
307+
call_args, call_kwargs = connection_mock.run.call_args
308+
assert call_args[0] == "RETURN $x"
309+
assert call_kwargs["parameters"] == expected_params

0 commit comments

Comments
 (0)