介绍
python 创建neo4j 数据通常使用py2neo,但是这个包 官方声明已经停止更新,根据neo4j网站推荐使用neomodel
neomodel 使用起来很想django 中的orm,如果有django基础的上手很简单,而且neomodel 支持 neo4j 5.X版本更新维护的也较及时
用于neo4j图形数据库的对象图形映射器 (OGM) ,基于很棒的neo4j_driver构建
如果您需要 neomodel 方面的帮助,请在https://github.com/neo4j-contrib/neomodel/上的 GitHub 存储库上创建问题。
- 具有适当继承性的熟悉的基于类的模型定义。
- 强大的查询API。
- 通过基数限制实施模式。
- 全面的交易支持。
- 线程安全。
- 前/后保存/删除挂钩。
- 通过django_neomodel集成 Django
安装
从 pypi 安装(推荐):
python
pip install neomodel
测试安装
python
#设置变量
config.DATABASE_URL = "bolt://用户名:密码@ip地址:7687"
config.DATABASE_NAME = "neo4j"
#如果数据库中有数据就能看到结果了
results = db.cypher_query("match (n) return n")
print(results)
定义节点实体和关系
python
from neomodel import (config, StructuredNode, StringProperty, IntegerProperty,
UniqueIdProperty, RelationshipTo)
config.DATABASE_URL = 'bolt://neo4j_username:neo4j_password@localhost:7687'
class Country(StructuredNode):
code = StringProperty(unique_index=True, required=True)
class City(StructuredNode):
name = StringProperty(required=True)
country = RelationshipTo(Country, 'FROM_COUNTRY')
class Person(StructuredNode):
uid = UniqueIdProperty()
name = StringProperty(unique_index=True)
age = IntegerProperty(index=True, default=0)
# traverse outgoing IS_FROM relations, inflate to Country objects
country = RelationshipTo(Country, 'IS_FROM')
# traverse outgoing LIVES_IN relations, inflate to City objects
city = RelationshipTo(City, 'LIVES_IN')
节点可定义的属性
- AliasProperty: 别名属性,用于为节点属性设置别名。
- IntegerProperty: 整数属性,用于存储整数值。
- ArrayProperty: 数组属性,用于存储数组或列表。
- JSONProperty: JSON 属性,用于存储 JSON 格式的数据。
- BooleanProperty: 布尔属性,用于存储布尔值(True 或 False)。
- RegexProperty: 正则表达式属性,用于存储符合特定正则表达式模式的字符串。
- DateProperty: 日期属性,用于存储日期值。
- StringProperty: 字符串属性,用于存储字符串值。有时候用于添加额外的注释说明。
- DateTimeProperty: 日期时间属性,用于存储日期和时间值。
- UniqueIdProperty: 唯一标识属性,用于存储唯一标识符。
- DateTimeFormatProperty: 日期时间格式属性,用于存储格式化的日期时间值。
- PointProperty: 点属性,用于存储空间中的点。
- FloatProperty: 浮点数属性,用于存储浮点数值。
如果是已有数据库 可以上述生成模型
python
$ neomodel_inspect_database -db bolt://neo4j_username:neo4j_password@localhost:7687 --write-to yourapp/models.py
创建、更新、删除操作
python
jim = Person(name='Jim', age=3).save() # Create
jim.age = 4
jim.save() # 更新, (with validation)
jim.delete()
jim.refresh() # reload 从数据库中reload 属性
jim.element_id # 查看id
批量节点操作
create()
python
with db.transaction:
people = Person.create(
{'name': 'Tim', 'age': 83},
{'name': 'Bob', 'age': 23},
{'name': 'Jill', 'age': 34},
)
create_or_update()创建或更新
python
people = Person.create_or_update(
{'name': 'Tim', 'age': 83},
{'name': 'Bob', 'age': 23},
{'name': 'Jill', 'age': 34},
)
more_people = Person.create_or_update(
{'name': 'Tim', 'age': 73},
{'name': 'Bob', 'age': 35},
{'name': 'Jane', 'age': 24},
)
create_or_update() 方法用于原子性地创建或更新节点。在使用此方法时,Neomodel 会根据提供的属性来判断是否应该创建新节点还是更新现有节点。具体来说,Neomodel 会按照以下步骤进行操作:
- 对于每个传递给 create_or_update() 方法的参数,Neomodel 首先检查是否存在具有相同属性值的节点。如果存在,则认为这是一个更新操作,将现有节点的属性更新为新提供的属性值。
- 如果不存在具有相同属性值的节点,则 Neomodel 将创建一个新节点,并将提供的属性值设置为新节点的属性。
- 如果属性中包含唯一标识符(例如节点的 ID 或其他唯一值),Neomodel 将使用这些标识符来确定是否存在具有相同标识符的节点,并相应地执行创建或更新操作。
- 对于省略了默认值的字段,Neomodel 将根据默认生成新值。
python
class Dog(StructuredNode):
name = StringProperty(required=True)
owner = RelationshipTo('Person', 'owner')
class Person(StructuredNode):
name = StringProperty(unique_index=True)
pets = RelationshipFrom('Dog', 'owner')
bob = Person.get_or_create({"name": "Bob"})[0]
bobs_gizmo = Dog.get_or_create({"name": "Gizmo"}, relationship=bob.pets)
tim = Person.get_or_create({"name": "Tim"})[0]
tims_gizmo = Dog.get_or_create({"name": "Gizmo"}, relationship=tim.pets)
# not the same gizmo
assert bobs_gizmo[0] != tims_gizmo[0]
get_or_create()
获取节点
python
# Return all nodes
all_nodes = Person.nodes.all()
# Returns Person by Person.name=='Jim' or raises neomodel.DoesNotExist if no match
jim = Person.nodes.get(name='Jim')
# Will return None unless "bob" exists
someone = Person.nodes.get_or_none(name='bob')
# Will return the first Person node with the name bob. This raises neomodel.DoesNotExist if there's no match.
someone = Person.nodes.first(name='bob')
# Will return the first Person node with the name bob or None if there's no match
someone = Person.nodes.first_or_none(name='bob')
# Return set of nodes
people = Person.nodes.filter(age__gt=3)
关系
python
class Person(StructuredNode):
car = RelationshipTo('Car', 'OWNS', cardinality=One)
class Car(StructuredNode):
owner = RelationshipFrom('Person', 'OWNS', cardinality=One)
关系是通过
Relationship,
RelationshipTo,
RelationshipFrom
对象定义的。RelationshipTo, RelationshipFrom 还可以指定允许遍历关系的方向。在这个特定的例子中, Country 对象可以通过 Person 对象访问,但不能反过来。
是在类定义中的一个上使用 Relationship 。在所有这些情况下,可导航性对于在 Python 中定义的模型更为重要。在 Neo4J 中将建立一个关系,但在 Relationship 的情况下,将可以查询任一方向。
cardinality关系的约束如下 属性值如下
表示一个关系可以有上述四种约束
如果现有的数据违反了基数约束,则会抛出一个 CardinalityViolation 异常。
关系上的属性
python
class FriendRel(StructuredRel):
since = DateTimeProperty(
default=lambda: datetime.now(pytz.utc),
index=True
)
met = StringProperty()
# Uniqueness constraints for relationship properties
# are only available from Neo4j version 5.7 onwards
meeting_id = StringProperty(unique_index=True)
class Person(StructuredNode):
name = StringProperty()
friends = RelationshipTo('Person', 'FRIEND', model=FriendRel)
rel = jim.friends.connect(bob)
rel.since # datetime object
rel = jim.friends.connect(bob,
{'since': yesterday, 'met': 'Paris'})
print(rel.start_node().name) # jim
print(rel.end_node().name) # bob
rel.met = "Amsterdam"
rel.save()
cypher_query结果转本地模型对象
Z = neomodel.db.cypher_query("MATCH (:BasePerson)-[r:FRIENDS_WITH]->(:BasePers>(:BasePon) RETURN r", resolve_objects=True)
注意这里 resolve_objects 被设置为 True ,这使得返回的对象能够自动解析为它们的"本地"数据模型对应物。
高级查询
model的结构
python
class SupplierRel(StructuredRel):
since = DateTimeProperty(default=datetime.now)
class Supplier(StructuredNode):
name = StringProperty()
delivery_cost = IntegerProperty()
coffees = RelationshipTo('Coffee', 'SUPPLIES')
class Coffee(StructuredNode):
name = StringProperty(unique_index=True)
price = IntegerProperty()
suppliers = RelationshipFrom(Supplier, 'SUPPLIES', model=SupplierRel)
使用过滤器进行查询
python
# nodes with label Coffee whose price is greater than 2
Coffee.nodes.filter(price__gt=2)
try:
java = Coffee.nodes.get(name='Java')
except Coffee.DoesNotExist:
print "Couldn't find coffee 'Java'"
过滤器方法借用了带有双下划线前缀操作符的相同 Django 过滤器格式:
- lt - less than 小于
- gt - greater than 大于
- lte - less than or equal to
小于或等于 - gte - greater than or equal to
大于或等于 - ne - not equal 不等于
- in - item in list
在列表中 - isnull - True IS NULL, False IS NOT NULL
isnull - 真 IS NULL,假 IS NOT NULL - exact - string equals exact - 字符串相等
- iexact - string equals, case insensitive
iexact - 字符串相等,不区分大小写 - contains - contains string value
包含 - 包含字符串值 - icontains - contains string value, case insensitive
icontains - 包含字符串值,不区分大小写 - startswith - starts with string value
startswith - 以字符串值开始 - istartswith - starts with string value, case insensitive
- endswith - ends with string value
endswith - 以字符串值结束 - iendswith - ends with string value, case insensitive
iendswith - 以字符串值结束,不区分大小写 - regex - matches a regex expression
正则表达式 - 匹配一个正则表达式 - iregex - matches a regex expression, case insensitive
不区分大小写的正则表达式 - 匹配一个正则表达式,不区分大小写
使用 Q 对象进行复杂查找
python
Lang.nodes.filter(
Q(name__startswith='Py'),
Q(year=2005) | Q(year=2006)
)
有一个关系
has 方法检查(一个或多个)关系的存在,在这种情况下,它返回一组具有供应商的 Coffee 节点:
python
#suppliers 为model上的关系属性
Coffee.nodes.has(suppliers=True)
这可以通过设置 suppliers=False 来否定,以找到没有供应商的 Coffee 节点。
按属性排序 ¶
通过特定的属性对结果进行排序是通过 order_by 方法完成的:
要从之前定义的查询中移除排序,可以通过将 None 传递给 order_by 来实现:
python
# Sort in descending order
suppliers = Supplier.nodes.order_by('-delivery_cost')
# Don't order; yield nodes in the order neo4j returns them
suppliers = suppliers.order_by(None)
获取路径 ¶
您可以使用单个查询检索与节点和关系类相对应的已实例化对象的整个路径。
假设以下模式:
python
class PersonLivesInCity(StructuredRel):
some_num = IntegerProperty(index=True,
default=12)
class CountryOfOrigin(StructuredNode):
code = StringProperty(unique_index=True,
required=True)
class CityOfResidence(StructuredNode):
name = StringProperty(required=True)
country = RelationshipTo(CountryOfOrigin,
'FROM_COUNTRY')
class PersonOfInterest(StructuredNode):
uid = UniqueIdProperty()
name = StringProperty(unique_index=True)
age = IntegerProperty(index=True,
default=0)
country = RelationshipTo(CountryOfOrigin,
'IS_FROM')
city = RelationshipTo(CityOfResidence,
'LIVES_IN',
model=PersonLivesInCity)
Then, paths can be retrieved with:
然后,可以使用以下方式检索路径:
q = db.cypher_query("MATCH p=(:CityOfResidence)<-[:LIVES_IN]-(:PersonOfInterest)-[:IS_FROM]->(:CountryOfOrigin) RETURN p LIMIT 1",
resolve_objects = True)
事务
python
from neomodel import db
with db.transaction:
Person(name='Bob').save()
#或作为函数装饰器:
@db.transaction
def update_user_name(uid, name):
user = Person.nodes.filter(uid=uid)[0]
user.name = name
user.save()
实战如何使用 neomodel - 10 个常见示例
python
from pytest import raises
from neomodel import (StructuredNode, StringProperty, IntegerProperty, UniqueIdProperty,
RelationshipTo, RelationshipFrom)
from neomodel.exceptions import UniqueProperty, DeflateError
class UniqueUser(StructuredNode):
uid = UniqueIdProperty()
name = StringProperty()
age = IntegerProperty()
def test_unique_id_property_batch():
users = UniqueUser.create(
{'name': 'bob', 'age': 2},
{'name': 'ben', 'age': 3}
)
assert users[0].uid != users[1].uid
users = UniqueUser.get_or_create(
{'uid': users[0].uid},
{'name': 'bill', 'age': 4}
)
python
def pre_save(self):
HOOKS_CALLED['pre_save'] += 1
def post_save(self):
HOOKS_CALLED['post_save'] += 1
class Badger(StructuredNode):
name = StringProperty(unique_index=True)
friend = Relationship('Badger', 'FRIEND', model=FriendRel)
hates = RelationshipTo('Stoat', 'HATES', model=HatesRel)
class Stoat(StructuredNode):
name = StringProperty(unique_index=True)
hates = RelationshipTo('Badger', 'HATES', model=HatesRel)
def test_either_connect_with_rel_model():
paul = Badger(name="Paul").save()
tom = Badger(name="Tom").save()
# creating rels
new_rel = tom.friend.disconnect(paul)
new_rel = tom.friend.connect(paul)
assert isinstance(new_rel, FriendRel)
assert isinstance(new_rel.since, datetime)
# updating properties
new_rel.since = datetime.now(pytz.utc)
assert isinstance(new_rel.save(), FriendRel)