Skip to content

Commit fd1c691

Browse files
Merge pull request #855 from neo4j-contrib/rc/5.4.3
Rc/5.4.3
2 parents ff7ee72 + f3fcae7 commit fd1c691

14 files changed

+113
-55
lines changed

.sonarcloud.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
sonar.sources = neomodel/
22
sonar.tests = test/
3-
sonar.python.version = 3.7, 3.8, 3.9, 3.10, 3.11
3+
sonar.python.version = 3.9, 3.10, 3.11, 3.12, 3.13

Changelog

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
Version 5.4.3 2025-02
2+
* Add Size() scalar function
3+
* Fix potential duplicate variable name in multiple traversals
4+
* Minor fixes
5+
* Note : With Neo4j now using CalVer, we can soon shift to true SemVer
6+
17
Version 5.4.2 2024-12
28
* Add support for Neo4j Rust driver extension : pip install neomodel[rust-driver-ext]
39
* Add initial_context parameter to subqueries

doc/source/configuration.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Adjust driver configuration - these options are only available for this connecti
3232
config.MAX_TRANSACTION_RETRY_TIME = 30.0 # default
3333
config.RESOLVER = None # default
3434
config.TRUST = neo4j.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES # default
35-
config.USER_AGENT = neomodel/v5.4.2 # default
35+
config.USER_AGENT = neomodel/v5.4.3 # default
3636

3737
Setting the database name, if different from the default one::
3838

doc/source/getting_started.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ Working with relationships::
235235
len(germany.inhabitant) # 1
236236

237237
# Find people called 'Jim' in germany
238-
germany.inhabitant.search(name='Jim')
238+
germany.inhabitant.filter(name='Jim')
239239

240240
# Find all the people called in germany except 'Jim'
241241
germany.inhabitant.exclude(name='Jim')

doc/source/spatial_properties.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,11 @@ Working with `PointProperty`
157157
----------------------------
158158
To define a ``PointProperty`` Node property, simply specify it along with its ``crs``: ::
159159

160+
from neomodel.contrib import spatial_properties as neomodel_spatial
161+
160162
class SomeEntity(neomodel.StructuredNode):
161163
entity_id = neomodel.UniqueIdProperty()
162-
location = neomodel.PointProperty(crs='wgs-84')
164+
location = neomodel_spatial.PointProperty(crs='wgs-84')
163165

164166
Given this definition of ``SomeEntity``, an object can be created by: ::
165167

neomodel/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "5.4.2"
1+
__version__ = "5.4.3"

neomodel/async_/match.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,8 @@ def __init__(
444444
self._ast = QueryAST()
445445
self._query_params: dict = {}
446446
self._place_holder_registry: dict = {}
447-
self._ident_count: int = 0
447+
self._relation_identifier_count: int = 0
448+
self._node_identifier_count: int = 0
448449
self._subquery_namespace: TOptional[str] = subquery_namespace
449450

450451
async def build_ast(self) -> "AsyncQueryBuilder":
@@ -494,9 +495,13 @@ async def build_source(
494495
return await self.build_node(source)
495496
raise ValueError("Unknown source type " + repr(source))
496497

497-
def create_ident(self) -> str:
498-
self._ident_count += 1
499-
return f"r{self._ident_count}"
498+
def create_relation_identifier(self) -> str:
499+
self._relation_identifier_count += 1
500+
return f"r{self._relation_identifier_count}"
501+
502+
def create_node_identifier(self, prefix: str) -> str:
503+
self._node_identifier_count += 1
504+
return f"{prefix}{self._node_identifier_count}"
500505

501506
def build_order_by(self, ident: str, source: "AsyncNodeSet") -> None:
502507
if "?" in source.order_by_elements:
@@ -535,7 +540,7 @@ async def build_traversal(self, traversal: "AsyncTraversal") -> str:
535540
rhs_label = ":" + traversal.target_class.__label__
536541

537542
# build source
538-
rel_ident = self.create_ident()
543+
rel_ident = self.create_relation_identifier()
539544
lhs_ident = await self.build_source(traversal.source)
540545
traversal_ident = f"{traversal.name}_{rel_ident}"
541546
rhs_ident = traversal_ident + rhs_label
@@ -600,7 +605,7 @@ def build_traversal_from_path(
600605
lhs_ident = stmt
601606

602607
already_present = part in subgraph
603-
rel_ident = self.create_ident()
608+
rel_ident = self.create_relation_identifier()
604609
rhs_label = relationship.definition["node_class"].__label__
605610
if relation.get("relation_filtering"):
606611
rhs_name = rel_ident
@@ -610,6 +615,7 @@ def build_traversal_from_path(
610615
rhs_name = relation["alias"]
611616
else:
612617
rhs_name = f"{rhs_label.lower()}_{rel_iterator}"
618+
rhs_name = self.create_node_identifier(rhs_name)
613619
rhs_ident = f"{rhs_name}:{rhs_label}"
614620
if relation["include_in_return"] and not already_present:
615621
self._additional_return(rhs_name)
@@ -1235,12 +1241,9 @@ def render(self, qbuilder: AsyncQueryBuilder) -> str:
12351241
class ScalarFunction(BaseFunction):
12361242
"""Base scalar function class."""
12371243

1238-
pass
1239-
1240-
1241-
@dataclass
1242-
class Last(ScalarFunction):
1243-
"""last() function."""
1244+
@property
1245+
def function_name(self) -> str:
1246+
raise NotImplementedError
12441247

12451248
def render(self, qbuilder: AsyncQueryBuilder) -> str:
12461249
if isinstance(self.input_name, str):
@@ -1250,7 +1253,25 @@ def render(self, qbuilder: AsyncQueryBuilder) -> str:
12501253
self._internal_name = self.input_name.get_internal_name()
12511254
else:
12521255
content = self.resolve_internal_name(qbuilder)
1253-
return f"last({content})"
1256+
return f"{self.function_name}({content})"
1257+
1258+
1259+
@dataclass
1260+
class Last(ScalarFunction):
1261+
"""last() function."""
1262+
1263+
@property
1264+
def function_name(self) -> str:
1265+
return "last"
1266+
1267+
1268+
@dataclass
1269+
class Size(ScalarFunction):
1270+
"""size() function."""
1271+
1272+
@property
1273+
def function_name(self) -> str:
1274+
return "size"
12541275

12551276

12561277
@dataclass

neomodel/async_/relationship.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ def __new__(
4444
return inst
4545

4646

47-
StructuredRelBase: type = RelationshipMeta(
48-
"RelationshipBase", (AsyncPropertyManager,), {}
49-
)
47+
class StructuredRelBase(AsyncPropertyManager, metaclass=RelationshipMeta):
48+
pass
5049

5150

5251
class AsyncStructuredRel(StructuredRelBase):

neomodel/async_/relationship_manager.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ def __init__(
433433
cls_name: str,
434434
direction: int,
435435
manager: type[AsyncRelationshipManager] = AsyncRelationshipManager,
436-
model: Optional[AsyncStructuredRel] = None,
436+
model: Optional[type[AsyncStructuredRel]] = None,
437437
) -> None:
438438
self._validate_class(cls_name, model)
439439

@@ -486,7 +486,7 @@ def __init__(
486486
adb._NODE_CLASS_REGISTRY[label_set] = model
487487

488488
def _validate_class(
489-
self, cls_name: str, model: Optional[AsyncStructuredRel] = None
489+
self, cls_name: str, model: Optional[type[AsyncStructuredRel]] = None
490490
) -> None:
491491
if not isinstance(cls_name, (str, object)):
492492
raise ValueError("Expected class name or class got " + repr(cls_name))
@@ -552,7 +552,7 @@ def __init__(
552552
cls_name: str,
553553
relation_type: str,
554554
cardinality: type[AsyncRelationshipManager] = AsyncZeroOrMore,
555-
model: Optional[AsyncStructuredRel] = None,
555+
model: Optional[type[AsyncStructuredRel]] = None,
556556
) -> None:
557557
super().__init__(
558558
relation_type, cls_name, OUTGOING, manager=cardinality, model=model
@@ -565,7 +565,7 @@ def __init__(
565565
cls_name: str,
566566
relation_type: str,
567567
cardinality: type[AsyncRelationshipManager] = AsyncZeroOrMore,
568-
model: Optional[AsyncStructuredRel] = None,
568+
model: Optional[type[AsyncStructuredRel]] = None,
569569
) -> None:
570570
super().__init__(
571571
relation_type, cls_name, INCOMING, manager=cardinality, model=model
@@ -578,7 +578,7 @@ def __init__(
578578
cls_name: str,
579579
relation_type: str,
580580
cardinality: type[AsyncRelationshipManager] = AsyncZeroOrMore,
581-
model: Optional[AsyncStructuredRel] = None,
581+
model: Optional[type[AsyncStructuredRel]] = None,
582582
) -> None:
583583
super().__init__(
584584
relation_type, cls_name, EITHER, manager=cardinality, model=model

neomodel/sync_/match.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,8 @@ def __init__(
442442
self._ast = QueryAST()
443443
self._query_params: dict = {}
444444
self._place_holder_registry: dict = {}
445-
self._ident_count: int = 0
445+
self._relation_identifier_count: int = 0
446+
self._node_identifier_count: int = 0
446447
self._subquery_namespace: TOptional[str] = subquery_namespace
447448

448449
def build_ast(self) -> "QueryBuilder":
@@ -492,9 +493,13 @@ def build_source(
492493
return self.build_node(source)
493494
raise ValueError("Unknown source type " + repr(source))
494495

495-
def create_ident(self) -> str:
496-
self._ident_count += 1
497-
return f"r{self._ident_count}"
496+
def create_relation_identifier(self) -> str:
497+
self._relation_identifier_count += 1
498+
return f"r{self._relation_identifier_count}"
499+
500+
def create_node_identifier(self, prefix: str) -> str:
501+
self._node_identifier_count += 1
502+
return f"{prefix}{self._node_identifier_count}"
498503

499504
def build_order_by(self, ident: str, source: "NodeSet") -> None:
500505
if "?" in source.order_by_elements:
@@ -533,7 +538,7 @@ def build_traversal(self, traversal: "Traversal") -> str:
533538
rhs_label = ":" + traversal.target_class.__label__
534539

535540
# build source
536-
rel_ident = self.create_ident()
541+
rel_ident = self.create_relation_identifier()
537542
lhs_ident = self.build_source(traversal.source)
538543
traversal_ident = f"{traversal.name}_{rel_ident}"
539544
rhs_ident = traversal_ident + rhs_label
@@ -598,7 +603,7 @@ def build_traversal_from_path(
598603
lhs_ident = stmt
599604

600605
already_present = part in subgraph
601-
rel_ident = self.create_ident()
606+
rel_ident = self.create_relation_identifier()
602607
rhs_label = relationship.definition["node_class"].__label__
603608
if relation.get("relation_filtering"):
604609
rhs_name = rel_ident
@@ -608,6 +613,7 @@ def build_traversal_from_path(
608613
rhs_name = relation["alias"]
609614
else:
610615
rhs_name = f"{rhs_label.lower()}_{rel_iterator}"
616+
rhs_name = self.create_node_identifier(rhs_name)
611617
rhs_ident = f"{rhs_name}:{rhs_label}"
612618
if relation["include_in_return"] and not already_present:
613619
self._additional_return(rhs_name)
@@ -1231,12 +1237,9 @@ def render(self, qbuilder: QueryBuilder) -> str:
12311237
class ScalarFunction(BaseFunction):
12321238
"""Base scalar function class."""
12331239

1234-
pass
1235-
1236-
1237-
@dataclass
1238-
class Last(ScalarFunction):
1239-
"""last() function."""
1240+
@property
1241+
def function_name(self) -> str:
1242+
raise NotImplementedError
12401243

12411244
def render(self, qbuilder: QueryBuilder) -> str:
12421245
if isinstance(self.input_name, str):
@@ -1246,7 +1249,25 @@ def render(self, qbuilder: QueryBuilder) -> str:
12461249
self._internal_name = self.input_name.get_internal_name()
12471250
else:
12481251
content = self.resolve_internal_name(qbuilder)
1249-
return f"last({content})"
1252+
return f"{self.function_name}({content})"
1253+
1254+
1255+
@dataclass
1256+
class Last(ScalarFunction):
1257+
"""last() function."""
1258+
1259+
@property
1260+
def function_name(self) -> str:
1261+
return "last"
1262+
1263+
1264+
@dataclass
1265+
class Size(ScalarFunction):
1266+
"""size() function."""
1267+
1268+
@property
1269+
def function_name(self) -> str:
1270+
return "size"
12501271

12511272

12521273
@dataclass

0 commit comments

Comments
 (0)