SQLAlchemy: различия между версиями

Содержимое удалено Содержимое добавлено
м <source> -> <syntaxhighlight> (phab:T237267)
Строка 11:
Вы можете установить SQLAlchemy с нуля, используя ''setuptools''. Если они установлены, то вы можете просто набрать в консоли:
 
<sourcesyntaxhighlight lang=bash># easy_install SQLAlchemy</sourcesyntaxhighlight>
 
Эта команда скачает последнюю версию SQLAlchemy из Python Cheese Shop и установит ее на вашу машину.
Строка 17:
Также можно просто и без затей скачать архив с SQLAlchemy c официального сайта и выполнить установочный скрипт ''setup.py'':
 
<sourcesyntaxhighlight lang=bash># python setup.py install</sourcesyntaxhighlight>
 
Установка с помощью ''pip'':
 
<sourcesyntaxhighlight lang=bash># pip install sqlalchemy</sourcesyntaxhighlight>
 
Для проверки правильности установки следует проверить версию библиотеки:
<sourcesyntaxhighlight lang = python>import sqlalchemy#начните работу с этой библиотеки
print("Версия SQLAlchemy:", sqlalchemy.__version__)# посмотреть версию SQLALchemy
</syntaxhighlight>
</source>
 
== Объектно-реляционная модель SQLAlchemy ==
Строка 33:
В этом уроке мы будем использовать только БД ''SQLite'', хранящуюся в памяти.
Чтобы соединиться с СУБД, мы используем функцию ''create_engine()'':
<sourcesyntaxhighlight lang=python>
from sqlalchemy import create_engine
engine = create_engine('sqlite:///:memory:', echo=True)
</syntaxhighlight>
</source>
Флаг ''echo'' включает ведение лога через стандартный модуль logging Питона.
Когда он включен, мы увидим все созданные нами SQL-запросы. Если вы хотите просто пробежать этот урок и убрать отладочный вывод, то просто уберите его, поставив <sourcesyntaxhighlight lang=python>echo=False</sourcesyntaxhighlight>
 
===== Создание таблицы в базе данных =====
Далее мы хотим рассказать SQLAlchemy о наших таблицах. Мы начнем с одиночной таблицы ''users'', В которой будем хранить записи о наших конечных пользователях, которые посещают некий сайт N. Мы определим нашу таблицу внутри каталога ''MetaData'', используя конструктор ''Table()'', который похож на SQLный ''CREATE TABLE:''
 
<sourcesyntaxhighlight lang=python>
from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
metadata = MetaData()
Строка 52:
Column('password', String)
)
</syntaxhighlight>
</source>
 
Все о том, как определять объекты Table и о том, как загружать их определение из существующей БД (рефлексия) рассмотрено в главе [[#Метаданные БД|Метаданные БД]].
 
Далее же мы пошлем базе ''CREATE TABLE'', параметры которого будут взяты из метаданных нашей таблицы. Мы вызовем метод ''create_all()'' и передадим ему наш объект ''engine'', который и указывает на базу. Там сначала будет проверено присутствие такой таблицы перед ее созданием, так что можно выполнять это много раз — ничего страшного не случится.
<sourcesyntaxhighlight lang=python>metadata.create_all(engine)</sourcesyntaxhighlight>
Те, кто знаком с синтаксисом SQL и в частности ''CREATE TABLE'', могут заметить, что колонки ''VARCHAR'' создаются без указания их длины. В ''SQLite'' и ''PostgreSQL'' это вполне допустимый тип данных, но во многих других СУБД так делать нельзя. Для того, чтобы выполнить этот урок в ''MySQL'', длина должна быть передана строкам, как здесь:
<sourcesyntaxhighlight lang=python>Column('name', String(50))</sourcesyntaxhighlight>
Поле «длина» в строках String, как и простая разрядность/точность в ''Integer'', ''Numeric'' и т. п. не используются более нигде, кроме как при создании таблиц.
 
===== Определение класса Python для отображения в таблицу =====
В то время, как класс ''Table'' хранит информацию о нашей БД, он ничего не говорит о логике объектов, что используются нашим приложением. SQLAlchemy считает это отдельным вопросом. Для соответствия нашей таблице users создадим элементарный класс ''User''. Нужно только унаследоваться от базового питоньего класса ''Object'' (то есть у нас совершенно новый класс).
<sourcesyntaxhighlight lang=python>
class User(object):
def __init__(self, name, fullname, password):
Строка 73:
def __repr__(self):
return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password)
</syntaxhighlight>
</source>
''__init__'' — это конструктор, ''__repr__'' же вызывается при операторе ''print''.
Они определены здесь для удобства. Они не обязательны и могут иметь любую форму. SQLAlchemy не вызывает ''__init__'' напрямую
Строка 79:
===== Настройка отображения =====
Теперь мы хотим слить в едином порыве нашу таблицу user_table и класс User. Здесь нам пригодится пакет ''SQLAlchemy ORM''. Мы применим функцию ''mapper'', чтобы создать отображение между ''users_table'' и ''User''.
<sourcesyntaxhighlight lang=python>
from sqlalchemy.orm import mapper
mapper(User, users_table)
# <Mapper at 0x...; User>
</syntaxhighlight>
</source>
 
Функция ''mapper()'' создаст новый ''Mapper-объект'' и сохранит его для дальнейшего применения, ассоциирующегося с нашим классом. Теперь создадим и проверим объект типа ''User'':
<sourcesyntaxhighlight lang=python>from sqlalchemy.orm import mapper #достать "Отобразитель" из пакета с объектно-реляционной моделью
print(mapper(User, users_table)) # и отобразить. Передает класс User и нашу таблицу
user = User("Вася", "Василий", "qweasdzxc")
print(user) #Напечатает <User('Вася', 'Василий', 'qweasdzxc'>
print(user.id) #Напечатает None
</syntaxhighlight>
</source>
 
Атрибут ''id'', который не определен в ''__init__'', все равно существует из-за того, что колонка ''id'' существует в объекте таблицы ''users_table''. Стандартно ''mapper()'' создает атрибуты класса для всех колонок, что есть в ''Table''. Эти атрибуты существуют как Python дескрипторы и определяют его функциональность. Она может быть очень богатой и включать в себе возможность отслеживать изменения и АВТОМАТИЧЕСКИ подгружать данные в базу, когда это необходимо.
Строка 98:
===== Декларативное создание таблицы, класса и отображения за один раз =====
Предыдущый подход к конфигурированию, включающий таблицу ''Table'', пользовательский класс и вызов ''mapper()'' иллюстрируют классический пример использования SQLAlchemy, в которой очень ценится разделение задач. Большое число приложений, однако, не требуют такого разделения, и для них SQLAlchemy предоставляет альтернативный, более лаконичный стиль: декларативный.
<sourcesyntaxhighlight lang=python>
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
Строка 121:
# Создание таблицы
Base.metadata.create_all(engine)
</syntaxhighlight>
</source>
 
Выше — функция ''declarative_base()'', что определяет новый класс, который мы назвали ''Base'', от которого будет унаследованы все наши ORM-классы. Обратите внимание: мы определили объекты Column безо всякой строки имени, так как она будет выведена из имени своего атрибута.
Низлежащий объект ''Table'', что создан нашей ''declarative_base()'' версией ''User'', доступен через атрибут ''__table__''
<sourcesyntaxhighlight lang=python>users_table = User.__table__</sourcesyntaxhighlight>
Имющиеся метаданные MetaData также доступны:
<sourcesyntaxhighlight lang=python>metadata = Base.metadata</sourcesyntaxhighlight>
Еще один «декларативный» метод для SQLAlchemy доступен в сторонней библиотеке ''Elixir''. Это полнофункциональный продукт, который включает много встроенных конфигураций высокоуровневого отображения. Как и деклaративы, как только классы и отображения определены, использование ORM будет таким же, как и в классическом SQLAlchemy.
 
===== Создание сессии =====
Теперь мы готовы начать наше общение с базой данных. «Ручка» базы в нашем случае — это сессия ''Session''. Когда сначала мы запускаем приложение, на том же уровне нашего ''create_engine()'' мы определяем класс ''Session'', что служит фабрикой объектов ''сессий'' (''Session'').
<sourcesyntaxhighlight lang=python>
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
</syntaxhighlight>
</source>
 
В случае же, если наше приложение не имеет ''Engine''-объекта базы данных, можно просто сделать так:
<sourcesyntaxhighlight lang=python>Session = sessionmaker()</sourcesyntaxhighlight>
А потом, когда вы создадите подключение к базе с помощью ''create_engine()'', соедините его с сессией, используя ''configure()'':
<sourcesyntaxhighlight lang=python>Session.configure(bind=engine) # Как только у вас появится engine</sourcesyntaxhighlight>
 
Такой класс ''Session'' будет создавать Session-объекты, которые привязаны к нашей базе.
 
Другие транзакционные параметры тоже можно определить вызовом sessionmaker()’а, но они будут описаны в следующей главе. Так, когда вам необходимо общение с базой, вы создаете объект класса ''Session'':
<sourcesyntaxhighlight lang=python>session = Session()</sourcesyntaxhighlight>
Сессия здесь ассоциирована с SQLite, но у нее еще нет открытых соединений с этой базой. При первом использовании она получает соединение из набора соединений, который поддерживается engine и удерживает его до тех пор, пока мы не применим все изменения и/или не закроем объект сессии.
 
===== Добавление новых объектов =====
Чтобы сохранить наш User-объект, нужно добавить его к нашей сессии, вызвав ''add()'':
<sourcesyntaxhighlight lang=python>
vasiaUser = User("vasia", "Vasiliy Pypkin", "vasia2000")
session.add(vasiaUser)
</syntaxhighlight>
</source>
Этот объект будет находиться в ожидании сохранения, никакого SQL-запроса пока послано не будет. Сессия пошлет SQL-запрос, чтобы сохранить Васю, как только это понадобится, используя процесс сброса на диск(flush). Если мы запросим Васю из базы, то сначала вся ожидающая информация будет сброшена в базу, а запрос последует потом.
 
Для примера, ниже мы создадим новый объект запроса (''Query''), который загружает User-объекты. Мы «отфильтровываем» по атрибуту «имя=Вася» и говорим, что нам нужен только первый результат из всего списка строк. Возвращается тот User, который равен добавленному:
<sourcesyntaxhighlight lang=python>
ourUser = session.query(User).filter_by(name="vasia").first()
# <User('vasia','Vasiliy Pypkin', 'vasia2000')>
</syntaxhighlight>
</source>
На самом деле сессия определила, что та запись из таблицы, что она вернула, та же самая, что и запись, что она уже представляла в своей внутренней хеш-таблице объектов. Так что мы просто получили точно тот же самый объект, который только что добавили.
 
Строка 166:
 
Мы можем добавить больше User-объектов, использовав ''add_all()''
<sourcesyntaxhighlight lang=python>session.add_all([User("kolia", "Cool Kolian[S.A.]","kolia$$$"), User("zina", "Zina Korzina", "zk18")]) #добавить сразу пачку записей</sourcesyntaxhighlight>
А вот тут Вася решил, что его старый пароль слишком простой, так что давайте его сменим:
<sourcesyntaxhighlight lang=python>vasiaUser.password = "-=VP2001=-" #старый пароль был таки ненадежен</sourcesyntaxhighlight>
Сессия внимательно следит за нами. Она, для примера, знает, что Вася был модифицирован:
<sourcesyntaxhighlight lang=python>session.dirty
IdentitySet([<User('vasia','Vasiliy Pypkin', '-=VP2001=-')>])</sourcesyntaxhighlight>
И что еще пара User’ов ожидают сброса в базу:
<sourcesyntaxhighlight lang=python>session.new
IdentitySet([<User('kolia','Cool Kolian[S.A.]', 'kolia$$$')>, <User('zina','Zina Korzina', 'zk18')>])</sourcesyntaxhighlight>
А теперь мы скажем нашей сессии, что мы хотим отправить все оставшиеся изменения в базу и применить все изменения, зафиксировав транзакцию, которая до того была в процессе. Это делается с помощью ''commit()'':
<sourcesyntaxhighlight lang=python>session.commit()</sourcesyntaxhighlight>
''commit()'' сбрасывает все оставшиеся изменения в базу и фиксирует транзакции. Ресурсы подключений, что использовались в сессии, снова освобождаются и возвращаются в набор. Последовательные операции с сессией произойдут в новой транзакции, которая снова запросит себе ресурсов по первому требованию.
Если посмотреть Васин атрибут id, что раньше был None, то мы увидим, что ему присвоено значение:
<sourcesyntaxhighlight lang=python>
vasiaUser.id
# 1
</syntaxhighlight>
</source>
После того, как сессия вставит новые записи в базу, все только что созданные идентификаторы … будут доступны в объекте, немедленно или по первому требованию. В нашем случае, целая запись была перезагружена при доступе, так как после вызова ''commit()'' началась новая транзакция. SQLAlchemy стандартно обновляет данные от предыдущей транзакции при первом обращении с новой транзакцией, так что нам доступно самое последнее ее состояние. Уровень подгрузки настраивается, как описано в главе ''«Сессии»''.
 
===== Откат изменений =====
Учитывая то, что сессия работает с транзакциями, мы можем откатить сделанные изменения. Давайте внесем два изменения, которые затем мы откатим; поменяем имя пользователя vasiaUser на Vaska:
<sourcesyntaxhighlight lang=python>vasiaUser.name = 'Vaska'</sourcesyntaxhighlight>
и добавим ошибочного пользователя, fake_user:
<sourcesyntaxhighlight lang=python>
fake_user = User('fakeuser', 'Invalid', '12345')
session.add(fake_user)
</syntaxhighlight>
</source>
Посылая запросы в сессию, мы можем увидеть, что они записаны в текущую транзакцию:
<sourcesyntaxhighlight lang=python>session.query(User).filter(User.name.in_(['Vasko', 'fakeuser'])).all()
# [<User('Vasko','Vasiliy Pypkin', '-=VP2001=-')>, <User('fakeuser','Invalid', '12345')>]</sourcesyntaxhighlight>
Откатывая, мы видим, что имя vasiaUser опять стало vasia, и fake_user был удален из транзакции:
<sourcesyntaxhighlight lang=python>
session.rollback()
 
Строка 203:
# u'vasia'
fake_user in session
# False</sourcesyntaxhighlight>
посылая запрос SELECT, видно какие изменения произошли в базе данных:
<sourcesyntaxhighlight lang=python>
session.query(User).filter(User.name.in_(['vasia', 'fakeuser'])).all()
# [<User('vasia','Vasiliy Pupkin', '-=VP2001=-')>]
</syntaxhighlight>
</source>
 
===== Запросы =====
Запрос создается посредством использования функции ''query()'' для сессии. Эта функция берет переменное число аргументов, которыми может быть любая комбинация классов и дескрипторов, созданных с помощью классов. Ниже, мы демонстрируем запрос, который загружает экземпляры User. В итеративном цикле возвращается список объектов User:
<sourcesyntaxhighlight lang=python>
for instance in session.query(User).order_by(User.id):
print instance.name, instance.fullname
# vasia Vasiliy Pupkin
# kolia Cool Kolian[S.A.]
# zina Zina Korzina</sourcesyntaxhighlight>
Запрос также поддерживает в качестве аргументов дескрипторы, созданные с помощью ORM. Каждый раз, когда запрашиваются разнообразные объекты классов или многоколоночные объекты в качестве аргументов функции ''query()'', результаты возвращаются в виде кортежей:
<sourcesyntaxhighlight lang=python>
for name, fullname in session.query(User.name, User.fullname):
print name, fullname
Строка 225:
# kolia Cool Kolian[S.A.]
# zina Zina Korzina
</syntaxhighlight>
</source>
Кортежи, которые возвращаются в запросе, являются именованными, и могут обрабатываться, как обычные объекты Python’а. Имена такие же, как и имена атрибутов для атрибутов, и имена классов для классов:
<sourcesyntaxhighlight lang=python>
for row in session.query(User, User.name).all():
print row.User, row.name
Строка 233:
# <User('kolia','Cool Kolian[S.A.]', 'kolia$$$')> kolia
# <User('zina','Zina Korzina', 'zk18')> zina
</syntaxhighlight>
</source>
Вы можете определять свои имена, используя конструкцию ''label()'' для скалярных атрибутов и ''aliased()'' для классов:
<sourcesyntaxhighlight lang=python>
from sqlalchemy.orm import aliased
user_alias = aliased(User, name='user_alias')
Строка 242:
# <User('vasia','Vasiliy Pupkin', '-=VP2001=-')> vasia
# <User('kolia','Cool Kolian[S.A.]', 'kolia$$$')> kolia
# <User('zina','Zina Korzina', 'zk18')> zina</sourcesyntaxhighlight>
Базовые операции с запросами включают в себя ''LIMIT'' и ''OFFSET'', которые удобно использовать со срезами массивов из Python, и обычно в сочетании с ''ORDER BY'':
<sourcesyntaxhighlight lang=python>
for u in session.query(User).order_by(User.id)[1:2]:
print u
# <User('kolia','Cool Kolian[S.A.]', 'kolia$$$')>
# <User('zina','Zina Korzina', 'zk18')></sourcesyntaxhighlight>
и фильтруя результаты, которые выполнены либо с помощью ''filter_by()'', которая использует ключевые аргументы:
<sourcesyntaxhighlight lang=python>
for name in session.query(User.name).filter_by(fullname='Vasiliy Pupkin'):
print name
# vasia</sourcesyntaxhighlight>
…либо ''filter()'', которая использует более подходящие для ''SQL'' конструкции языка. Это позволяет использовать более привычные операторы Python’а с атрибутами класса вашего отображения класса:
<sourcesyntaxhighlight lang=python>
for name in session.query(User.name).filter(User.fullname=='Vasiliy Pupkin'):
print name
# vasia</sourcesyntaxhighlight>
Объект запроса является полностью генеративным(порождающим), а это означает, что каждый вызов метода возвращает объект запроса, для которого можно добавить дополнительные ограничения. Например, чтобы получить пользователей с именем \"vasia\" и полным именем \"Vasiliy Pupkin\", вы можете повторно использовать функцию ''filter()'', которая объединит критерии отбора, используя ''AND''
<sourcesyntaxhighlight lang=python>
for user in session.query(User).filter(User.name=='vasia').filter(User.fullname=='Vasiliy Pupkin'):
print user
# <User('vasia','Vasiliy Pupkin', '-=VP2001=-')></sourcesyntaxhighlight>
 
== См. также ==