ArangoDB with Python

416 阅读7分钟

与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数据库已被建立,如下图:

截屏2022-04-25 下午3.07.25.png

如果 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中确实没有数据,见下图:

截屏2022-04-25 下午3.55.27.png

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界面中能看到刚才新加入的一批文档

截屏2022-04-25 下午4.15.26.png

读取 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}

参考

[1]www.arangodb.com/tutorials/t…