Введение в ZODB

Краткое введение в работу с базой данных ZODB в статье «Introduction to the ZODB». Автор: Michel Pelletier.

В этой статье, мы опишем основы объектной базы данных Zope(ZODB). Эта небольшая статья почти все что нужно, чтобы начать использовать эту мощную объектную базу данных в Python. ZODB – это база данных для объектов Python, входящая в Zope. Если вы когда либо работали с реляционными базами данных, такими как PostgreSQL, MySQL или Oracle, то вы должны быть знакомы с ролью базы данных. Это краткосрочное или долгосрочное хранилище данных для приложений.

Для многих задач реляционных базы данных хорошее решение, но иногда реляционных базы данных трудно совместимы с объектной моделью приложения. Если имеется множество различных взаимосвязанных объектов со сложным взаимодействием, а также изменяемой схемой, то использование ZODB будет лучшим решением.

Отличительной чертой ZODB является прозрачность. Вам не нужно писать код для явной записи или чтения объектов из базы. Вы просто добавляете ваш сохраняемы объект в контейнер, который работает как словарь Python. Все что содержится в этом словаре сохранится в базе данных. Этот словарь называется корнем базы данных. Это как «волшебная сумка», любой объект Python, который вы добавляете туда, становится сохраняемым.

На самом деле существует некоторые ограничения на то, что вы можете сохранять в ZODB. Вы можете сохранять любой объект, который может быть преобразован в стандартный, кросплатформенный формат с помощью модуля pickle.

Простой пример

Первое что вы должны сделать, создать корневой каталог. Это происходит во время первого соединения с хранилищем. ZODB поддерживает множество подключаемых хранилищ, но целью данной статьи является показать как использовать FileStorage. с сохранением объектов в файл. Другие хранилища поддерживают сохранение данных в реляционных базах данных, в базах данных Berkely и хранение на удаленных серверах баз данных.

Для начала необходимо установить ZODB. ZODB поставляется с Zope, поэтому самый простой способ установить получить ZODB – установить Zope и использовать ZODB включенный в него. Также можно скачать и установить ZODB отдельно, для этого следуйте инструкциям в статье.

После установки ZODB, вы можете начать экспериментировать с ним прямо из командной строки интерпретатора Python. Например, попробуйте следующий код:

>>> from ZODB import FileStorage, DB
>>> storage = FileStorage.FileStorage('mydatabase.fs')
>>> db = DB(storage)
>>> connection = db.open()
>>> root = connection.root()

Здесь мы создаем и используем файл «mydatabase.fs» для хранения данных. Далее, база данных должна быть открыта с помощью метода open(). Он вернет объект подключения к базе данных. Объект подключения позволяет получить доступ к корневому контейнеру с помощью вызова метода root().

Корневой контейнер это словарь, который содержит все сохраняемые объекты. Например, вы можете сохранить простой список строк в нем:

>>> root['employees'] = ['Mary', 'Jo', 'Bob']

Теперь вы изменили базу данных, добавив новый объект, но изменения пока не сохранились. Для сохранения изменений вы должны завершить транзакцию:

>>> import transaction
>>> transaction.commit()

Транзакция это группа изменений применяемых как одна атомарная операция. Это очень мощное средство, я покажу его вам в одной из следующих моих статей. Пока вы можете думать что закрытие транзакции это что то типа контрольной точки, где вы сохраняете все изменения, который вы сделали до этого.

Давайте теперь проверим, действительно ли данные были сохранены. Сначала закроем наше соединение:

>>> connection.close()

Затем выйдете из интерпретатора Python. Теперь запустите заново и подключитесь к базе данных, который вы только что создали:

>>> from ZODB import FileStorage, DB
>>> storage = FileStorage.FileStorage('mydatabase.fs')
>>> db = DB(storage)
>>> connection = db.open()
>>> root = connection.root()

И посмотрим на наш объект в корневом контейнере:

>>> root.items()
[('employees', ['Mary', 'Jo', 'Bob'])]

Там наш список. Если бы вы использовали реляционную базу данных, вы бы должны были разрабатывать SQL запрос даже для сохранения простого списка Python как в примере выше. Вам также необходимо было бы реализовать код для преобразования SQL запроса обратно в список, когда вы захотите использовать его опять. Вы не нужно делать ничего подобного когда вы используете ZODB. Использование ZODB практически прозрачно и программы основанные на ZODB часто выглядят подозрительно простыми.

Имейте в виду, что корневой словарь это лишь верхушка айсберга. Хранимые объекты могут иметь атрибуты, которые сами по себе являются хранимыми объектами. Другими словами, даже если у вас только один или два хранимых объекта верхнего уровня как значения в корневом контейнере, Вы все же можете иметь тысячи подобъектов. Вот по сути как Zope делает это. В Zope только один объект верхнего уровня – корень приложения, в котором сохраняются все остальные объекты.

Обнаружение изменений

Одним из того что делает ZODB таким простым для использования является то, что не требуется самостоятельно отслеживать изменения. Все что должны сделать это изменить хранимые объекты и закрыть транзакцию. Все что изменилось будет сохранено в базе данных.

Но есть исключение, когда речь идет о списках и словарях Python. Если вы измените список или словарь, который уже сохранен, то изменения не вступят в силу. Рассмотрим пример:

>>> root['employees'].append('Bill')
>>> transaction.commit()

Это не будет работать, как вы ожидали. Причина тому, что ZODB не может определить что список изменился. Существует множество простых способов обойти эту проблему. Самый простой – повторно перезаписать список в контейнер:

>>> employees = root['employees']
>>> employees.append('Bill')
>>> root['employees'] = employees
>>> transaction.commit()

Ниже в этой статье, мы покажем вам другой способ оповещения ZODB о том что ваш объект изменился. Также вы увидите, как использовать приспособленные для ZODB классы списка и словаря, которые поставляются с ZODB.

Хранимые объекты

Простейший способ создать объект, который будет оповещать ZODB об изменениях - создать класс хранимого объекта наследовавшись от Persistent. Класс Persistent позволяет сохранять ваши собственные объекты в базе данных. Например, следующий класс, представляющий сотрудника:

import ZODB
from Persistence import Persistent

class Employee(Persistent):

    def setName(self, name):
        self.name = name

Из-за некоторой магии, которую делает ZODB, Вы должны сначала подключить модуль ZODB, перед тем как подключить модуль Persistent. Модуль Persistent в действительности создается во время подключения модуля ZODB.

Теперь Вы можете добавить объект «сотрудник» в базу данных:

>>> employees=[]
>>> for name in ['Mary', 'Joe', 'Bob']:
...     employee = Employee()
...     employee.setName(name)
...     employees.append(employee)
>>> root['employees']=employees
>>> transaction.commit()

Теперь Вы можете изменить ваши объекты и изменения будут сохранены в базу данных. Например, Вы можете изменить имя Боба на Роберт:

>>> bob=root['employees'][2]
>>> bob.setName('Robert')
>>> transaction.commit()

Вы даже можете изменить атрибут хранимого объекта без вызова методов:

>>> bob=root['employees'][2]
>>> bob._coffee_prefs=('Cream', 'Sugar')
>>> transaction.commit()

Не имеет значения меняете ли Вы атрибут напрямую или же он изменяется через метод. Или по другому можно сказать, все нормальные правила языка Python все равно работают как вы ожидали.

Ранее мы убедились, что ZODB не может определить изменился ли список или словарь или нет. Тоже самое будет если вы используете хранимые объекты, наследуемые от Persistent, если такой объект имеет атрибут список или словарь. Например, следующий класс:

class Employee(Persistent):

    def __init__(self):
        self.tasks = []

    def setName(self, name):
        self.name = name

    def addTask(self, task):
        self.task.append(task)

Когда Вы вызовете метод addTask, ZODB не узнает, что атрибут tasks изменился. Как Вы видели раньше, Вы можете перезаписать атрибут. Однако, когда вы использует хранимые объекты, наследуемые от Persistent, у Вас есть другой вариант. Вы можете подать сигнал ZODB, что объект был изменен с помощью специального атрибута _p_changed:

class Employee(Persistent):
    ...

    def addTask(self, task):
        self.task.append(task)
        self._p_changed = 1

Для того чтобы показать ZODB, что объект был изменен, установите атрибут _p_changed в 1. Даже если вы измените атрибут несколько раз, достаточно установить этот атрибут только раз. Атрибут _p_changed заставляет нас следовать еще одному правилу при использовании хранимых объектов наследованных от Persistent: нельзя использовать атрибуты, имя которых начитается с _p_, они зарезервированы для использования ZODB.

Полный пример программы:

from ZODB import DB
from ZODB.FileStorage import FileStorage
from ZODB.PersistentMapping import PersistentMapping
from Persistence import Persistent
import transaction

class Employee(Persistent):
    """An employee"""

    def __init__(self, name, manager=None):
        self.name=name
        self.manager=manager

# setup the database
storage=FileStorage("employees.fs")
db=DB(storage)
connection=db.open()
root=connection.root()

# get the employees mapping, creating an empty mapping if
# necessary
if not root.has_key("employees"):
    root["employees"] = {}
employees=root["employees"]


def listEmployees():
    if len(employees.values())==0:
        print "There are no employees."
        print
        return
    for employee in employees.values():
        print "Name: %s" % employee.name
        if employee.manager is not None:
            print "Manager's name: %s" % employee.manager.name
        print

def addEmployee(name, manager_name=None):
    if employees.has_key(name):
        print "There is already an employee with this name."
        return
    if manager_name:
        try:
            manager=employees[manager_name]
        except KeyError:
            print
            print "No such manager"
            print
            return
        employees[name]=Employee(name, manager)
    else:
        employees[name]=Employee(name)

    root['employees'] = employees  # reassign to change
    transaction.commit()
    print "Employee %s added." % name
    print


if __name__=="__main__":
    while 1:
        choice=raw_input("Press 'L' to list employees, 'A' to add"
                         "an employee, or 'Q' to quit:")
        choice=choice.lower()
        if choice=="l":
            listEmployees()
        elif choice=="a":
            name=raw_input("Employee name:")
            manager_name=raw_input("Manager name:")
            addEmployee(name, manager_name)
        elif choice=="q":
            break

    # close database
    connection.close()

Это программа демонстрирует несколько интересных вешей. Первое – программа показывает, как хранимые объекты ссылаются друг на друга. Атрибут slef.manager объекта Employee может ссылаться на другой объект Employee. В отличии от реляционной базы данных, здесь нет необходимости использовать дополнительные данные, такие как идентификаторы объектов при ссылки с одного объекта на другой. Вы можете просто использовать обычные ссылки Python. В самом деле, Вы даже можете использовать циклические ссылки.

Также в этой программе показано, как искать хранимый объект и, если его нет - создавать. Это позволяет просто запустить программу без каких либо дополнительных установочных скриптов для создания базы данных. Если базы не существует, программа создаст и инициализирует ее.

Оригинальный текст статьи на zope.org

Перевод КОМТЕТ komtet.ru

 

 

Вам также может помочь