Введение в ZODB
В этой статье, мы опишем основы объектной базы данных 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