diff --git a/neomodel/core.py b/neomodel/core.py index 907fc8fc..bb2e58d2 100644 --- a/neomodel/core.py +++ b/neomodel/core.py @@ -681,20 +681,18 @@ def save(self): # create or update instance node if hasattr(self, "element_id"): - # TODO Get Neo4j version at creation time, and use method accordingly - # Node will now always have element_id, and deprecated id for users to call for a while - # Calling id now returns element_id value instead though - # For Neo4j version 5 : element_id=id=Neo elementId - # For Neo4j version 4 : element_id_id=Neo id # update params = self.deflate(self.__properties__, self) - query = f""" - MATCH (n) WHERE {db.get_id_method()}(n)=$self - SET - """ - query += ", ".join([f"n.{key} = ${key}" + "\n" for key in params.keys()]) - for label in self.inherited_labels(): - query += f"SET n:`{label}`\n" + query = f"MATCH (n) WHERE {db.get_id_method()}(n)=$self\n" + + if params: + query += "SET " + query += ",\n".join([f"n.{key} = ${key}" for key in params]) + query += "\n" + if self.inherited_labels(): + query += "\n".join( + [f"SET n:`{label}`" for label in self.inherited_labels()] + ) self.cypher(query, params) elif hasattr(self, "deleted") and self.deleted: raise ValueError( diff --git a/neomodel/util.py b/neomodel/util.py index fe78715c..ca008a16 100644 --- a/neomodel/util.py +++ b/neomodel/util.py @@ -62,8 +62,9 @@ class Database(local): A singleton object via which all operations from neomodel to the Neo4j backend are handled with. """ + _NODE_CLASS_REGISTRY = {} + def __init__(self): - self._NODE_CLASS_REGISTRY = {} self._active_transaction = None self.url = None self.driver = None diff --git a/test/test_models.py b/test/test_models.py index 94ced50f..c5139447 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -28,6 +28,10 @@ def email_alias(self, value): self.email = value +class NodeWithoutProperty(StructuredNode): + pass + + def test_issue_233(): class BaseIssue233(StructuredNode): __abstract_node__ = True @@ -101,6 +105,12 @@ def test_save_to_model(): assert u.age == 3 +def test_save_node_without_properties(): + n = NodeWithoutProperty() + assert n.save() + assert n.element_id != "" + + def test_unique(): install_labels(User) User(email="jim1@test.com", age=3).save() @@ -231,6 +241,7 @@ def credit_account(self, amount): assert len(jim.labels()) == 1 assert jim.labels()[0] == "Shopper" + def test_inherited_optional_labels(): class BaseOptional(StructuredNode): __optional_labels__ = ["Alive"] @@ -244,16 +255,17 @@ def credit_account(self, amount): self.balance = self.balance + int(amount) self.save() - henry = ExtendedOptional(name='henry', balance=300).save() + henry = ExtendedOptional(name="henry", balance=300).save() henry.credit_account(50) - assert ExtendedOptional.__label__ == 'ExtendedOptional' + assert ExtendedOptional.__label__ == "ExtendedOptional" assert henry.balance == 350 assert len(henry.inherited_labels()) == 2 assert len(henry.labels()) == 2 assert set(henry.inherited_optional_labels()) == {"Alive", "RewardsMember"} + def test_mixins(): class UserMixin: name = StringProperty(unique_index=True)