乐趣区

关于python:周边生态贡献者1一个TDengine的Python-ORM库crown

本文介绍了一个用于操作 TDengine 的 Python ORM 库。本文的预期读者是,须要应用 Python 语言操作 TDengine 数据库的开发人员。

我的项目地址:
https://github.com/machine-w/crown

什么是 ORM?

ORM 就是对象关系映射(Object Relational Mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型零碎的数据之间的转换。从成果上说,它其实是创立了一个可在编程语言里应用的“虚构对象数据库”。简略说来就是,通过建设类与数据库表,对象与数据库数据条目标对应关系。从而能够通过编程语言的数据类型操作数据库。

为 TDengine 开发 ORM 库的动因

作为一个应用 Python 作为主力编程语言的开发者,笔者常常要编写操作各种数据库的代码。对于键值对类型(如:Redis)或者文档类型 (如:MongoDB) 的数据库,Python 生态都提供了很好的第三方连贯库。而对于最常应用的关系型数据(如:MySQL、PostgreSQL),Python 则提供了 SQLAlchemy、Peewee 等 ORM 第三方库的解决方案。所以,笔者日常工作中,须要手工拼接 SQL 查问语句的场景非常少 (甚至缓缓遗记了这项技能)。

近来,笔者须要率领团队实现一个智能电力系统的我的项目。在技术选型过程中发现了优良的物联网大数据平台 TDengine。通过测试和评估发现,无论从超高性能和稳定性、还是简洁的设计、开源的理念。TDengine 都非常适合作为智能电力系统的根底平台应用。然而,在应用过程中,咱们发现了一个比拟辣手的问题。那就是:因为 TDengine 诞生不久,相比拟其余曾经倒退很多年的其余数据库平台,周边的相干生态软件还略少一些。特地是,苹果操作系统 OS X 下临时没有原生连接器可用,写好的程序须要拿到 Linux 下来调试。这对于被“宠坏”的 python 程序员来讲真得没法适应。而且思考到笔者团队中其余程序员都习惯了 ORM 的操作形式,对原始 SQL 并不相熟。所以,笔者意识到:如果应用原生的连接器进行开发,将会遇到很多艰难。于是就开始了 TDengine 的开源 ORM 库的开发。一方面,能够帮忙团队更高效的实现零碎开发工作。另外一方面,也能够为帮忙 TDengine 更好的欠缺生态工具链。

如何装置和应用

简介

  • 须要 Python 3.0 版本以上
  • 在 TDengine 2.0.8 版本测试通过
  • 解决 Mac 操作系统下没有原生 Python 连接器的问题
  • 极大的升高了 Python 程序员应用 TDengine 技术门槛
  • 能够不便的将数据转换到 numpy 与 pandas

因为目前 TDengine 没有提供 Mac 操作系统下的原生 client, 为保障库的兼容性,目前 crown 库底层应用的 RESTful 接口进行连贯。当前的版本中,笔者将提供在 Windows 和 Linux 下的原生连接器接口可供配置应用。

我的项目地址:

https://github.com/machine-w/crown

装置

crown 库像其余 Python 第三方库一样,能够通过 pip,轻松装置最新版本:

pip install crown

还能够通过 git 装置,应用办法:

git clone https://github.com/machine-w/crown.gitcd crowmpython setup.py install

简略应用

1. 连贯数据库

应用 crown 连贯 TDengine,只须要提供 taos restful 服务的地址、端口号、以及须要操作的数据库名。而后即可应用 TdEngineDatabase 类新建一个数据库对象。以下为连贯数据库的例子代码:

from crown import * #导入库
DATABASENAME = 'taos_test'  #数据库名
HOST = 'localhost'
PORT = 6041 
db = TdEngineDatabase(DATABASENAME) # 默认端口 6041,默认用户名:root, 默认明码:taosdata
#如不应用默认参数,能够应用上面的办法提供参数
# db = TdEngineDatabase(DATABASENAME,host=HOST,port=PORT,user='yourusername',passwd='yourpassword') 
 # 个别状况咱们应用 connect 办法尝试连贯数据库,如果以后数据库不存在,则会主动建库。db.connect() 
# 连贯数据库后,db 对象后会主动获取全副数据库信息,以字典的模式保留在属性 databases 中。print(db.databases) 
#当然也能够应用手动建库办法建设数据库。db.create_database(safe=True)   #参数 safe 为 True 示意:如果库存在,则跳过建库指令。#可选字段:建库时配置数据库参数,具体字段含意请参考 tdengine 文档。# db.create_database(safe=True,keep= 100,comp=0,replica=1,quorum=2,blocks=115) 
#能够通过调用 alter_database 办法批改数据库参数。db.alter_database(keep= 120,comp=1,replica=1,quorum=1,blocks=156) 
#删除以后数据库办法 drop_database
db.drop_database(safe=True) #参数 safe:如果库不存在,则跳过删库指令。

实践上应用 crown 库操作 TDengine,所有的数据库操作都无需手工拼装 SQL 语句,然而为了应答比拟非凡的利用场景,crown 库也提供了执行原始 SQL 语句的性能。

# 通过数据库对象的 raw_sql 办法间接执行 sql 语句,语句规定与 TDengine restful 接口要求统一。res = db.raw_sql('select c1,c2 from taos_test.member1')
print(res)
print(res.head)
print(res.rowcount) 
#返回的对象为二维数据。res.head 属性为数组对象,保留每一行数据的代表的列名。res.rowcount 属性保留返回行数。# res: [[1.2,2.2],[1.3,2.1],[1.5,2.0],[1.6,2.1]]
# res.head: ['c1','c2']
# res.rowcount: 4

2. 建表删表操作

建好数据库对象后,就能够通过为 model 类建设子类的形式定义并新建数据库表(应用办法和 Python 中罕用的 ORM 库相似),新建的一个类对应数据库中的一张表,类的一个对象对应表中一条数据。以下示例创立一个简略的数据库表:

# 表模型类继承自 Model 类,每个模型类对应数据库中的一张表,模型类中定义的每个 Field,对应表中的一列,# 如果不明确定义主键类型字段,会默认增加一个主键,主键名为“ts”class Meter1(Model):
    cur = FloatField()  #如果省略列名参数,则应用属性名作为列名
    curInt = IntegerField(db_column='c2')
    curDouble = DoubleField(db_column='c3')
    desc = BinaryField(db_column='des')
    # custom_ts = PrimaryKeyField() # 如果定义了主键列,则应用主键列名作为主键
    class Meta: # Meta 子类中定义模型类的配置信息
        database = db # 指定之前建的数据库对象
        db_table = 'meter1' # 指定表名

crown 反对的字段类型与 TDengine 字段类型的对应关系:

定义好表模型后,即可调用类办法 create_table 进行建表操作:

# create_table 运行胜利返回 True, 失败则 raise 谬误
Meter1.create_table(safe=True) #safe:如果表存在,则跳过建表指令
#也能够通过数据库对象的 create_table 办法进行建表。# db.create_table(Meter1,safe=True) 
# drop_table 办法进行删除表操作,运行胜利返回 True, 失败则 raise 谬误
Meter1.drop_table(safe=True) #safe:如果表不存在,则跳过删表指令
#同样能够通过数据库对象删表,性能同上
# db.drop_table(Meter1,safe=True) 
#table_exists 办法查看表是否存在,存在返回 True, 不存在返回:False
Meter1.table_exists()

3. 数据插入

能够通过新建的数据表类 Meter1 新建数据对象并传入具体字段的数值,而后应用对象的 save 办法插入数据。

也能够间接应用 Meter1 类的类办法 insert 直接插入数据。上面的例子别离演示了这两种办法:

import time
#办法一
for i in range(1,101):
    #应用模型类实例化的每个对象对应数据表中的每一行,能够通过传入属性参数的形式给每一列赋值
    m = Meter1(cur = 1/i,curInt=i,curDouble=1/i+10,desc='g1',ts= datetime.datetime.now())
    time.sleep(1)
    #应用对象的 save 办法将数据存入数据库
    m.save()
print(Meter1.select().count()) # 后果:100
#办法二
for i in range(1,101):
    #也能够间接应用模型类的 insert 办法插入数据。Meter1.insert(cur = 1/i,curInt=i,curDouble=1/i+10,desc='g1',ts= datetime.datetime.now() - datetime.timedelta(seconds=(102-i)))
print(Meter1.select().count()) # 后果:101

如果不传入工夫属性 ts,则会以以后时刻为默认值传入

Meter1.insert(cur = 1/i,curInt=i,curDouble=1/i+10,desc='g1')
m = Meter1(cur = 1/i,curInt=i,curDouble=1/i+10,desc='g1')

4. 数据查问

crown 提供了丰盛的数据查问性能,因为篇幅的起因,这里只介绍笔者我的项目中比拟罕用的几种查问。理解更多的查问应用办法,请查看我的项目文档:

https://github.com/machine-w/crown/blob/main/README.rst

单条数据查问 :应用 Meter1 类的 select() 办法能够获取表的查问对象,查问对象的 one 办法能够获取满足条件的第一条数据。

# 获取一条数据:应用 select()类办法获取查问字段(参数留空示意取全副字段),而后能够链式应用 one 办法获取第一条数据
res = Meter1.select().one()
print(res.desc,res.curDouble,res.curInt,res.cur,res.ts)
#select 函数中能够抉择要读取的字段
res = Meter1.select(Meter1.cur,Meter1.desc).one()
print(res.desc,res.curDouble,res.curInt,res.cur,res.ts)

多条数据查问 :应用 Meter1 类的 select() 办法能够获取表的查问对象,查问对象的 all 办法能够获取满足条件的全副数据。

# 应用 select()类办法获取查问字段(参数留空示意取全副字段),而后能够链式应用 all 办法获取全副数据
res_all = Meter1.select().all()
for res in res_all:
    print(res.desc,res.curDouble,res.curInt,res.cur,res.ts)
#select 函数中能够抉择要读取的字段
res_all = Meter1.select(Meter1.cur,Meter1.desc).all()
for res in res_all:
    print(res.desc,res.curDouble,res.curInt,res.cur,res.ts)

读取数据导入 numpy 和 pandas:尽管 TDengine 提供了很多聚合和统计函数,然而把时序数据导入 numpy 或 pandas 等数据分析组件中进行解决的状况也是很常见的操作。

上面演示如何通过 crown 把后果数据导入 numpy 和 pandas:

# 导入 numpy
#通过 all_raw 函数能够获取二维数组格局的数据查问后果。后果每列代表的题目保留在后果对象的 head 属性中。raw_results = Meter1.select(Meter1.cur,Meter1.curInt,Meter1.curDouble).all_raw()
#能够很不便的将后果转换为 numpy 数组对象
np_data = np.array(raw_results)
print(np_data)
print(raw_results.head)
#导入 pandas
raw_results = Meter1.select().all_raw()
#应用以下办法,能够轻松的将数据导入 pandas, 并且应用工夫点作为 index, 应用返回的数据题目作为列名。pd_data = pd.DataFrame(raw_results,columns=raw_results.head).set_index('ts')
print(pd_data)

抉择列四则运算

# 应用 select()类办法获取查问字段时,能够返回某列或多列间的值加、减、乘、除、取余计算结果(+ - * / %)res_all = Meter1.select((Meter1.curDouble+Meter1.cur),Meter1.ts).all()
for res in res_all:
    #返回的后果对象能够用 get 办法获取原始计算式后果
    print(res.get(Meter1.curDouble+Meter1.cur),res.ts) 
#字段别名
#给运算式起别名(不仅运算式,其余放在 select 函数中的任何属性都能够应用别名)res_all = Meter1.select(((Meter1.curDouble+Meter1.cur)*Meter1.curDouble).alias('new_name'),Meter1.ts).all() 
for res in res_all:
    #应用别名获取运算后果
    print(res.new_name,res.ts)

where 函数

# 能够在 select 函数后链式调用 where 函数进行条件限
one_time =datetime.datetime.now() - datetime.timedelta(hours=10)
ress = Meter1.select().where(Meter1.ts > one_time).all()
#限定条件能够应用 > < == >= <= != and or ! 等。字符类型的字段能够应用 % 作为含糊查问(相当于 like)ress = Meter1.select().where(Meter1.cur > 0 or Meter1.desc % 'g%').all()
#where 函数能够接管任意多参数,每个参数为一个限定条件,参数条件之间为 "与" 的关系。ress = Meter1.select().where(Meter1.cur > 0, Meter1.ts > one_time, Meter1.desc % '%1').all()

分页与 limit

# 能够在 select 函数后链式调用 paginate 函数进行分页操作,以下例子为取第 6 页 每页 5 条数据。ress_1 = Meter1.select().paginate(6,page_size=5).all()
ress_2 = Meter1.select().paginate(6).all() #默认 page_size 为 20
#能够在 select 函数后链式调用 limit 函数和 offset 函数条数限度和定位操作。ress_3 = Meter1.select().limit(2).offset(5).all()
ress_4 = Meter1.select().limit(2).all()

排序:目前 TDengine 只反对主键排序

# 能够在 select 函数后链式调用 desc 或者 asc 函数进行时间轴的正序或者倒序查问
res = Meter1.select().desc().one()

以下是应用的例子。

#count
count = Meter1.select().count() #统计行数
print(count) # 后果:100
count = Meter1.select().count(Meter1.desc) #统计指定列非空行数
print(count) # 后果:90
#avg(sum,stddev,min,max,first,last,last_row,spread 应用办法与 avg 雷同)avg1 = Meter1.select().avg(Meter1.cur,Meter1.curDouble.alias('aa')) #能够同时获取多列,并且能够应用别名
print(avg1.get(Meter1.cur.avg()),avg1.aa) #打印统计后果
#twa 必须配合 where 函数,且必须抉择时间段
twa1 = Meter1.select().where(Meter1.ts > datetime.datetime(2020, 11, 19, 15, 9, 12, 946118),Meter1.ts < datetime.datetime.now()).twa(Meter1.cur,Meter1.curDouble.alias('aa'))
print(twa1.get(Meter1.cur.twa()),avg1.aa) #打印统计后果
#diff
diffs = Meter1.select().diff(Meter1.curInt.alias('aa')) #diff 目前只能够聚合一个属性。for diff1 in diffs:
    print(diff1.aa,diff1.ts) # 工夫点数据同时返回
#top(bottom 函数应用形式雷同)
# top 函数须要提供要统计的属性,行数,以及别名
tops = Meter1.select().top(Meter1.cur,3,alias='aa') 
for top1 in tops:
    print(top1.aa,top1.ts) # 工夫点数据同时返回
tops = Meter1.select().top(Meter1.cur,3) # 能够不指定别名
for top1 in tops:
    #不指定别名,需用应用 get 办法获取属性
    print(top1.get(Meter1.cur.top(3))) 
#percentile (apercentile 函数应用形式雷同) 
#每个属性参数为一个元组(数组),别离定义要统计的属性,P 值(P 值取值范畴 0≤P≤100),可选别名。percentile1 = Meter1.select().percentile((Meter1.cur,1,'aa'),(Meter1.curDouble,2)) 
print(percentile1.aa)
#不指定别名,需用应用 get 办法获取属性
print(percentile1.get(Meter1.curDouble.percentile(2)))
#leastsquares
#每个属性参数为一个元组(数组),别离定义要统计的属性,start_val(自变量初始值),step_val(自变量的步长值),可选别名。leastsquares1 = Meter1.select().leastsquares((Meter1.cur,1,1,'aa'),(Meter1.curDouble,2,2)) 
print(leastsquares1.aa) # 后果:{slop:-0.001595, intercept:0.212111}
#不指定别名,需用应用 get 办法获取属性
print(leastsquares1.get(Meter1.curDouble.leastsquares(2,2)))

留神:以后版本并不反对多表 join 查问,须要多表查问的状况请应用 raw_sql 函数,执行原始 sql 语句。前期版本会补充 join 性能。

5. 超级表定义

超级表定义与一般表的区别在于继承自 SuperModel。而且,在 Meta 类中,能够定义标签。超级表与一般表的查问操作应用形式雷同,以上介绍的所有办法也能够在超级表类中应用,查问操作时标签字段也能够当作一般字段一样操作。

# 超级表模型类继承自 SuperModel 类
class Meters(SuperModel):
    cur = FloatField(db_column='c1')
    curInt = IntegerField(db_column='c2')
    curDouble = DoubleField(db_column='c3')
    desc = BinaryField(db_column='des')
    class Meta:
        database = db
        db_table = 'meters'
        # Meta 类中定义的 Field,为超级表的标签
        location = BinaryField(max_length=30)
        groupid = IntegerField(db_column='gid')
#建设超级表
Meters.create_table(safe=True) 
#删除超级表
Meters.drop_table(safe=True) 
#查看超级表是否存在
Meters.supertable_exists()

6. 从超级表建设子表

对于数据插入操作,就须要从超级表中建设子表。能够应用 Meters 类的 create_son_table 办法创立子表。该办法返回一个子表对应的类对象。该对象能够和以上介绍的一般表类对象(Meter1)一样应用。

# 生成字表模型类的同时,主动在数据库中建表。SonTable_d3 = Meters.create_son_table('d3',location='beijing',groupid=3)
# SonTable_d3 的应用办法和继承自 Modle 类的模型类一样。能够进行插入与查问操作
SonTable_d3.table_exists() 
#子表中插入数据
m = SonTable_d3(cur = 65.8,curInt=10,curDouble=1.1,desc='g1',ts = datetime.datetime.now())
m.save()

下面介绍了 TDengine 的 Python ORM 连贯库 crown 的根本装置和应用办法。除了下面介绍的内容,crown 还提供了很多十分实用的性能,比方动静建表、依据表名获取模型类、分组查问等。有趣味的读者能够返回 GitHub 上查看应用文档。也欢送在 GitHub 上多提宝贵意见与通报 bug。笔者将继续保护该我的项目,致力提供更加丰盛的性能和更加齐备的文档供大家应用。

结语

TDengine 作为优良的国产开源软件,领有优雅的软件设计和杰出的性能体现。非常适合在物联网大数据利用场景下作为数据根底平台应用。将来随着物联网行业的蓬勃发展,TDengine 也必将成为物联网大数据基础架构的一部分而备受全世界相干畛域从业者的宽泛关注。笔者作为一个物联网行业的一名一般开发人员,十分荣幸有机会开发和保护这样一个 TDengine 周边的开源小我的项目。心愿通过这个我的项目能够让更多的开发人员能够更加不便便捷的应用 TDengine,进步工作效率。也心愿可能起到抛砖引玉的作用,激励更多的开发者退出到开源我的项目开发中来,大家一起来为丰盛 TDengine 的周边生态做奉献。


如果你有好的 idea 想和 TDengine 一起实现,欢送成为 Contributor Family 的一员。点击下方链接,退出咱们!

https://www.taosdata.com/cn/contributor/

退出移动版