本文介绍了一个用于操作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_databasedb.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,不存在返回:FalseMeter1.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)#导入pandasraw_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()
以下是应用的例子。
#countcount = Meter1.select().count() #统计行数print(count) # 后果:100count = 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) #打印统计后果#diffdiffs = 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/