Skip to content

Commit 960aa76

Browse files
author
matmoncon
committed
fix: fix validation error when passing "*" to $maxHops
1 parent 98ff03c commit 960aa76

File tree

2 files changed

+47
-2
lines changed

2 files changed

+47
-2
lines changed

pyneo4j_ogm/queries/validators.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from typing import Any, Dict, List, Literal, Optional, Type, Union
99

1010
from pydantic import BaseModel, Field, ValidationError
11-
from pydantic.class_validators import root_validator, validator
1211

1312
from pyneo4j_ogm.logger import logger
1413
from pyneo4j_ogm.pydantic_utils import (
@@ -25,6 +24,8 @@
2524

2625
if IS_PYDANTIC_V2:
2726
from pydantic import field_validator, model_validator
27+
else:
28+
from pydantic.class_validators import root_validator, validator
2829

2930

3031
def _normalize_fields(cls: Type[BaseModel], values: Any) -> Any:
@@ -394,7 +395,7 @@ class MultiHopFiltersModel(BaseModel):
394395
"""
395396

396397
min_hops_: Optional[int] = Field(alias="$minHops", ge=0, default=None)
397-
max_hops_: Optional[Union[int, Literal["*"]]] = Field(alias="$maxHops", ge=1, default="*")
398+
max_hops_: Optional[Union[int, Literal["*"]]] = Field(alias="$maxHops", default="*")
398399
node_: MultiHopNodeModel = Field(alias="$node")
399400
relationships_: Optional[List[MultiHopRelationshipOperatorsModel]] = Field(alias="$relationships", default=None)
400401
direction_: Optional[RelationshipMatchDirection] = Field(
@@ -404,13 +405,25 @@ class MultiHopFiltersModel(BaseModel):
404405
if IS_PYDANTIC_V2:
405406
normalize_and_validate_fields = model_validator(mode="after")(_normalize_fields)
406407

408+
@field_validator("max_hops_")
409+
def validate_max_hops_v2(cls, v: Any) -> Any:
410+
if isinstance(v, int) and v <= 0:
411+
raise ValueError("$maxHops must be greater than 0")
412+
return v
413+
407414
model_config = {
408415
"extra": "allow",
409416
"use_enum_values": True,
410417
}
411418
else:
412419
normalize_and_validate_fields = root_validator(allow_reuse=True)(_normalize_fields)
413420

421+
@validator("max_hops_")
422+
def validate_max_hops_v1(cls, v: Any) -> Any:
423+
if isinstance(v, int) and v <= 0:
424+
raise ValueError("$maxHops must be greater than 0")
425+
return v
426+
414427
class Config:
415428
"""
416429
Pydantic configuration

tests/test_query_builder_filters.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,38 @@ def test_valid_multi_hop_filters(query_builder: QueryBuilder):
111111
assert query_builder.parameters == {"_n_0": ["Node"], "_n_1": "Jenny"}
112112

113113

114+
def test_valid_multi_hop_filters_with_min_hops(query_builder: QueryBuilder):
115+
query_builder.multi_hop_filters({"$node": {"$labels": "Node", "name": "Jenny"}, "$minHops": 3})
116+
117+
assert query_builder.query["match"] == ", path = (n)-[r*3..]->(m)"
118+
assert query_builder.query["where"] == "ALL(i IN labels(m) WHERE i IN $_n_0) AND m.name = $_n_1"
119+
assert query_builder.parameters == {"_n_0": ["Node"], "_n_1": "Jenny"}
120+
121+
122+
def test_valid_multi_hop_filters_with_max_hops(query_builder: QueryBuilder):
123+
query_builder.multi_hop_filters({"$node": {"$labels": "Node", "name": "Jenny"}, "$maxHops": 3})
124+
125+
assert query_builder.query["match"] == ", path = (n)-[r*..3]->(m)"
126+
assert query_builder.query["where"] == "ALL(i IN labels(m) WHERE i IN $_n_0) AND m.name = $_n_1"
127+
assert query_builder.parameters == {"_n_0": ["Node"], "_n_1": "Jenny"}
128+
129+
130+
def test_valid_multi_hop_filters_with_max_hops_special_char(query_builder: QueryBuilder):
131+
query_builder.multi_hop_filters({"$node": {"$labels": "Node", "name": "Jenny"}, "$maxHops": "*"})
132+
133+
assert query_builder.query["match"] == ", path = (n)-[r*]->(m)"
134+
assert query_builder.query["where"] == "ALL(i IN labels(m) WHERE i IN $_n_0) AND m.name = $_n_1"
135+
assert query_builder.parameters == {"_n_0": ["Node"], "_n_1": "Jenny"}
136+
137+
138+
def test_valid_multi_hop_filters_with_min_and_max_hops(query_builder: QueryBuilder):
139+
query_builder.multi_hop_filters({"$node": {"$labels": "Node", "name": "Jenny"}, "$minHops": 3, "$maxHops": 5})
140+
141+
assert query_builder.query["match"] == ", path = (n)-[r*3..5]->(m)"
142+
assert query_builder.query["where"] == "ALL(i IN labels(m) WHERE i IN $_n_0) AND m.name = $_n_1"
143+
assert query_builder.parameters == {"_n_0": ["Node"], "_n_1": "Jenny"}
144+
145+
114146
def test_valid_multi_hop_filters_with_ref(query_builder: QueryBuilder):
115147
query_builder.multi_hop_filters({"$node": {"$labels": "Node", "name": "Jenny"}}, start_ref="start", end_ref="end")
116148

0 commit comments

Comments
 (0)