diff --git a/README.rst b/README.rst index 254fa067f..85df0c79e 100644 --- a/README.rst +++ b/README.rst @@ -31,7 +31,8 @@ Quick Example from neo4j import GraphDatabase - driver = GraphDatabase.driver("neo4j://localhost:7687", auth=("neo4j", "password")) + driver = GraphDatabase.driver("neo4j://localhost:7687", + auth=("neo4j", "password")) def add_friend(tx, name, friend_name): tx.run("MERGE (a:Person {name: $name}) " @@ -39,15 +40,16 @@ Quick Example name=name, friend_name=friend_name) def print_friends(tx, name): - for record in tx.run("MATCH (a:Person)-[:KNOWS]->(friend) WHERE a.name = $name " - "RETURN friend.name ORDER BY friend.name", name=name): + query = ("MATCH (a:Person)-[:KNOWS]->(friend) WHERE a.name = $name " + "RETURN friend.name ORDER BY friend.name") + for record in tx.run(query, name=name): print(record["friend.name"]) with driver.session() as session: - session.write_transaction(add_friend, "Arthur", "Guinevere") - session.write_transaction(add_friend, "Arthur", "Lancelot") - session.write_transaction(add_friend, "Arthur", "Merlin") - session.read_transaction(print_friends, "Arthur") + session.execute_write(add_friend, "Arthur", "Guinevere") + session.execute_write(add_friend, "Arthur", "Lancelot") + session.execute_write(add_friend, "Arthur", "Merlin") + session.execute_read(print_friends, "Arthur") driver.close() diff --git a/docs/source/api.rst b/docs/source/api.rst index fc19dbdd8..7f065513c 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -849,8 +849,8 @@ Managed Transactions (`transaction functions`) ============================================== Transaction functions are the most powerful form of transaction, providing access mode override and retry capabilities. -+ :meth:`neo4j.Session.write_transaction` -+ :meth:`neo4j.Session.read_transaction` ++ :meth:`neo4j.Session.execute_write` ++ :meth:`neo4j.Session.execute_read` These allow a function object representing the transactional unit of work to be passed as a parameter. This function is called one or more times, within a configurable time limit, until it succeeds. @@ -869,7 +869,7 @@ Example: def create_person(driver, name) with driver.session() as session: - node_id = session.write_transaction(create_person_tx, name) + node_id = session.execute_write(create_person_tx, name) def create_person_tx(tx, name): query = "CREATE (a:Person { name: $name }) RETURN id(a) AS node_id" diff --git a/docs/source/async_api.rst b/docs/source/async_api.rst index 3bf4c34ac..c39e7b77b 100644 --- a/docs/source/async_api.rst +++ b/docs/source/async_api.rst @@ -522,8 +522,8 @@ Managed Async Transactions (`transaction functions`) ==================================================== Transaction functions are the most powerful form of transaction, providing access mode override and retry capabilities. -+ :meth:`neo4j.AsyncSession.write_transaction` -+ :meth:`neo4j.AsyncSession.read_transaction` ++ :meth:`neo4j.AsyncSession.execute_write` ++ :meth:`neo4j.AsyncSession.execute_read` These allow a function object representing the transactional unit of work to be passed as a parameter. This function is called one or more times, within a configurable time limit, until it succeeds. @@ -542,7 +542,7 @@ Example: async def create_person(driver, name) async with driver.session() as session: - node_id = await session.write_transaction(create_person_tx, name) + node_id = await session.execute_write(create_person_tx, name) async def create_person_tx(tx, name): query = "CREATE (a:Person { name: $name }) RETURN id(a) AS node_id" diff --git a/docs/source/index.rst b/docs/source/index.rst index 21530e0d2..9fe6c7686 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -109,9 +109,9 @@ Creating nodes and relationships. name=name, friend=friend) with driver.session() as session: - session.write_transaction(create_person, "Alice") - session.write_transaction(create_friend_of, "Alice", "Bob") - session.write_transaction(create_friend_of, "Alice", "Carl") + session.execute_write(create_person, "Alice") + session.execute_write(create_friend_of, "Alice", "Bob") + session.execute_write(create_friend_of, "Alice", "Carl") driver.close() @@ -135,7 +135,7 @@ Finding nodes. return friends with driver.session() as session: - friends = session.read_transaction(get_friends_of, "Alice") + friends = session.execute_read(get_friends_of, "Alice") for friend in friends: print(friend) @@ -164,7 +164,7 @@ Example Application def create_friendship(self, person1_name, person2_name): with self.driver.session() as session: # Write transactions allow the driver to handle retries and transient errors - result = session.write_transaction( + result = session.execute_write( self._create_and_return_friendship, person1_name, person2_name) for record in result: print("Created friendship between: {p1}, {p2}".format( @@ -197,7 +197,7 @@ Example Application def find_person(self, person_name): with self.driver.session() as session: - result = session.read_transaction(self._find_and_return_person, person_name) + result = session.execute_read(self._find_and_return_person, person_name) for record in result: print("Found person: {record}".format(record=record)) diff --git a/tests/integration/test_readme.py b/tests/integration/test_readme.py index dd54444bf..872461307 100644 --- a/tests/integration/test_readme.py +++ b/tests/integration/test_readme.py @@ -16,9 +16,7 @@ # limitations under the License. -import pytest - -from neo4j.exceptions import ServiceUnavailable +from pathlib import Path # python -m pytest tests/integration/test_readme.py -s -v @@ -28,9 +26,14 @@ def test_should_run_readme(uri, auth): names = set() print = names.add + # === START: README === from neo4j import GraphDatabase + driver = GraphDatabase.driver("neo4j://localhost:7687", + auth=("neo4j", "password")) + # === END: README === driver = GraphDatabase.driver(uri, auth=auth) + # === START: README === def add_friend(tx, name, friend_name): tx.run("MERGE (a:Person {name: $name}) " @@ -38,20 +41,48 @@ def add_friend(tx, name, friend_name): name=name, friend_name=friend_name) def print_friends(tx, name): - for record in tx.run( - "MATCH (a:Person)-[:KNOWS]->(friend) WHERE a.name = $name " - "RETURN friend.name ORDER BY friend.name", name=name): + query = ("MATCH (a:Person)-[:KNOWS]->(friend) WHERE a.name = $name " + "RETURN friend.name ORDER BY friend.name") + for record in tx.run(query, name=name): print(record["friend.name"]) with driver.session() as session: + # === END: README === session.run("MATCH (a) DETACH DELETE a") - + # === START: README === session.execute_write(add_friend, "Arthur", "Guinevere") session.execute_write(add_friend, "Arthur", "Lancelot") session.execute_write(add_friend, "Arthur", "Merlin") session.execute_read(print_friends, "Arthur") - + # === END: README === session.run("MATCH (a) DETACH DELETE a") + # === START: README === driver.close() + # === END: README === assert names == {"Guinevere", "Lancelot", "Merlin"} + + +def test_readme_contains_example(): + test_path = Path(__file__) + readme_path = test_path.parents[2] / "README.rst" + + with test_path.open("r") as fd: + test_content = fd.read() + with readme_path.open("r") as fd: + readme_content = fd.read() + + stripped_test_content = "" + + adding = False + for line in test_content.splitlines(keepends=True): + if line.strip() == "# === START: README ===": + adding = True + continue + elif line.strip() == "# === END: README ===": + adding = False + continue + if adding: + stripped_test_content += line + + assert stripped_test_content in readme_content diff --git a/tests/unit/async_/work/test_session.py b/tests/unit/async_/work/test_session.py index b14ffbe67..0a9caea69 100644 --- a/tests/unit/async_/work/test_session.py +++ b/tests/unit/async_/work/test_session.py @@ -37,6 +37,16 @@ from ...._async_compat import mark_async_test +@contextmanager +def assert_warns_tx_func_deprecation(tx_func_name): + if tx_func_name.endswith("_transaction"): + with pytest.warns(DeprecationWarning, + match=f"{tx_func_name}.*execute_"): + yield + else: + yield + + @mark_async_test async def test_session_context_calls_close(mocker): s = AsyncSession(None, SessionConfig()) @@ -237,17 +247,25 @@ async def test_session_run_wrong_types(fake_pool, query, error_type): await session.run(query) -@pytest.mark.parametrize("tx_type", ("write_transaction", "read_transaction")) +@pytest.mark.parametrize("tx_type", ("write_transaction", "read_transaction", + "execute_write", "execute_read",)) @mark_async_test async def test_tx_function_argument_type(fake_pool, tx_type): + called = False + async def work(tx): + nonlocal called + called = True assert isinstance(tx, AsyncManagedTransaction) async with AsyncSession(fake_pool, SessionConfig()) as session: - await getattr(session, tx_type)(work) + with assert_warns_tx_func_deprecation(tx_type): + await getattr(session, tx_type)(work) + assert called -@pytest.mark.parametrize("tx_type", ("write_transaction", "read_transaction")) +@pytest.mark.parametrize("tx_type", ("write_transaction", "read_transaction", + "execute_write", "execute_read")) @pytest.mark.parametrize("decorator_kwargs", ( {}, {"timeout": 5}, @@ -259,12 +277,18 @@ async def work(tx): async def test_decorated_tx_function_argument_type( fake_pool, tx_type, decorator_kwargs ): + called = False + @unit_of_work(**decorator_kwargs) async def work(tx): + nonlocal called + called = True assert isinstance(tx, AsyncManagedTransaction) async with AsyncSession(fake_pool, SessionConfig()) as session: - await getattr(session, tx_type)(work) + with assert_warns_tx_func_deprecation(tx_type): + await getattr(session, tx_type)(work) + assert called @mark_async_test diff --git a/tests/unit/sync/work/test_session.py b/tests/unit/sync/work/test_session.py index 2c49b5ffb..99f7b5045 100644 --- a/tests/unit/sync/work/test_session.py +++ b/tests/unit/sync/work/test_session.py @@ -37,6 +37,16 @@ from ...._async_compat import mark_sync_test +@contextmanager +def assert_warns_tx_func_deprecation(tx_func_name): + if tx_func_name.endswith("_transaction"): + with pytest.warns(DeprecationWarning, + match=f"{tx_func_name}.*execute_"): + yield + else: + yield + + @mark_sync_test def test_session_context_calls_close(mocker): s = Session(None, SessionConfig()) @@ -237,17 +247,25 @@ def test_session_run_wrong_types(fake_pool, query, error_type): session.run(query) -@pytest.mark.parametrize("tx_type", ("write_transaction", "read_transaction")) +@pytest.mark.parametrize("tx_type", ("write_transaction", "read_transaction", + "execute_write", "execute_read",)) @mark_sync_test def test_tx_function_argument_type(fake_pool, tx_type): + called = False + def work(tx): + nonlocal called + called = True assert isinstance(tx, ManagedTransaction) with Session(fake_pool, SessionConfig()) as session: - getattr(session, tx_type)(work) + with assert_warns_tx_func_deprecation(tx_type): + getattr(session, tx_type)(work) + assert called -@pytest.mark.parametrize("tx_type", ("write_transaction", "read_transaction")) +@pytest.mark.parametrize("tx_type", ("write_transaction", "read_transaction", + "execute_write", "execute_read")) @pytest.mark.parametrize("decorator_kwargs", ( {}, {"timeout": 5}, @@ -259,12 +277,18 @@ def work(tx): def test_decorated_tx_function_argument_type( fake_pool, tx_type, decorator_kwargs ): + called = False + @unit_of_work(**decorator_kwargs) def work(tx): + nonlocal called + called = True assert isinstance(tx, ManagedTransaction) with Session(fake_pool, SessionConfig()) as session: - getattr(session, tx_type)(work) + with assert_warns_tx_func_deprecation(tx_type): + getattr(session, tx_type)(work) + assert called @mark_sync_test