sqla-logo6.gif

Введение в SQLAlchemy

SQLAlchemy — это программное обеспечение с открытым исходным кодом для работы с базами данных при помощи языка SQL. Оно реализует технологию программирования ORM (Object-Relational Mapping), которая связывает базы данных с концепциями объектно-ориентированных языков программирования. SQLAlchemy позволяет описывать структуры баз данных и способы взаимодействия с ними прямо на языке Python. SQLAlchemy Реализована в виде пакета для Python под лицензией MIT, а значит возможно использование ее проприетарном ПО. SQLAlchemy была выпущена в феврале 2006 и быстро стала одним из самых распространенных инструментов ORM среди разработчиков на Python. SQLAlchemy обладает несколькими областями применения, которые могут использоваться как вместе, так и по отдельности. Его основные компоненты приведены ниже.

Установка SQLAlchemy

Вы можете установить SQLAlchemy с нуля, используя pip. Если он установлен, то вы можете просто набрать в консоли:

# pip install SQLAlchemy

Эта команда скачает последнюю версию SQLAlchemy из Python Cheese Shop и установит ее на вашу машину.

Также можно просто скачать архив с SQLAlchemy с официального сайта и выполнить установочный скрипт setup.py

# python setup.py install

Для обновления уже установленной SQLAlchemy наберите в консоли:

# pip install --upgrade SQLAlchemy

Для проверки правильности установки следует проверить версию библиотеки:

   1 import sqlalchemy # начните работу с этой библиотеки
   2 print "Версия SQLAlchemy:", sqlalchemy.__version__# посмотреть версию SQLALchemy

Объектно-реляционная модель SQLAlchemy

Соединение с базой данных

В этом уроке мы будем использовать только БД SQLite, хранящуюся в памяти. Чтобы соединиться с СУБД, мы используем функцию create_engine():

   1 >>> from sqlalchemy import create_engine
   2 >>> engine = create_engine('sqlite:///:memory:', echo=True)

Флаг echo включает ведение лога через стандартный модуль logging Питона. Когда он включен, мы увидим все созданные нами SQL-запросы. Если вы хотите просто пробежать этот урок и убрать отладочный вывод, то просто уберите его, поставив

echo=False

По умолчанию соединение с БД через 8 часов простоя обрывается. Чтобы это не случилось нужно добавить опцию

pool_recycle = 7200

и тогда каждые два часа соединение будет переустанавливаться.

Соединение с базой данных

Далее мы пожелаем рассказать SQLAlchemy о наших таблицах. Мы начнем с одиночной таблицы users, в которой будем хранить записи о наших конечных пользователях, которые посещают некий сайт N. Мы определим нашу таблицу внутри каталога MetaData, используя конструктор Table(), который похож на SQLный CREATE TABLE:

   1 >>> from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
   2 >>> metadata = MetaData()
   3 >>> users_table = Table('users', metadata,
   4 ...     Column('id', Integer, primary_key=True),
   5 ...     Column('name', String),
   6 ...     Column('fullname', String),
   7 ...     Column('password', String)
   8 ... )

Все о том, как определять объекты Table и о том, как загружать их определение из существующей БД (рефлексия) рассмотрено в главе Метаданные БД. Далее же мы пошлем базе CREATE TABLE, параметры которого будут взяты из метаданных нашей таблицы. Мы вызовем метод create_all() и передадим ему наш объект engine, который и указывает на базу. Там сначала будет проверено присутствие такой таблицы перед ее созданием, так что можно выполнять это много раз — ничего страшного не случится.

   1 >>> metadata.create_all(engine)

Те, кто знаком с синтаксисом SQL и в частности CREATE TABLE, могут заметить, что колонки VARCHAR создаются без указания их длины. В SQLite это вполне допустимый тип данных, но во многих других СУБД так делать нельзя. Для того, чтобы выполнить этот урок в PostgreSQL или MySQL, длина должна быть передана строкам, как здесь:

   1 >>> Column('name', String(50))

Поле «длина» в строках String, как и простая разрядность/точность в Integer, Numeric и т. п. не используются более нигде, кроме как при создании таблиц.

Определение класса Python для отображения в таблицу

В то время, как класс Table хранит информацию о нашей БД, он ничего не говорит о логике объектов, что используются нашим приложением. SQLAlchemy считает это отдельным вопросом. Для соответствия нашей таблице users создадим элементарный класс User. Нужно только унаследоваться от базового питоньего класса Object (то есть у нас совершенно новый класс).

   1 >>> class User(object):
   2 ...     def __init__(self, name, fullname, password):
   3 ...         self.name = name
   4 ...         self.fullname = fullname
   5 ...         self.password = password
   6 ...
   7 ...     def __repr__(self):
   8 ...        return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password)

__init__ — это конструктор, __repr__ же вызывается при операторе print. Они определены здесь для удобства. Они не обязательны и могут иметь любую форму. SQLAlchemy не вызывает __init__ напрямую

Настройка отображения

Теперь мы хотим слить в едином порыве нашу таблицу user_table и класс User. Здесь нам пригодится пакет SQLAlchemy ORM. Мы применим функцию mapper, чтобы создать отображение между user_table и User.

   1 >>> from sqlalchemy.orm import mapper
   2 >>> mapper(User, users_table)
   3 <Mapper at 0x...; User>

Функция mapper() создаст новый Mapper-объект и сохранит его для дальнейшего применения, ассоциирующегося с нашим классом. Теперь создадим и проверим объект типа User:

   1 from sqlalchemy.orm import mapper  #достать "Отобразитель" из пакета с объектно-реляционной моделью
   2 print mapper(User, users_table)  # и отобразить. Передает класс User и нашу таблицу
   3 user = User("Вася", "Василий", "qweasdzxc")
   4 print user  #Напечатает <User('Вася', 'Василий', 'qweasdzxc'>
   5 print user.id  #Напечатает None

Атрибут id, который не определен в __init__, все равно существует из-за того, что колонка id существует в объекте таблицы users_table. Стандартно mapper() создает атрибуты класса для всех колонок, что есть в Table. Эти атрибуты существуют как питоньи дескрипторы и определяют его функциональность. Она может быть очень богатой и включать себе возможность отслеживать изменения и АВТОМАТИЧЕСКИ подгружать данные в базу, когда это необходимо. Так как мы не сказали SQLAlchemy сохранить Василия в базу, его id выставлено на None. Когда позже мы сохраним его, в этом атрибуте будет храниться некое автоматически сформированное значение.

Декларативное создание таблицы, класса и отображения за один раз.

Предыдущее приближение к конфигурированию, включающее таблицу Table, пользовательский класс и вызов mapper() иллюстрируют классический пример использования SQLAlchemy, в которой очень ценится разделение задач. Большое число приложений, однако, не требуют такого разделения, и для них SQLAlchemy предоставляет альтернативный, более лаконичный стиль: декларативный.

   1 >>>Base = declarative_base()
   2 >>>class User(Base):
   3 ...    __tablename__ = 'users'
   4 ...    id = Column(Integer, primary_key=True)
   5 ...    name = Column(String)
   6 ...    fullname = Column(String)
   7 ...    password = Column(String)
   8 
   9 ...    def __init__(self, name, fullname, password):
  10 ...        self.name = name
  11 ...        self.fullname = fullname
  12 ...        self.password = password
  13 ...    def __repr__(self):
  14 ...        return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password)

Выше - функция declarative_base(), что определяет новый класс, который мы назвали Base, от которого будет унаследованы все наши ORM-классы. Обратите внимание: мы определили объекты Column безо всякой строки имени, так как она будет выведена из имени своего атрибута. Низлежащий объект Table, что создан нашей declarative_base() версией User, доступен через атрибут __table__

   1 >>> users_table = User.__table__

Имющиеся метаданные MetaData также доступны:

   1 >>> metadata = Base.metadata

Еще один «декларативный» метод для SQLAlchemy доступен в сторонней библиотеке Elixir. Это полнофункциональный продукт, который включает много встроенных конфигураций высокоуровневого отображения. Как и деклaративы, как только классы и отображения определены, использование ORM будет таким же, как и в классическом SQLAlchemy.

Создание сессии

Теперь мы готовы начать наше общение с базой данных. «Ручка» базы в нашем случае – это сессия Session. Когда сначала мы запускаем приложение, на том же уровне нашего create_engine() мы определяем класс Session, что служит фабрикой объектов сессий (Session).

   1 >>> from sqlalchemy.orm import sessionmaker
   2 >>> Session = sessionmaker(bind=engine)

В случае же, если наше приложение не имеет Engine-объекта базы данных, можно просто сделать так:

   1 >>> Session = sessionmaker()

А потом, когда вы создадите подключение к базе с помощью create_engine(), соедините его с сессией, используя configure():

   1 >>> Session.configure(bind=engine)  # Как только у вас появится engine

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

   1 >>> session = Session()

Сессия здесь ассоциирована с SQLite, но у нее еще нет открытых соединений с этой базой. При первом использовании она получает соединение из набора соединений, который поддерживается engine и удерживает его до тех пор, пока мы не применим все изменения и/или не закроем объект сессии.

Добавление новых объектов

Чтобы сохранить наш User-объект, нужно добавить его к нашей сессии, вызвав add():

   1 >>> vasiaUser = User("vasia", "Vasiliy Pypkin", "vasia2000")
   2 >>> session.add(vasiaUser)

Этот объект будет находиться в ожидании сохранения, никакого SQL-запроса пока послано не будет. Сессия пошлет SQL-запрос, чтобы сохранить Васю, как только это понадобитья, используя процесс сброса на диск(flush). Если мы запросим Васю из базы, то сначала вся ожидающая информация будет сброшена в базу, а запрос последует потом. Для примера, ниже мы создадим новый объект запроса (Query), который загружает User-объекты. Мы «отфильтровываем» по атрибуту «имя=Вася» и говорим, что нам нужен только первый результат из всего списка строк. Возвращается тот User, который равен добавленному:

   1 >>> ourUser = session.query(User).filter_by(name="vasia").first()
   2 <User('vasia','Vasiliy Pypkin', 'vasia2000')>

На самом деле сессия определила, что та запись из таблицы, что она вернула, та же самая, что а запись, что она уже представляла в своей внутренней хеш-таблицу объектов. Так что мы просто получили точно тот же самые объект, что только что добавили. Та концепция ORM, что работает здесь, известная как карта идентичности, обеспечивает, что все операции над конкретной записью внутри сессии оперируют одним и тем же набором данных. Как только объект с неким первичным ключом появится в сессии, все SQL-запросы на этой сессии вернут тот же самые питоний объект для этого самого первичного ключа. Будет выдана ошибка в случае попытки поместить в эту сессию другой, уже сохраненный объект с тем же первичным ключом. Мы можем добавить больше User-объектов, использовав add_all()

   1 >>> session.add_all([User("kolia", "Cool Kolian[S.A.]","kolia$$$"), User("zina", "Zina Korzina", "zk18")])   #добавить сразу пачку записей

А вот тут Вася решил, что его старый пароль слишком простой, так что давайте его сменим:

   1 >>> vasiaUser.password = "-=VP2001=-"   #старый пароль был таки ненадежен

Сессия внимательно следит за нами. Она, для примера, знает, что Вася был модифицирован:

   1 >>> session.dirty
   2 IdentitySet([<User('vasia','Vasiliy Pypkin', '-=VP2001=-')>])

И что еще пара User’ов ожидают сброса в базу:

   1 >>> session.new
   2 IdentitySet([<User('kolia','Cool Kolian[S.A.]', 'kolia$$$')>, <User('zina','Zina Korzina', 'zk18')>])

А теперь мы скажем нашей сессии, что мы хотим отправить все оставшиеся изменения в базу и применить все изменения, зафиксировав транзакцию, которая до того была в процессе. Это делается с помощью commit():

   1 >>> session.commit()

commit() сбрасывает все оставшиеся изменеия в базу и фиксирует транзакции. Ресурсы подключений, что использовались в сессии, снова освобождаются и возвращаются в набор. Последовательные операции с сессией произойдут в новой транзакции, которая снова запросит себе ресурсов по первому требованию. Если посмотреть васин атрибут id, что раньше был None, то мы увидим, что ему присвоено значение:

   1 >>> vasiaUser.id
   2 1

После того, как сессия вставит новые записи в базу, все только что созданные идентификаторы … будут доступны в объекте, немедленно или по первому требованию. В нашем случае, целая запись была перезагружена при доступе, как как после вызова commit() началась новая транзакция. SQLAlchemy стандартно обновляет данные от предыдущей транзакции при первом обращении с новой транзакцией, так что нам доступно самое последнее ее состояние. Уровень подгрузки настраивается, как описано в главе «Сессии».

Перевод: Головизин Алексей

Документации/SQLAlchemy (последним исправлял пользователь crenergo 2010-07-12 11:08:36)