1. 程式人生 > >Python ORM框架Pony —— Pony入門

Python ORM框架Pony —— Pony入門

安裝

要安裝Pony我們首先要安裝好pip,假設我們已經安裝完成pip,請在dos命令視窗下鍵入以下命令:

pip install pony

之後pip會自動將我們的Pony安裝到Python 2.7或Python 3上

要確保已成功安裝Pony,請以互動方式啟動Python編譯器並鍵入:

>>> from pony.orm import *

這將匯入使用Pony所需的整個(而不是非常大)的類和函式集。最終您可以選擇要匯入的內容,但我們建議您首先使用import *。

如果您不想將所有內容匯入全域性名稱空間,則可以只匯入orm包:

>>> from pony import orm

在這種情況下,您不會將所有Pony的函式載入到全域性名稱空間中,但它會要求您使用orm作為任何Pony的函式和裝飾器的字首。

 

建立資料庫物件

Pony中的實體連線到資料庫。這就是我們需要首先建立資料庫物件的原因。在Python直譯器中,鍵入:

>>> db = Database()

定義實體

現在,讓我們建立兩個實體 -  Person和Car。實體Person有兩個屬性 - 名稱和年齡,Car有屬性make和model。在Python直譯器中,鍵入以下程式碼:

>>> class Person(db.Entity):
...     name = Required(str)
...     age = Required(int)
...     cars = Set('Car')
...
>>> class Car(db.Entity):
...     make = Required(str)
...     model = Required(str)
...     owner = Required(Person)
...
>>>

凡繼承Database.Entity的類都是實體類,這意味著這兩個不是普通的類,而是實體類。實體類例項儲存在資料庫中,該資料庫繫結到db變數。

在實體Person中,我們建立了三個屬性 - 名稱,年齡和汽車。名稱和年齡是必需屬性。換句話說,他們這些屬性不能具有None值。名稱是字串屬性,而age是數字。

cars屬性宣告為Set並具有Car型別。也就是聲明瞭Car型別的Set集合屬性,這說明了一種關係可以是多對一,或多對多,因為在這個集合中可以存入多個Car型別的物件,並通過這個集合與其他物件進行關聯。

這裡得注意一點,在宣告Person類中的cars時首先其型別為字串型別,因為在宣告Person時Car類還未被定義

Car實體有三個必需屬性:make和model是字串,owner屬性是一對多關係的另一邊。Pony中的關係總是由兩個屬性定義,這兩個屬性代表關係的兩個方面。如果我們需要在兩個實體之間建立多對多關係,我們應該在兩端宣告兩個Set屬性。Pony自動建立中間資料庫表。

str型別用於表示Python 3中的unicode字串.Python 2有兩種型別的字串 -  str和unicode。從Pony Release 0.6開始,您可以使用str或unicode作為字串屬性,它們都表示unicode字串。我們建議對字串屬性使用str型別,因為它在Python 3中看起來更自然。

如果需要在互動模式下檢查實體定義,可以使用show()函式。將實體類或實體例項傳遞給此函式以打印出定義:

>>> show(Person)
class Person(Entity):
    id = PrimaryKey(int, auto=True)
    name = Required(str)
    age = Required(int)
    cars = Set(Car)

您可能會注意到該實體獲得了一個名為id的額外屬性。為什麼會這樣?

每個實體必須包含一個主鍵,允許區分一個實體與另一個實體。由於我們尚未手動設定主鍵屬性,因此它是自動建立的。如果主鍵是自動建立的,則將其命名為id並且是intl型別和自增,當然你也可以手動將auto設定為false.如果手動建立主鍵屬性,則可以指定所選的名稱和型別。Pony還支援複合主鍵。

如果您正在使用其他資料庫,則需要安裝特定的資料庫介面卡。對於PostgreSQL,Pony使用psycopg2。對於MySQL MySQLdb或pymysql介面卡。對於Oracle Pony,使用cx_Oracle介面卡。

# SQLite
db.bind(provider='sqlite', filename=':memory:')
# or
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='')

 

將實體對映到資料庫表

現在我們需要建立資料庫表來儲存資料。為此,我們需要在Database物件上呼叫generate_mapping()方法:

>>> db.generate_mapping(create_tables=True)

引數create_tables = True表示如果實體指向的表尚不存在,則使用CREATE TABLE命令建立它們。必須在呼叫generate_mapping()方法之前定義連線到資料庫的所有實體。

使用除錯模式

使用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
    # database session cache will be cleared automatically
    # database connection will be returned to the pool

@db_session
def add_car(person_id, make, model):
    Car(make=make, model=model, owner=Person[person_id])
    # commit() will be done automatically
    # database session cache will be cleared automatically
    # database connection will be returned to the pool

 db_session()裝飾器對退出函式執行以下操作:

  • 如果函式引發異常,則執行事務回滾
  • 如果資料已更改且未發生異常,則提交事務
  • 返回連線池的資料庫連線
  • 清除資料庫會話快取

即使函式只是讀取資料並且沒有進行任何更改,它也應該使用db_session()來返回到連線池的連線。

實體例項僅在db_session()中有效。如果需要使用這些物件呈現HTML模板,則應在db_session()中執行此操作。

使用資料庫的另一個選擇是使用db_session()作為context manager 而不是裝飾器:

with db_session:
    p = Person(name='Kate', age=33)
    Car(make='Audi', model='R8', owner=p)
    # commit()將自動完成
    # 資料庫會話快取將自動清除
    # 資料庫連線將返回池中

編寫查詢

現在我們已經在其中儲存了五個物件的資料庫,我們可以嘗試一些查詢。例如,這是一個返回超過二十歲的人員列表的查詢:

>>> 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]]

Sometimes, when working in the interactive mode, you might want to see the values of all object attributes. For this purpose, you can use the Query.show() method:

>>> 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”屬性,因為它需要對資料庫進行額外查詢,並且可能很龐大。這就是為什麼你看不到上面相關汽車的資訊。但是如果例項具有“一對一”關係,那麼它將顯示:

>>> Car.select().show()
id|make  |model   |owner
--+------+--------+---------
1 |Toyota|Prius   |Person[2]
2 |Ford  |Explorer|Person[3]

如果您不想獲取物件列表,但需要迭代生成的序列,則可以在不使用切片運算子的情況下使用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

在上面的示例中,我們獲取名稱屬性包含字母“o”的所有Person物件,並顯示該人的姓名和年齡。

查詢不一定必須返回實體物件。例如,您可以獲取一個列表,其中包含object屬性:

>>> 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)]

獲取物件

要通過主鍵獲取物件,您需要在方括號中指定主鍵值:

>>> 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屬性不是唯一鍵。僅當我們通過主鍵或唯一鍵查詢物件時,才會使用資料庫會話快取記憶體。

您可以將實體例項傳遞給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]

[u'Mary', u'Bob']