Reputation: 1483
I would like to know if there's a way to associate different labels to a class with NeoModel. If not, what module can allow me to do this?
My understanding is that when using the following class declaration, "Person" is a label.
class Person(StructuredNode):
name = StringProperty(unique_index=True)
age = IntegerProperty(index=True, default=0)
Let's say that I'd like to add a second label, "Employed", "Unemployed", "Student".
With Cypher I could use:
CREATE(p:Person:Student)
Is there anyway I can achieve the same with NeoModel?
Notes: From my research working with labels yields faster queries than with properties (neo4j/cypher), which is why I would like employed/unemployed/student to be labels. Otherwise I would be fine adding "occupation" as a node property.
Upvotes: 8
Views: 2370
Reputation: 1321
As of 2020, while using neomodel 4.0.1
answer by All Іѕ Vаиітy doesn't produce the desired outcome. Therefore the answer from user3280193 is the most correct, but not without a small caveat, so let me elaborate.
First let's look, why label hacking is flawed:
class Unemployed(StructuredNode):
__label__ = 'Person:Unemployed'
name = StringProperty(unique_index=True)
Unemployed(name='Carol').save()
If you check, it cannot detect nodes later on properly, even if they are correctly saved in the database:
print(len(Unemployed.nodes)) # prints 0
One could think that if we had another class Person
, then we could retrieve it that way - unfortunately not. See for yourself:
class Unemployed(StructuredNode):
__label__ = 'Person:Unemployed'
name = StringProperty(unique_index=True)
class Person(StructuredNode):
name = StringProperty(unique_index=True)
Unemployed(name='Carol').save()
So far, so good, so let's try to get some nodes. The following outcome looks well.
print(len(Person.nodes)) # prints 1
However, the problem will arise when we try to access that node:
print(Person.nodes[0])
# Results in two exceptions
#
# Traceback (most recent call last):
# ...
# KeyError: frozenset({'Person', 'Unemployed'})
#
# During handling of the above exception, another exception occurred:
# ...
# neomodel.exceptions.ModelDefinitionMismatch: <exception str() failed>
I will not go into details why this happens, but simply put neomodel can't cope with label hacking as it was not designed for it. If anyone wants to understand this behaviour, I suggest looking into neomodel.core
part of the library.
Officially, neomodel promotes inheritance and mixins. Read more at:
https://neomodel.readthedocs.io/en/latest/extending.html#inheritance https://neomodel.readthedocs.io/en/latest/extending.html#mixins
As mixins don't provide additional labels, I will focus on inheritance. Let's assume the following example, where we go 2 levels deep into inheritance.
class Person(StructuredNode):
name = StringProperty(unique_index=True)
class Student(Person):
pass
class Employed(Person):
pass
class EmployedStudent(Student, Employed):
pass
Person(name='Bob').save()
Student(name='Will').save()
Employed(name='John').save()
EmployedStudent(name='Kim').save()
Results:
print(len(Person.nodes)) # 4
print(len(Student.nodes)) # 2
print(len(Employed.nodes)) # 2
print(len(EmployedStudent.nodes)) # 1
This has the correct behaviour but seemingly produces one artefact - the label EmployedStudent
. There is no simple hack to get rid of this additional label as it is crucial for automatic class resolution.
Conclusion: OGM has its cons, but I would anytime opt for the additional redundant label over writing cypher queries by myself for every class I construct.
Upvotes: 7
Reputation: 460
It can be done with subclasses, e.g.:
class Person(StructuredNode):
name = StringProperty(unique_index=True)
age = IntegerProperty(index=True, default=0)
class PersonSub(Person):
pass
Then, instantiate the subclass and call labels() method:
psub = PersonSub(name = 'Person Sub', age=30).save()
psub.labels() #['Person', 'PersonSub']
You can also use cypher query to verify:
psub.cypher("MATCH (a:PersonSub) RETURN a")
Upvotes: 2
Reputation: 26402
You can simply hack on the __label__
property,
class Person(StructuredNode):
__label__ = 'Label1:Label2:Label3'
name = StringProperty(unique_index=True)
age = IntegerProperty(index=True, default=0)
on saving, it will add labels Label1
, Label2
, and Label3
to the created node
Upvotes: 3
Reputation: 384
There isn't currently a method for adding labels to a neomodel structurednode, however this can be done via cypher. I'd be happy to add a method to do this also. You have to be-careful that the label doesn't conflict with a class name. You can return the labels of a node through its labels() method
Upvotes: 2