pony.orm 操作数据库, 很 Pythonic
不要怀疑,写点代码这种事情,测试工程师其实也常干。
想用 Python 优雅的操作数据库?
Pony ORM 了解一下?
本文作者:于静,高级软件测试工程师。
曾负责某全球智能药房设备系统,及某容器平台解决方案微服务治理平台的测试工作。
什么是 Pony ORM
Pony 是一个高级的对象关系映射器。
ORM 允许开发人员以对象的形式来处理数据库的内容。
一个关系型数据库包含的是存储在表中的行。
然而,当用高级的面向对象语言编写程序时,当从数据库中检索的数据可以以对象的形式访问时,就会方便很多。
Pony ORM 是 Python语言的一个库,可以方便地处理以行形式存储在关系型数据库中的对象。
优点
- 编写查询的语法特别方便
- 自动查询优化
- 自动创建数据表
安装
pip install pony
导入
from pony.orm import *
这将导入使用 Pony 所需的整个(但不是非常大)的类和函数集。最终您可以选择要导入的内容,但我们建议您首先使用 import *
,或者仅导入所需:
from pony.orm import (Json, PrimaryKey, Required, db_session, select, desc, Set, get, Optional, Database)
创建数据库对象
方法一、创建空的数据连接然后绑定
db = Database()
空的连接往往是更方便做后期使用 DB()
方法。这样你就可以使用不同的数据库进行测试和制作。
然后进行绑定,支持多种数据库 db.bind()
-
postgres
db.bind('postgres', user='', password='', host='', database='')
-
mysql
db.bind('mysql', host='', user='', passwd='', db='')
-
oracle
db.bind('oracle', 'user/password@dsn')
-
sqlite
db.bind('sqlite', 'filename', create_db=True) # create_db:如果数据库不存在创建数据库文件
方法二、通过参数连接
db = Database("mysql", host="localhost", user="root", passwd="123123", db="t2")
代码示例:
from pony.orm import Database
import os
import logging
from pony.orm import (Json, PrimaryKey, Required, db_session, select, desc, Set, get, Optional)
'''
Pony规定与数据库进行交互的代码必须在数据库会话中工作
可以使用@db_session装修或db_session上下文管理数据库的工作。
当会话结束时,它会做以下操作
提交事务或则回滚事务。
返回连接池的数据库连接。
清除缓存。
'''
# logging.basicConfig(level=logging.DEBUG,format='%(asctime)s::%(message)s::%(levelname)s::%(args)s')
db = Database(provider = "mysql",
host = os.getenv('HOST') if os.getenv('MY_HOST') else '10.1.1.1',
port = int(os.getenv('Port')) or 3307,
user = os.getenv('USER') if os.getenv('MY_USER') else 'root',
password = os.getenv('PASSWORD') or '1256',
database = os.getenv('DB') or 'message_center'
)
定义实体
凡继承 Database.Entity
的类都是实体类,实体类实例存储在数据库中,该数据库绑定到 db 变量。
例:class Content(db.Entity)
属性
class Info(db.Entity):
_table_ = 'info'
id = PrimaryKey(int, auto = True) #autoBoolean 是否自增
key = Required(str, 50)
name = Required(str, 30, default = "未知") #如果是必须的话必须加默认值
url = Optional(str, 100)
comment = Optional(str, 50, nullable = True)
create_time = Required(datetime.datetime, default = datetime.datetime.now(), nullable = True)
update_time = Optional(datetime.datetime)
type = Required(str, 50)
isdelete = Optional(bool)
属性类型分:
- Required(必选)
- Optional(可选)
- PrimaryKey(主键)
- Set
Set
定义了一对一,一对多,多对多等数据结构
classStudent(db.Entity):
name = Required(str)
courses = Set("Course")
classCourse(db.Entity):
name = Required(str)
semester = Required(int)
students = Set(Student)
PrimaryKey(name, semester)
属性数据类型
格式为:属性名 = 属性类型(数据类型)
str
unicode
int
float
Decimal
datetime
date
time
timedelta
bool
buffer - used for binary data in Python 2 and 3
bytes - used for binary data in Python 3
LongStr - used for large strings
LongUnicode - used for large strings
UUID
属性参数
- 字符串长度
默认为 255
name = Required(str,40)# VARCHAR(40)
- 整数的大小
默认 32bit INTEGER
attr1 = Required(int,size=8) #8bit - TINYINTinMySQL
attr5 = Required(int,size=64) #64bit - BIGINTinMySQL
- 无符号
attr1 = Required(int, size=8,unsigned=True)# TINYINT UNSIGNEDinMySQL
- 小数和精度
price = Required(Decimal,10,2)# DECIMAL(10, 2)
- 时间
```python
dt = Required(datetime, 6)
- 其他的参数
unique
Boolean 是否唯一
auto
Boolean 是否自增
default
- 默认值
sql_default
created_at = Required(datetime, sql_default=’CURRENT_TIMESTAMP’)
- index
index=True 创建的默认索引名称
index='index_name' 指定索引名称
- reverse
指定应用于关系的另一端的属性。
- reverse_column
用于对称关系,以便指定中间表的数据库列的名称。
- reverse_columns
如果实体具有复合主键,则用于对称关系。允许为中间表指定数据库列的名称。
- table
多对多中间表的表名
- nullable
允许该列为数据库中的空
将实体映射到数据库表
创建表,实体类的映射关系,这种映射关系非常重要, pony在启动项目时会检查整个项目的所有实体类的映射关系是否正确。
为此,我们需要在 Database
对象上调用 generate_mapping()
方法:
db.generate_mapping(create_tables=True)
import connexion
from database.mysql_conn import db
from database.db_info import Info
from database.db_content import Content
if __name__ == '__main__':
db.generate_mapping(create_tables=True) # 创建表,实体类的映射关系,这种映射关系非常重要,pony在启动项目时会检查整个项目的所有实体类的映射关系是否正确。
参数 create_tables = True
表示如果实体指向的表尚不存在,则使用 CREATE TABLE
命令创建它们。
必须在调用 generate_mapping()
方法之前定义连接到数据库的所有实体。
注:仅调用一次,且调用的的 py 文件必须 import 全部实体的类,否则报错
pony.orm.core.ERDiagramError: Cannot define entity 'Content': database mapping has already been generated
实例
每个实例对应于数据库表中的一行
Content(msgtype=msgtype, name=name, content=content, project=project, sprint=sprint, create_time=datetime.datetime.now(), isdelete=0)
会话
Pony 规定与数据库进行交互的代码必须在数据库会话中工作可以使用 @db_session
装饰,当会话结束时,它会做以下操作:
- 如果函数引发异常,则执行事务回滚
- 如果数据已更改且未发生异常,则提交事务
- 返回连接池的数据库连接
- 清除数据库会话缓存
@classmethod
@db_session
def db_create_content(cls, msgtype, name, content):
obj = get(n for n in Content if n.name == name)
if obj:
raise IsExist(title='该名字的模版已经存在', detail=f'name为【{name}】的模版已经存在')
else:
Content(msgtype=msgtype, name=name, content=str(content), create_time=datetime.datetime.now(), isdelete=0)
查询
要对结果列表进行排序,可以使用 Query.order_by()
方法。
如果只需要结果集的一部分,则可以使用切片运算符,与在Python列表上执行的操作完全相同。
例如,如果要按名称对所有人进行排序并提取前两个对象,则可以这样做:
select(pforpinPerson).order_by(Person.name)
select(n for n in Content if n.isdelete==0)
获取对象
要通过主键获取对象,您需要在方括号中指定主键值:
p1 = Person[1]
print p1.name
@classmethod
@db_session
def db_get_content(cls):
objs = select(n for n in Content if n.isdelete==0)
data = []
for obj in objs:
dict = {
"name": obj.name,
"content": obj.content,
"msgtype": obj.msgtype,
# "project": obj.project,
# "sprint": obj.sprint
}
data.append(dict)
return data
您可能会注意到没有向数据库发送任何查询。
发生这种情况是因为此对象已存在于数据库会话高速缓存中。
缓存减少了需要发送到数据库的请求数。
要通过其他属性检索对象,可以使用 Entity.get()
方法:
mary = Person.get(name='Mary')
在这种情况下,即使对象已经加载到缓存中,仍然必须将查询发送到数据库,因为 name
属性不是唯一键。
仅当我们通过主键或唯一键查找对象时,才会使用数据库会话高速缓存。