不要怀疑,写点代码这种事情,测试工程师其实也常干。
想用 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 属性不是唯一键。

仅当我们通过主键或唯一键查找对象时,才会使用数据库会话高速缓存。