与Neo4j数据库一样, ArangoDB生态圈内也有多种Python驱动程序,可供用户在程序中操作和管理ArangoDB服务器和数据库。
本文针对ArangoDB 3.x系列,可能在旧版本上无法使用
本教程基于Tariq Daouda的 pyArango 驱动程序。你需要在主机上 安装 并启动ArangoDB,然后从Python Package Index中安装pyArango。
# 推荐在Python虚拟环境中安装
# 一旦完成安装过程,就可以在Python中开发ArangoDB应用程序
pip install pyArango # 亲测应该为pyArango
# pip install pyarango --user
使用Python
与使用传统RDBMS一样,同样需先建立一个与数据库服务器的连接,才能在Python应用中操作数据库。
PyArango通过Connection
类来方便管理服务器连接。
from pyArango.connection import *
conn = Connection(username="root", password="")
当以上代码执行时,它会初始化一个数据库连接赋值给conn, 默认情况, pyArango会尝试建立与http://127.0.0.1:8529 的连接,即尝试与本地主机8529端口建立远程连接。当然你也可以通过 Connection
类指定其他的地址和端口。
建立并打开数据库
有了与ArangoDB服务器的连接之后,就可以在服务器上建立并打开数据库,并在其中进行操作。连接的createDatabase()
方法,会执行创建操作,并返回一个 Database
实例。
db = conn.createDatabase(name="school")
此时,通过Web界面,可以看到school数据库已被建立,如下图:
如果 school
数据库不存在,则pyArango会建立它。 如果已经存在,则会打开该数据库。 还可以把已经存在的数据库名称用作conn的key来打开该数据库,比如
db = conn["school"]
print(db)
# 输出:
ArangoDB database: school
建立集合 Collections
ArangoDB是把documents和edges都存入collections中,collections类似于RDBMS中的表,区别在于,collections是无模式的(schema-less),熟悉MongoDB的人对此不会陌生。
使用pyArango
, 可在指定数据库上通过调用 createCollection()方法创建collection。比如,我们在建好的 school
数据库中建立一个collection名叫 students
studentsCollection = db.createCollection(name="Students")
print(db["Students"])
# 输出, id可能不一样
ArangoDB collection name: Students, id: 411734, type: document, status: loaded
建立文档 Documents
下一步,就可以往collection中加入文档了,文档类似于RDBMS表中的行,同样,文档也是无模式的,你可以包含应用程序所需的任何值的排列。例如,我们在Students中加入一个学生文档:
doc1 = studentsCollection.createDocument()
doc1["name"] = "John Smith"
doc2 = studentsCollection.createDocument()
doc2["firstname"] = "Emily"
doc2["lastname"] = "Bronte"
print(doc1)
print(doc2)
# 输出:
ArangoDoc '_id: None, _key: None, _rev: None': <store: {'name': 'John Smith'}>
ArangoDoc '_id: None, _key: None, _rev: None': <store: {'firstname': 'Emily', 'lastname': 'Bronte'}>
文档中的_id
为 “None”是因为你还没有将该文档真正存入ArangoDB中,此时doc1,doc2变量都还只是Python程序中的变量,存储在内存中。此时去Web界面中,可以验证Students中确实没有数据,见下图:
ArangoDB通过把collection名字和_key
组合形成 _id
值,比如:
doc1._key = "johnsmith"
doc1.save()
print(doc1)
# 输出,此时Web界面中也可以看到加入的文档
# 可以看到, _id是把集合名字和_key组合而成
ArangoDoc '_id: Students/johnsmith, _key: johnsmith, _rev: _eDXDjOW---': <store: {'name': 'John Smith'}>
你可能希望通过循环输入数据,而不是单独手动输入和保存所有学生的数据。例如
students = [('Oscar', 'Wilde', 3.5), ('Thomas', 'Hobbes', 3.2),
('Mark', 'Twain', 3.0), ('Kate', 'Chopin', 3.8),
('Fyodor', 'Dostoevsky', 3.1), ('Jane', 'Austen',3.4),
('Mary', 'Wollstonecraft', 3.7), ('Percy', 'Shelley', 3.5),
('William', 'Faulkner', 3.8), ('Charlotte', 'Bronte', 3.0),
]
for first, last, gpa in students:
doc = studentsCollection.createDocument()
doc['name'] = f"{first} {last}"
doc['gpa'] = gpa
doc['year'] = 2017
doc._key = f"{first}{last}".lower()
doc.save()
执行后,在Web界面中能看到刚才新加入的一批文档
读取 Documents
加入后,你需要读取这些文档,最简单的方式是通过 _key
。假设在一个应用中,你想获取某个学生的GPA分数,则可以:
def report_gpa(document) -> str:
return f"Student: {document['name']} \
\nGPA:\t{document['gpa']}"
kate = studentsCollection['katechopin']
print(report_gpa(kate))
# 输出:
Student: Kate Chopin
GPA: 3.8
更新 Documents
当把一个文档从ArangoDB读出来后,可以对其进行修改,随后再用 save()
方法存回到数据库中,比如说每学期期末成绩出来后,需要更新学生们的平均分,因为这个操作要重复执行,可以定义专门的函数来更新:
def update_gpa(key:str, new_gpa: float):
doc = studentsCollection[key]
doc['gpa'] = new_gpa
doc.save()
列举全部 Documents
有时可能对给定集合中的所有文档进行操作。使用 fetchAll()
方法,可以获取和迭代文档列表。例如,学期末,你想知道哪些学生的平均成绩在3.5以上:
from typing import List
def top_scores(col, gpa) -> List[str]:
"""在指定的集合中,返回大于指定gpa的文档
Args:
col : 指定的Collection
gpa ([float]): gpa阈值
"""
return [stu['name'] for stu in col.fetchAll() if ('gpa' in stu) and (stu['gpa'] >= gpa)]
print("Top Scoring Students:")
print("\n".join(top_scores(studentsCollection, 3.5)))
# 输出:
Top Scoring Students:
Oscar Wilde
Kate Chopin
Mary Wollstonecraft
Percy Shelley
William Faulkner
删除 Documents
如果最终想从数据库中删掉documents,可以通过 delete()
方法,比如说学生'Thomas Hobbes'转学了:
tom = studentsCollection['thomashobbes']
tom.delete() # 从数据库中删掉了,从Web界面可验证
print(studentsCollection['thomashobbes'])
# 报错:
pyArango.theExceptions.DocumentNotFoundError: Unable to find document with _key: thomashobbes.
Errors: {'code': 404,
'error': True,
'errorMessage': 'document not found',
'errorNum': 1202}
使用AQL
除了前述介绍用python中的方法,ArangoDB提供的查询语言AQL,也可以进行CRUD操作,在pyArango中,可以使用 AQLQuery()
方法执行这些查询。
熟悉RDBMS的朋友,可以联想到在RDBMS中,既可以直接用SQL语言操作数据库,也可以通过ORM(Object-Relational Mapping)的方式操作数据库,但在ArangoDB这种NoSQL范式的数据库中,数据可以直接对应编程语言中的数据结构,所以就不需要做ORM映射,反而更简单。
例如,假设要检索ArangoDB中所有文档的_key
:
aql = "FOR x IN Students RETURN x._key"
# queryResult类型为: pyArango.query.AQLQuery
queryResult = db.AQLQuery(aql, rawResults=True, batchSize=100)
for k in queryResult:
print(k)
# 输出,猜测是按照字典序排列:
# 但官方文档里又提到,不保证顺序(待验证)
charlottebronte
fyodordostoevsky
janeausten
johnsmith
katechopin
marktwain
marywollstonecraft
oscarwilde
percyshelley
williamfaulkner
在上面的例子中,AQLQuery()
方法使用AQL查询作为参数,还有另外两个参数:
rawResults
定义是否要返回实际的查询结果, AQLQuery()中rawResults默认为True
batchSize
当查询返回的结果超过给定值时,pyArango驱动程序将自动请求新的批次。
提醒:此查询不能保证文档返回的顺序。如果需要特定顺序的结果,请向AQL查询中添加一个排序子句。
用AQL插入 Documents
也可以用AQL语句插入文档,可通过使用 AQLQuery()
方法的 bindVars
参数配合 INSERT
语句来完成:
# 随后可在Web界面中验证,确实插入了
doc = {'_key': 'denisdiderot', 'name': 'Denis Diderot', 'gpa': 3.7}
bind = {"doc": doc}
aql = """INSERT @doc INTO Students
LET newDoc = NEW
RETURN newDoc"""
queryResult = db.AQLQuery(aql, bindVars=bind)
# 由于aql语句中,新插入的文档作为了返回值赋给queryResult,可以访问看到:
print(queryResult[0])
# 返回:
ArangoDoc '_id: Students/denisdiderot, _key: denisdiderot, _rev: _eDYboWO---': <store: {'name': 'Denis Diderot', 'gpa': 3.7}>
用AQL更新 Documents
可以使用 UPDATE语句更新文档
# 查看旧分数
print(db['Students']['katechopin'])
grades = {'katechopin': 4.0} # 学生的新分数,此处只以一个学生为例
for k, gpa in grades.items():
doc = {'gpa': float(gpa)}
bind = {'doc': doc, 'key': k}
# UPDATE语句
aql = """UPDATE @key WITH @doc IN Students
LET updated = NEW
RETURN updated
"""
db.AQLQuery(aql, bindVars=bind)
# 查看更新后的分数
print(db['Students']['katechopin'])
# 输出
ArangoDoc '_id: Students/katechopin, _key: katechopin, _rev: _eDXSn3O---': <store: {'name': 'Kate Chopin', 'gpa': 3.8, 'year': 2017}>
ArangoDoc '_id: Students/katechopin, _key: katechopin, _rev: _eDYyqSG---': <store: {'name': 'Kate Chopin', 'gpa': 4, 'year': 2017}>
用AQL删除 Documents
使用REMOVE
语句可删除文档, 比如说某一年级的学生毕业了,你想将他们统一删除,可以使用FILTER
子句实现
bind = {"@collection": "Students"}
aql = """
FOR x IN @@collection
FILTER x.year == 2017
REMOVE x IN @@collection
LET removed = OLD
RETURN removed
"""
queryResult = db.AQLQuery(aql, bindVars=bind)
@@collection
(注意 两个@@
)定义集合名称的绑定变量。
# 查看被删掉的首尾两个学生
print(queryResult[0])
print(queryResult[-1])
# 输出:
ArangoDoc '_id: Students/oscarwilde, _key: oscarwilde, _rev: _eDXSn22---': <store: {'name': 'Oscar Wilde', 'gpa': 3.5, 'year': 2017}>
ArangoDoc '_id: Students/katechopin, _key: katechopin, _rev: _eDYyqSG---': <store: {'name': 'Kate Chopin', 'gpa': 4, 'year': 2017}>
# 再次提取该文档,会报错:
db["Students"]["williamfaulkner"]
pyArango.theExceptions.DocumentNotFoundError:
Unable to find document with _key: williamfaulkner.
Errors: {'code': 404, 'error': True, 'errorMessage': 'document not found', 'errorNum': 1202}