Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@
## Other changes

* Updated required `neo4j` driver from `4.4.2` to the latest 4.4 path release (`4.4.12`) or later.
* Avoid duplications or user-indepedent logs and warnings introduced by the driver option `warn_notification_severity` in `neo4j>=5.21.0`.
26 changes: 22 additions & 4 deletions graphdatascience/query_runner/neo4j_query_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ def create(
else:
raise ValueError(f"Invalid endpoint type: {type(endpoint)}")

if Neo4jQueryRunner._NEO4J_DRIVER_VERSION >= ServerVersion(5, 21, 0):
notifications_logger = logging.getLogger("neo4j.notifications")
# the client does not expose YIELD fields so we just skip these warnings for now
notifications_logger.addFilter(
lambda record: (
"The query used a deprecated field from a procedure" in record.msg and "by 'gds." in record.msg
)
)

return query_runner

@staticmethod
Expand Down Expand Up @@ -124,10 +133,19 @@ def run_cypher(
else:
self._last_bookmarks = session.last_bookmarks()

notifications = result.consume().notifications
if notifications:
for notification in notifications:
self._forward_cypher_warnings(notification)
if (
Neo4jQueryRunner._NEO4J_DRIVER_VERSION >= ServerVersion(5, 21, 0)
and result._warn_notification_severity == "WARNING"
):
# the client does not expose YIELD fields so we just skip these warnings for now
warnings.filterwarnings(
"ignore", message=r".*The query used a deprecated field from a procedure\. .* by 'gds.* "
)
else:
notifications = result.consume().notifications
if notifications:
for notification in notifications:
self._forward_cypher_warnings(notification)

return df

Expand Down
21 changes: 17 additions & 4 deletions graphdatascience/tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def neo4j_driver() -> Generator[Driver, None, None]:

@pytest.fixture(scope="package")
def runner(neo4j_driver: Driver) -> Generator[Neo4jQueryRunner, None, None]:
_runner = Neo4jQueryRunner(neo4j_driver)
_runner = Neo4jQueryRunner.create(neo4j_driver)
_runner.set_database(DB)

yield _runner
Expand Down Expand Up @@ -194,10 +194,9 @@ def pytest_collection_modifyitems(config: Any, items: Any) -> None:
server_version = gds._server_version
except Exception as e:
print("Could not derive GDS library server version")
gds.close()
raise e

gds.close()
finally:
gds.close()

skip_incompatible_versions = pytest.mark.skip(reason=f"incompatible with GDS server version {server_version}")

Expand All @@ -211,3 +210,17 @@ def pytest_collection_modifyitems(config: Any, items: Any) -> None:

if "max_exclusive" in kwargs and kwargs["max_exclusive"] <= server_version:
item.add_marker(skip_incompatible_versions)

db_driver_version = Neo4jQueryRunner._NEO4J_DRIVER_VERSION
skip_incompatible_driver_version = pytest.mark.skip(reason=f"incompatible with driver version {db_driver_version}")

for item in items:
for mark in item.iter_markers(name="compatible_with_db_driver"):
kwargs = mark.kwargs

if "min_inclusive" in kwargs and kwargs["min_inclusive"] > db_driver_version:
item.add_marker(skip_incompatible_driver_version)
continue

if "max_exclusive" in kwargs and kwargs["max_exclusive"] <= db_driver_version:
item.add_marker(skip_incompatible_driver_version)
37 changes: 37 additions & 0 deletions graphdatascience/tests/integration/test_error_handling.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from typing import Generator

import pytest
from neo4j import Driver, GraphDatabase

from graphdatascience import GraphDataScience
from graphdatascience.server_version.server_version import ServerVersion
from graphdatascience.tests.integration.conftest import AUTH, URI

GRAPH_NAME = "g"

Expand All @@ -29,6 +32,15 @@ def run_around_tests(gds: GraphDataScience) -> Generator[None, None, None]:
gds.graph.drop(GRAPH_NAME)


@pytest.fixture
def warning_driver() -> Generator[Driver, None, None]:
driver = GraphDatabase.driver(URI, auth=AUTH, warn_notification_severity="WARNING")

yield driver

driver.close()


def test_bogus_algo(gds: GraphDataScience) -> None:
G, _ = gds.graph.project(GRAPH_NAME, "*", "*")
with pytest.raises(SyntaxError, match="There is no 'gds.bogusAlgoWithLongName.stream' to call$"):
Expand Down Expand Up @@ -189,3 +201,28 @@ def test_auto_completion_false_positives(gds: GraphDataScience) -> None:
# Without `graph` prefix
with pytest.raises(SyntaxError, match="There is no 'gds.toUndirected' to call"):
gds.toUndirected()


def test_forward_server_side_warning(gds: GraphDataScience) -> None:
with pytest.raises(Warning, match="The query used a deprecated function: `id`."):
gds.run_cypher("MATCH (n) RETURN id(n)")


@pytest.mark.filterwarnings("ignore: notification warnings are a preview feature")
@pytest.mark.compatible_with_db_driver(min_inclusive=ServerVersion(5, 21, 0))
def test_forward_driver_configured_warning(warning_driver: Driver) -> None:
gds = GraphDataScience(warning_driver)

with pytest.raises(Warning, match="The query used a deprecated function: `id`."):
gds.run_cypher("MATCH (n) RETURN id(n)")


def test_filter_out_client_related__warning(gds: GraphDataScience) -> None:
gds.graph.drop("g", failIfMissing=False)


@pytest.mark.filterwarnings("ignore: notification warnings are a preview feature")
@pytest.mark.compatible_with_db_driver(min_inclusive=ServerVersion(5, 21, 0))
def test_filter_out_client_related__driver_configured_warning(warning_driver: Driver) -> None:
gds = GraphDataScience(warning_driver)
gds.graph.drop("g", failIfMissing=False)
1 change: 1 addition & 0 deletions graphdatascience/tests/pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ markers =
encrypted_only: mark a test as requiring GDS with either bolt or https ssl.policy configured.
model_store_location: mark a test as a requiring neo4j config for gds.model.store_location
compatible_with: mark a test as only being compatible with certain GDS server versions
compatible_with_db_driver: mark a test as only being compatible with a certain Neo4j driver version
skip_on_aura: mark a test to not be run when targeting an AuraDS instance
only_on_aura: mark a test to be run only when targeting an AuraDS instance
ogb: mark a test as requiring the ogb dependency
Expand Down