02.开始使用Pony
安装
文档中的例子是使用Python2.X写成,因此一些使用方法可能会和Python3.X有所区别,
如果在运行时出现了不一致的情况,请根据Python版本进行修正。
许多例子,译者已尝试修改为了Python3.X版本,但不免存在遗漏的情况——gthank
要安装Pony,请在命令提示符中键入以下命令:
pip install pony
Pony可以安装在Python 2.7或Python 3上。
如果你要使用SQLite数据库,你不需要安装其他任何东西。
如果你想使用其他数据库,你需要有数据库的访问权限,并安装了相应的数据库驱动:
- PostgreSQL:psycopg2或psycopg2cffi
- MySQL: MySQL-python或PyMySQL
- Oracle: cx_Oracle
- CockroachDB:psycopg2或psycopg2cffi
要确保Pony已经成功安装,请启动交互式模式下的Python解释器,然后键入以下内容:
>>> from pony.orm import *
这将导入整个(而且不是很大)的类和方法,以便与Pony一起工作。
最终你可以选择导入什么,但我们建议刚开始使用import *
来进行练习
如果你不想把所有的东西都导入到全局命名空间,你可以只导入orm
包。
>>>>from pony import orm
在这种情况下,你不会把Pony的所有函数都加载到全局命名空间中,但这将要求你对任何一个Pony的函数和装饰器使用orm
作为前缀。
熟悉Pony的最好方法是在交互模式下使用它。
让我们创建一个包含实体类Person
的示例数据库,为其添加三个对象,并编写一个查询。
创建数据库对象
Pony中的实体是与数据库相连的,这就是为什么我们需要先创建数据库对象的原因。
在Python解释器中,键入:
>>>> db = Database()
定义实体
现在,让我们创建两个实体:Person和Car,实体Person有两个属性--名字和年龄,而Car有属性make和model,在 Python 解释器中,键入以下代码。
>>>> class Person(db.Entity):
....name = Required(str)
.... age = Required(int)
.... car = Set('Car')
...
>>> class Car(db.Entity):
....make = Required(str)
...model = Required(str)
...owner = Required(Person)
...
>>>
这相当于在数据库中建立了两个表:Person和Car,并定义了相关字段,其中这两个表的关系是一对多,即一个人可以拥有多辆车——gthank
我们所创建的类是从数据库对象的Database.Entity属性中派生出来的,这意味着它们不是普通的类,而是实体。
实体实例存储在数据库中,而数据库是绑定在db变量上的,使用Pony可以同时处理多个数据库,但每个实体都属于一个特定的数据库。
在实体Person里面,我们创建了三个属性--name、age和car。name和age是必须的属性,换句话说,这些属性不能有None的值,name是一个字符串属性,而age是数字属性。
car属性被声明为Set,并且具有Car类型,这意味着,这是一个关系,它可以保留Car实体的实例集合。
"Car "在这里被指定为字符串,因为此时我们还没有声明实体Car。
Car实体有三个强制属性:make和model是字符串,而owner属性是一对多关系的另一面,Pony中的关系总是由两个属性来定义,这两个属性代表关系的两边。
如果我们需要在两个实体之间建立多对多的关系,我们应该在两端声明两个Set属性,Pony会自动创建中间数据库表。
在Python 3中,str类型用于表示一个单码字符串,Python 2中的字符串有两种类型--str和unicode。
从 Pony 0.6 版开始,你可以使用 str 或 unicode 来表示字符串属性,这两种类型都意味着一个 unicode 字符串。
我们推荐使用 str 类型的字符串属性,因为它在 Python 3 中看起来更自然。
如果需要在交互模式下检查一个实体定义,可以使用 show() 函数。将实体类或实体实例传递给这个函数,以便打印出定义。
>>>>> show(Person)
class Person(Entity).id = PrimaryKey(int, auto=True)
id = PrimaryKey(int, auto=True)
name = Required(str)
age = Required(int)
car = Set(Car)
你可能会注意到,这个实体多了一个名为id的属性。为什么会出现这种情况呢?
因为每个实体都必须包含一个主键,它可以区分一个实体和另一个实体。
因为我们没有手动设置主键属性,所以它是自动创建的。
如果主键属性是自动创建的,那么它被命名为id,并且有一个数字格式。如果是手动创建的主键属性,可以指定自己选择的名称和类型。
Pony还支持复合主键。
当主键自动创建时,它总是有自动设置为True的选项,这意味着该属性的值将通过数据库的增量计数器或数据库序列自动分配。
数据库绑定
数据库对象有Database.bind()方法。它用于将声明的实体附加到特定的数据库中。
如果你想在交互模式下玩Pony,可以使用在内存中创建SQLite数据库。
>>>> db.bind(provider='sqlite', filename=':memory:')
目前Pony支持5种数据库类型:'sqlite'、'mysql'、'postgresql'、'cockroach'和'oracle'。
后面的参数是每个数据库所特有的,这些参数与你通过DB-API模块连接到数据库时使用的参数是一样的。
对于SQLite,必须指定数据库文件名或字符串':memory:'作为参数,这取决于数据库的创建位置。
如果数据库是在内存中创建的,那么一旦Python中的交互会话结束,它将被删除。
为了处理存储在文件中的数据库,可以将前面的一行替换成下面的内容。
>>>> db.bind( provider='sqlite', filename='database.sqlite', create_db=True)
在这种情况下,如果数据库文件不存在,就会被创建。
database.sqlite
文件是即将要保存数据的数据库,和你的python文件在同一个目录下,也可以自行指定保存的位置及后缀名,如d:/test.db——gthank
在我们的例子中,我们可以使用在内存中创建的数据库。
如果你使用的是其他的数据库,你需要安装特定的数据库适配器,对于PostgreSQL,Pony使用psycopg2。对于MySQL使用MySQLdb或pymysql适配器,对于Oracle Pony使用cx_Oracle适配器。
下面是你如何连接到数据库的方法:
# SQLite
db.bind( provider='sqlite', filename=':memory:')
# 或者
db.bind(provider='sqlite', filename='database.sqlite', create_db=True)
# PostgreSQL
db.bind(provider='postgres', user='', password='', host='', database='')
# MySQL
db.bind(provider='mysql', host='', user='', passwd='', db='')
# Oracle
db.bind(provider='oracle', user='', password='', dsn='')
# CockroachDB
db.bind(provider='cockroach', user='', password='', host='', database='', )
映射实体到数据表
现在,我们需要创建数据库表,并在其中持久化我们的数据。
为此,我们需要调用数据库对象上的 generate_mapping() 方法。
>>>> db.generate_mapping(create_tables=True)
参数create_tables=True表示,如果表不存在,那么将使用create_tables
命令创建表。
在调用 generate_mapping()
方法之前,必须先定义所有连接到数据库的实体。
定义实体,就相当于建立了各种表,通过映射,自动在数据库中建立表,这个过程只有一次,因为如果表存在的话就自动忽略——gthank
使用调试模式
使用set_sql_debug()函数,可以看到Pony向数据库发送的SQL命令。为了打开调试模式,请键入以下内容。
>>>>> set_sql_debug(True)
如果在调用generate_mapping()
方法之前执行了这个命令,那么在创建表的过程中,你会看到用于生成表的SQL代码。
创建实体实例
现在,让我们创建五个描述三个人和两辆车的对象,并将这些信息保存在数据库中。
>>> p1 = Person(name='John', age=20)
>>> p2 = Person(name='Mary', age=22)
>>> p3 = Person(name='Bob', age=30)
>>> c1 = Car(make='Toyota', model='Prius', owner=p2)
>>> c2 = Car(make='Ford', model='Explorer', owner=p3)
>>> commit()
Pony不会立即保存数据库中的对象,这些对象只有在调用commit()函数后才会被保存。
如果开启了调试模式,那么在commit()期间,你会看到五个insert
命令被发送到数据库中。
db_session
与数据库交互的代码必须放在数据库会话中,当你使用Python的交互式shell工作时,你不需要担心数据库会话,因为它是由Pony自动维护的。
但是当你在应用程序中使用 Pony 时,所有的数据库交互都应该在数据库会话中完成。
为了做到这一点,你需要用db_session()
装饰符来包装与数据库交互的函数。
@db_session
def print_person_name(person_id):
p = Person[person_id]
print(p.name)
# 数据库会话缓存将被自动清除
#数据库连接将被返回到池中
@db_session
def add_car(person_id, make, model):
Car(make=make, model=model, owner=Person[person_id])
# commit()将自动完成
# 数据库会话缓存将被自动清除
#数据库连接将被返回到池中
db_session()装饰器在退出函数时执行以下操作:
- 如果函数引发异常,则执行事务回滚操作。
- 如果数据被更改且没有发生异常,则提交交易。
- 返回连接池的数据库连接
- 清除数据库会话缓存
即使函数只是读取数据而不做任何修改,也应该使用db_session()
来返回连接池的连接。
实体实例只在db_session()中有效,如果需要使用这些对象渲染一个HTML模板,应该在db_session()内进行。
另一个与数据库一起工作的选择是使用db_session()
作为上下文管理器,而不是装饰器@de_session
:
with db_session:
p = Person(name='Kate', age=33)
Car(make='Audi', model='R8', owner=p)
# commit()将自动完成
# 数据库会话缓存将被自动清除
#数据库连接将被返回到池中
进行查询
现在我们已经在数据库中保存了五个对象,我们可以尝试一些查询。
例如,这个查询就是返回一个年龄超过20岁的人的列表。
>>>> select(p for p in Person if p.age > 20)
<pony.orm.core.query at 0x105e74d10>
select()
函数将Python生成器转换为SQL查询,并返回一个Query类的实例。
一旦我们开始迭代查询,这个SQL查询就会被发送到数据库中,获取对象列表的方法之一是对其应用切片操作符[:]:
>>> select(p for p in Person if p.age > 20)[:]
SELECT "p". "id", "p". "name", "p". "age"
FROM "Person" "p"
WHERE "p". "age">20
# [Person[2],Person[3]]
正如结果上面的结果,你可以看到发送至数据库的SQL查询语句和返回的对象列表。
当我们打印出查询结果时,实体实例由实体名称和主键进行表示,例如:Person[2]。
为了对结果列表进行排序,你可以使用Query.order_by()
方法。
如果你只需要结果集的一部分,你可以使用切片操作符,就像在 Python 列表中一样。
例如,如果你想按照名字对所有的人进行排序,然后提取前两个对象,你可以这样做:
>>>>> select(p for p in Person).order_by(Person.name)[:2]
SELECT "p". "id", "p". "name", "p". "age"
FROM "Person" "p"
order by "p". "name "
limit 2
# [Person[3],Person[1]]
有时,在交互式模式下工作时,可能会想看到所有对象属性的值。
为此,可以使用Query.show()
方法。
>>> select(p for p in Person).order_by(Person.name)[:2].show()
SELECT "p"."id", "p"."name", "p"."age"
FROM "Person" "p"
ORDER BY "p"."name"
LIMIT 2
id|name|age
--+----+---
3 |Bob |30
1 |John|20
Query.show()方法不显示 "to-many "属性,因为这将需要对数据库进行额外的查询,可能会很笨重。
这也是为什么在上面看不到相关车的信息的原因,但是如果一个实例有 "to-one "关系,那么就会显示出来。
>>> Car.select().show()
id|make |model |owner
--+------+--------+---------
1 |Toyota|Prius |Person[2]
2 |Ford |Explorer|Person[3]
如果是一对多,或者是多对多,
show()
是不会显示这些结果的,如果是一对一的关系的话,则会进行显示——gthank
如果你不想得到一个对象的列表,但需要对结果序列进行迭代,你可以使用for循环而不使用切片运算符。
>>> persons = select(p for p in Person if 'o' in p.name)
>>> for p in persons:
... print p.name, p.age
...
SELECT "p"."id", "p"."name", "p"."age"
FROM "Person" "p"
WHERE "p"."name" LIKE '%o%'
John 20
Bob 30
在上面的例子中,我们得到所有的Person对象,其name属性包含字母'o',并显示该人的名字和年龄。
查询不一定要返回实体对象,例如,你可以得到一个由对象属性组成的列表。
>>>> select(p.name for p in Person if p.age !=30)[:]
SELECT DISTINCT "p". "name"
FROM "Person" "p"
WHERE "p". "age"<> 30
[u'John',u'Mary']
或者是一个元组列表。
>>> select((p, count(p.cars)) for p in Person)[:]
SELECT "p"."id", COUNT(DISTINCT "car-1"."id")
FROM "Person" "p"
LEFT JOIN "Car" "car-1"
ON "p"."id" = "car-1"."owner"
GROUP BY "p"."id"
[(Person[1], 0), (Person[2], 1), (Person[3], 1)]
在上面的例子中,我们得到一个由Person对象和他们拥有的汽车数量组成的元组列表。
使用Pony,你也可以运行聚合查询,下面是一个返回一个人的最大年龄的查询示例。
>>> print max(p.age for p in Person)
SELECT MAX("p"."age")
FROM "Person" "p"
30
在本手册的以下部分,你将看到如何编写更复杂的查询。
获取对象
要通过主键获取对象,你需要在方括号中指定主键值:
>>>> p1 = Person[1]
>>>> print(p1.name)
John
你可能会注意到,没有向数据库发送任何查询,出现这种情况是因为这个对象已经存在于数据库会话缓存中。
缓存减少了需要向数据库发送的请求数量,
对于通过其他属性来检索对象,可以使用Entity.get()
方法。
>>> mary = Person.get(name='Mary')
SELECT "id", "name", "age"
FROM "Person"
WHERE "name" = ?
[u'Mary']
>>> print(mary.age)
22
在这种情况下,即使对象已经被加载到缓存中,但由于name属性不是唯一的key,所以查询仍然要发送到数据库。
只有当我们根据对象的主键或唯一键来查询对象时,数据库会话缓存才会被使用。
你可以将一个实体实例传递给show()函数,以显示实体类和属性值。
>>> show(mary)
instance of Person
id|name|age
--+----+---
2 |Mary|22
更新一个对象
>>> mary.age +=1
>>> commit()
Pony会跟踪所有改变的属性。
当commit()
函数被执行时,所有在当前事务中更新的对象都将被保存在数据库中。
Pony只保存那些在数据库会话期间被更改的属性。
编写原生SQL查询
如果你需要通过原生SQL查询实体,你可以这样做。
>>> x = 25
>>> Person.select_by_sql('SELECT * FROM Person p WHERE p.age < $x')
SELECT * FROM Person p WHERE p.age < ?
[25]
[Person[1],Person[2]]
如果你想直接和数据库一起工作而不使用实体,可以使用Database.select()
方法。
>>> x = 20
>>> db.select('name FROM Person WHERE age > $x')
SELECT name FROM Person WHERE age > ?
[20]
['Mary', 'Bob']
Pony的一些例子
你可以从Pony发行版包中查看示例,而不是手动创建模型。
>>> from pony.orm.examples.estore import *
这里你可以看到这个例子的数据库设计图https://editor.ponyorm.com/user/pony/eStore
在第一次导入时,将创建一个SQLite数据库,其中包含所有必要的表。
为了将数据填充到数据库中,你需要调用以下函数:
>>> populate_database()
这个函数将创建对象并将其放置在数据库中,对象创建好后,你可以尝试一些查询,例如,这里是如何显示我们有大部分客户的国家。
>>> select((customer.country, count(customer))
... for customer in Customer).order_by(-2).first()
SELECT "customer"."country", COUNT(DISTINCT "customer"."id")
FROM "Customer" "customer"
GROUP BY "customer"."country"
ORDER BY 2 DESC
LIMIT 1
在这个例子中,我们按照国家对对象进行分组,按照第二列(客户数)的反向顺序对其进行排序,然后提取第一行。
你可以在pony.orm.examples.estore模块中的test_queries()
函数中找到更多的查询示例。
网友评论