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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,6 @@ build-iPhoneSimulator/

# Avoid accidental commiting of dev notebooks
examples/dev/*

# Datasets from loaders such as OGBLoader
dataset/
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@
## Improvements

## Other changes

- Add support for PyArrow 21.0.0
- Drop support for PyArrow 17.0
- Support numpy 1.24.0
- Add support for neo4j 6.0
11 changes: 11 additions & 0 deletions graphdatascience/error/cypher_warning_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ def wrapper(self: CallerBase, *args: Any, **kwargs: Any) -> Any:
message=r"The query used a deprecated function. \('id'.*",
)

# with neo4j driver 6.0.0
warnings.filterwarnings(
"ignore",
message=r"warn: feature deprecated with replacement\. id is deprecated. It is replaced by elementId or consider using an application-generated id\.",
)
# with neo4j driver 6.0.0 + Neo4j 5.X
warnings.filterwarnings(
"ignore",
message=r"warn: feature deprecated without replacement\. id is deprecated and will be removed without a replacement\.",
)

return func(self, *args, **kwargs)

return cast(F, wrapper)
Expand Down
4 changes: 2 additions & 2 deletions graphdatascience/graph/ogb_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def _parse_homogeneous(self, dataset: HomogeneousOGBNDataset) -> tuple[list[pd.D
node_dict["features"] = graph["node_feat"].tolist() # type: ignore

if len(dataset.labels[0]) == 1:
node_dict["classLabel"] = [cl[0] for cl in dataset.labels]
node_dict["classLabel"] = [cl[0] for cl in dataset.labels] # type: ignore
else:
node_dict["classLabel"] = dataset.labels.tolist() # type: ignore

Expand Down Expand Up @@ -174,7 +174,7 @@ def _parse_heterogeneous(self, dataset: HeterogeneousOGBNDataset) -> tuple[list[

if node_label in class_labels:
if len(class_labels[node_label]) == 1:
node_dict["classLabel"] = [cl[0] for cl in class_labels[node_label]]
node_dict["classLabel"] = [cl[0] for cl in class_labels[node_label]] # type: ignore
else:
node_dict["classLabel"] = class_labels[node_label].tolist() # type: ignore

Expand Down
36 changes: 29 additions & 7 deletions graphdatascience/query_runner/neo4j_query_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,14 +196,12 @@ def run_cypher(
df = result.to_df()

if self._NEO4J_DRIVER_VERSION < SemanticVersion(5, 0, 0):
self._last_bookmarks = [session.last_bookmark()]
self._last_bookmarks = [session.last_bookmark()] # type: ignore
else:
self._last_bookmarks = session.last_bookmarks()

notifications = result.consume().notifications
if notifications:
for notification in notifications:
self._forward_cypher_warnings(notification)
result_summary = result.consume()
self._handle_notifications(result_summary)

return df

Expand Down Expand Up @@ -321,7 +319,21 @@ def encrypted(self) -> bool:
def driver_config(self) -> dict[str, Any]:
return self._config

def _forward_cypher_warnings(self, notification: dict[str, Any]) -> None:
def _handle_notifications(self, result_summary: neo4j.ResultSummary) -> None:
if self._NEO4J_DRIVER_VERSION < SemanticVersion(6, 0, 0):
notifications = result_summary.notifications
if notifications:
for notification in notifications:
self._forward_cypher_notification(notification)
if self._NEO4J_DRIVER_VERSION >= SemanticVersion(6, 0, 0):
status_objects = result_summary.gql_status_objects
for status in status_objects:
if status.raw_classification == "DEPRECATION" and not re.match(
r"(.*returned by the procedure.*)|(.*procedure field deprecated.*)", status.status_description
):
warnings.warn(DeprecationWarning(status.status_description))

def _forward_cypher_notification(self, notification: dict[str, Any]) -> None:
# (see https://neo4j.com/docs/status-codes/current/notifications/ for more details)
severity = notification["severity"]
if severity == "WARNING":
Expand Down Expand Up @@ -434,7 +446,7 @@ def _verify_connectivity(
category=neo4j.ExperimentalWarning,
message=r"^The configuration may change in the future.$",
)
else:
elif self._NEO4J_DRIVER_VERSION < SemanticVersion(6, 0, 0):
warnings.filterwarnings(
"ignore",
category=neo4j.ExperimentalWarning,
Expand All @@ -443,6 +455,13 @@ def _verify_connectivity(
"They might be changed or removed in any future version without prior notice.$"
),
)
else:
warnings.filterwarnings(
"ignore",
category=neo4j.warnings.PreviewWarning, # type: ignore[attr-defined]
message=(r"^Passing key-word arguments to verify_connectivity\(\) is a preview feature.*"),
)

self._driver.verify_connectivity(database=database)
break
except neo4j.exceptions.DriverError as e:
Expand Down Expand Up @@ -478,6 +497,9 @@ def __configure_warnings_filter(self) -> None:
warnings.filterwarnings("ignore", message=r".*The procedure has a deprecated field.*by 'gds.*")
# neo4j driver 4.4
warnings.filterwarnings("ignore", message=r".*The query used a deprecated field from a procedure.*by 'gds.*")
# neo4j driver 6.0
warnings.filterwarnings("ignore", message=r".*returned by the procedure.* is deprecated.*")
warnings.filterwarnings("ignore", message=r".*procedure field deprecated..*")

class ConnectivityRetriesConfig(NamedTuple):
max_retries: int = 600
Expand Down
7 changes: 7 additions & 0 deletions graphdatascience/tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ def is_neo4j_44(gds: GraphDataScience) -> bool:
return False


def id_warning_pattern() -> str:
if neo4j.__version__.startswith("6."):
return r"(.*feature deprecated without replacement\. id is deprecated and will be removed without a replacement.*)|(.*feature deprecated with replacement\. id is deprecated.*)"
else:
return r".*The query used a deprecated function.*[`']id[`'].*"


@pytest.fixture(autouse=True)
def clean_up(gds: GraphDataScience) -> Generator[None, None, None]:
yield
Expand Down
6 changes: 3 additions & 3 deletions graphdatascience/tests/integration/test_error_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

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

GRAPH_NAME = "g"

Expand Down Expand Up @@ -207,7 +207,7 @@ def test_forward_server_side_warning(gds: GraphDataScience) -> None:
if is_neo4j_44(gds):
return

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


Expand All @@ -219,7 +219,7 @@ def test_forward_driver_configured_warning(warning_driver: Driver) -> None:
if is_neo4j_44(gds):
return

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


Expand Down
4 changes: 2 additions & 2 deletions graphdatascience/tests/integration/test_graph_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from graphdatascience.query_runner.arrow_query_runner import ArrowQueryRunner
from graphdatascience.query_runner.query_runner import QueryRunner
from graphdatascience.server_version.server_version import ServerVersion
from graphdatascience.tests.integration.conftest import AUTH, DB, URI, is_neo4j_44
from graphdatascience.tests.integration.conftest import AUTH, DB, URI, id_warning_pattern, is_neo4j_44

GRAPH_NAME = "g"

Expand Down Expand Up @@ -844,7 +844,7 @@ def test_graph_relationships_stream_without_arrow(gds_without_arrow: GraphDataSc
else:
result = gds_without_arrow.beta.graph.relationships.stream(G, ["REL", "REL2"])

warnings.filterwarnings("ignore", category=DeprecationWarning, message="The query used a deprecated function")
warnings.filterwarnings("ignore", category=DeprecationWarning, message=id_warning_pattern())
expected = gds_without_arrow.run_cypher(
"MATCH (n)-[r]->(m) RETURN id(n) AS src_id, id(m) AS trg_id, type(r) AS rel_type"
)
Expand Down
1 change: 1 addition & 0 deletions graphdatascience/tests/integration/test_model_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ def test_missing_model_drop(gds: GraphDataScience) -> None:

@pytest.mark.model_store_location
@pytest.mark.filterwarnings("ignore: The query used a deprecated procedure.")
@pytest.mark.filterwarnings("ignore: .*feature deprecated with replacement")
def test_deprecated_model_Functions_still_work(gds: GraphDataScience, gs_model: GraphSageModel) -> None:
gds.beta.model.list()
gds.alpha.model.store(gs_model)
Expand Down
3 changes: 3 additions & 0 deletions graphdatascience/tests/integration/test_system_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def test_list_defaults(gds: GraphDataScience) -> None:


@pytest.mark.filterwarnings("ignore: The query used a deprecated procedure")
@pytest.mark.filterwarnings("ignore: .*feature deprecated with replacement")
@pytest.mark.enterprise
@pytest.mark.skip_on_aura
def test_alpha_backup(gds: GraphDataScience) -> None:
Expand All @@ -88,6 +89,7 @@ def test_backup(gds: GraphDataScience) -> None:


@pytest.mark.filterwarnings("ignore: The query used a deprecated procedure")
@pytest.mark.filterwarnings("ignore: .*feature deprecated with replacement")
@pytest.mark.enterprise
@pytest.mark.skip_on_aura
def test_alpha_restore(gds: GraphDataScience) -> None:
Expand All @@ -106,6 +108,7 @@ def test_restore(gds: GraphDataScience) -> None:


@pytest.mark.filterwarnings("ignore: The query used a deprecated procedure")
@pytest.mark.filterwarnings("ignore: .*feature deprecated with replacement.")
@pytest.mark.compatible_with(min_inclusive=ServerVersion(2, 5, 0))
def test_deprecated_endpoints(gds: GraphDataScience) -> None:
gds.beta.listProgress()
Expand Down
6 changes: 3 additions & 3 deletions requirements/base/base.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
multimethod >= 1.0, < 3.0
neo4j >= 4.4.12, < 6.0
numpy < 2.3
neo4j >= 4.4.12, < 7.0
numpy < 2.4
pandas >= 1.0, < 3.0
pyarrow >= 17.0, < 21.0
pyarrow >= 17.0, < 22.0
textdistance >= 4.0, < 5.0
tqdm >= 4.0, < 5.0
typing-extensions >= 4.0, < 5.0
Expand Down
2 changes: 1 addition & 1 deletion requirements/dev/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ ruff == 0.11.7
mypy == 1.13.0
nbconvert == 7.16.4
pandas-stubs == 2.2.3.241009
tox == 4.11.3
tox == 4.30.2
types-setuptools == 75.8.0.20250110
sphinx == 7.3.7
enum-tools[sphinx] == 0.12.0
Expand Down
2 changes: 1 addition & 1 deletion requirements/dev/test.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pytest == 8.3.3
requests_mock == 1.11.0
pytest_mock == 3.12.0
pytest_mock == 3.15.1
testcontainers >= 4.0, < 4.13.0
python-dateutil >= 2.9
39 changes: 22 additions & 17 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
[tox]
envlist =
# Vary Neo4j & Pandas versions
py{39, 310}-neo4j{4,5}-pandas{1,2}-main-{standard,encrypted},
# Vary Pandas 1.x + PyArrow
py{39,310}-pandas1-pyarrow{17, 18, 19, 20}-main-{standard,encrypted},
py311-pandas1-pyarrow{17, 18, 19, 20}-main-{standard,encrypted},
# Vary Pandas 2.x + PyArrow
py{39,310,311}-pandas2-pyarrow{19, 20}-main-{standard,encrypted},
# Vary Pandas versions
py310-neo4j6-pandas{1,2}-main-standard,
# Vary Neo4j versions
py310-neo4j{4,5,6}-pandas2-main-standard,
# Vary Pandas
py310-pandas{1,2}-pyarrow21-main-standard,
# Vary PyArrow
py311-pandas2-pyarrow{18, 19, 20, 21}-main-standard,
# Vary Python version
py3{12,13}-pandas2-pyarrow{20}-main-{standard,encrypted},
py3{9, 10, 11, 12, 13}-pandas2-pyarrow21-main-standard,

# PR envs
py{39,310}-neo4j{4,5}-pullrequest-{standard,encrypted},
py{311,312,313}-pullrequest-{standard,encrypted},
# encrypted tests
py310-neo4j6-pandas2-pyarrow21-main-encrypted,

# Aura
# against an AuraDS instance
py311-main-aura,

# Vary networkx & neo4j versions only for relevant tests
py311-neo4j{4,5,6}-pandas2-pyarrow20-networkx{2,3}-main-nx,

# Session tests
py39-neo4j{5}-pyarrow17-main-{cloud-architecture}
py39-pullrequest-{cloud-architecture}

# Vary networkx versions only for relevant tests
py311-neo4j{4,5}-pandas1-pyarrow20-networkx{2,3}-main-nx
py311-neo4j{4,5}-pandas2-pyarrow20-networkx{2,3}-main-nx

# PR envs
py10-neo4j{4,5,6}-pullrequest-{standard,encrypted},
py{39,311,312,313}-pullrequest-standard,
py39-pullrequest-{cloud-architecture}
py311-networkx3-pullrequest-nx

# Notebooks
Expand All @@ -45,14 +49,15 @@ deps =
-r {toxinidir}/requirements/dev/test.txt
neo4j4: neo4j >= 4.4.2, < 5.0
neo4j5: neo4j >= 5.0, < 6.0
neo4j6: neo4j >= 6.0, < 7.0
pandas1: pandas >= 1.0, < 2.0
pandas1: numpy == 1.24.3
pandas2: pandas >= 2.2.2, < 3.0
pandas2: numpy >= 2.0
pyarrow17: pyarrow >= 17.0, < 18.0
pyarrow18: pyarrow >= 18.0, < 19.0
pyarrow19: pyarrow >= 19.0, < 20.0
pyarrow20: pyarrow >= 20.0, < 21.0
pyarrow21: pyarrow >= 21.0, < 22.0
networkx2: networkx >= 2.0, < 3.0
networkx3: networkx >= 3.0, < 4.0
commands =
Expand Down