39
SQLAlchemy доступ к реляционным данным в стиле Python Юревич Юрий, http://pyobject.ru Семинар Учебного центра Люксофт, 16 сентября 2008

SQLAlchemy Seminar

Embed Size (px)

DESCRIPTION

Seminar topic about SQLAlchemy -- Pythonic ORM and SQL toolkit.

Citation preview

Page 1: SQLAlchemy Seminar

SQLAlchemyдоступ к реляционным данным

в стиле Python

Юревич Юрий, http://pyobject.ru

Семинар Учебного центра Люксофт,16 сентября 2008

Page 2: SQLAlchemy Seminar

2

«SQLAlchemy — это Python SQL тулкит и ORM,

которые предоставляют разработчику

всю мощь и гибкость SQL»

http://sqlalchemy.org

Page 3: SQLAlchemy Seminar

3

Active Record (Django ORM, Storm)

Page 4: SQLAlchemy Seminar

4

Data Mapper (SQLAlchemy)

Page 5: SQLAlchemy Seminar

5

Архитектура SQLAlchemy

Page 6: SQLAlchemy Seminar

6

Про что будет

Page 7: SQLAlchemy Seminar

7

Про что (почти) не будет

Page 8: SQLAlchemy Seminar

8

Управление схемой данных

Page 9: SQLAlchemy Seminar

9

Схема == метаданные

Page 10: SQLAlchemy Seminar

10

Создание таблиц: SQL

CREATE TABLE users ( id INTEGER NOT NULL, name VARCHAR(255), PRIMARY KEY (id));

CREATE TABLE creds ( id INTEGER NOT NULL, user_id INTEGER, login VARCHAR(20), passwd VARCHAR(40), PRIMARY KEY (id), FOREIGN KEY(user_id) REFERENCES users (id));

Page 11: SQLAlchemy Seminar

11

Создание таблиц: SQLAlchemy

meta = MetaData()

users = Table('users', meta, Column('id', Integer, primary_key=True), Column('name', Unicode(255)),)

creds = Table('creds', meta, Column('id', Integer, primary_key=True), Column('user_id', Integer, ForeignKey('users.id')), Column('login', String(20)), Column('passwd', String(40)),)

Page 12: SQLAlchemy Seminar

12

MetaData: что умеет

● Создавать/удалять:

– Всё сразу: meta.create_all() / meta.drop_all()

– По отдельности: users.create() / users.drop()

● Рефлексия:

– Потаблично: users = Table('users', meta, autoload=True)

– Всё сразу: meta.reflect()

● Интроспекция:

– meta.tables

Page 13: SQLAlchemy Seminar

13

MetaData: пример из жизни

# ... описание схемы

def get_indexes(): return [ Index('ix_users_id', users.c.id, unique=True), Index('ix_creds_id', creds.c.id, unique=True), Index('ix_creds_user_id', creds.c.user_id), ]

def load(): # ... загрузка данных pass

def postload(): for ix in get_indexes(): ix.create()

Page 14: SQLAlchemy Seminar

14

Язык SQL-выражений

Page 15: SQLAlchemy Seminar

15

Для любителей трехбуквенных сокращений

● DML (Data Managament Language):– insert/update/delete

● DQL (Data Query Language):– select

Page 16: SQLAlchemy Seminar

16

Insert

● Данные указываются при создании ins = users.insert(values={'name': u'Jack'}) ins.execute()

● Данные указываются при выполнении ins = users.insert() ins.execute(name=u'Jack')

● Несколько записей сразу ins = users.insert() ins.execute([{'name': u'Jack'}, {'name': u'Ed'}])

● Явно указывая соединение engine = create_engine('sqlite:///') ins = insert(users) engine.connect().execute(ins, {'name': u'Jack'})

Page 17: SQLAlchemy Seminar

17

Delete

● Условие — SQLAlchemy SQL expressiondel_ = users.delete(users.c.name==u'Jack')del_.execute()

● Условие — строка с SQLdel_ = users.delete('users.name=:user')del_.params({'user': u'Jack'}).execute()

Page 18: SQLAlchemy Seminar

18

SelectSELECT * FROM users

q = users.select()

SELECT users.id, users.name FROM users

Page 19: SQLAlchemy Seminar

19

SelectSELECT id FROM users WHERE name='Jack'

q = users.select([users.c.id], users.c.name==u'Jack')

SELECT users.id FROM users WHERE users.name=:name

Page 20: SQLAlchemy Seminar

20

SelectSELECT * FROM usersJOIN creds ONcreds.user_id=users.id

q = users.join(creds).select()

SELECT users.id, users.name, creds.id, creds.user_id, creds.login, creds.passwdJOIN creds ONusers.id=creds.user_id

Page 21: SQLAlchemy Seminar

21

Почему SA SQL Expr?

● Потому что Python круче SQL

Page 22: SQLAlchemy Seminar

22

Почему SA SQL Expr?

● Потому что Python круче SQL● Генерация SQL на лету:

– q = select([users, creds.c.login], from_obj=users.join(creds), whereclause=users.c.name==u'Jack')

– q = users.select()q = q.where(users.c.name==u'Jack')q = q.column(creds.c.login)q.append_from(join(users, creds))

– SELECT users.id, users.name, creds.loginFROM usersJOIN creds ON creds.user_id = users.idWHERE users.name = 'Jack'

Page 23: SQLAlchemy Seminar

23

Object Relational Mapper (ORM)

Page 24: SQLAlchemy Seminar

24

Data Mapper, снова

Page 25: SQLAlchemy Seminar

25

Рабочий пример: схема данных

Page 26: SQLAlchemy Seminar

26

Рабочий пример: таблицы в SQL

CREATE TABLE users ( id INTEGER NOT NULL, name VARCHAR(255), PRIMARY KEY (id));

CREATE TABLE creds ( id INTEGER NOT NULL, user_id INTEGER, login VARCHAR(20), passwd VARCHAR(20), PRIMARY KEY (id), FOREIGN KEY(user_id) REFERENCES users (id)

);

CREATE TABLE messages ( id INTEGER NOT NULL, subject VARCHAR(255), body TEXT, author_id INTEGER, PRIMARY KEY (id), FOREIGN KEY(author_id) REFERENCES users (id));

CREATE TABLE message_recipients ( message_id INTEGER NOT NULL, recipient_id INTEGER NOT NULL, PRIMARY KEY ( message_id, recipient_id), FOREIGN KEY(message_id) REFERENCES messages (id), FOREIGN KEY(recipient_id) REFERENCES users (id));

Page 27: SQLAlchemy Seminar

27

Рабочий пример:Данные

users = { u'Jack': ['jack', 'jack-rabbit'], u'Edvard': ['ed'], u'Mary': ['mary'],}

messages = ( { 'author': u'Jack', 'recipients': [u'Edvard', u'Mary'], 'title': u'The first', 'body': u'Ha-ha, I\'m the first!, }, { 'author': u'Edvard', 'recipients': [u'Jack', u'Mary'], 'title': u'Hey all', 'body': u'Hi, I\'m here', }, { 'author': u'Edvard', 'recipients': [u'Mary'], 'title': u'The silence', 'body': u'Why are you ignoring me?', }, { 'author': u'Mary', 'recipients': [u'Jack'], 'title': u'Hi', 'body': u'Hi, Jack, how are you?', },)

Page 28: SQLAlchemy Seminar

28

Рабочий пример: таблицы в SA SQL Expr

users = Table('users', meta,Column('id', Integer, primary_key=True),Column('name', Unicode(255)),)

creds = Table('creds', meta,Column('id', Integer, primary_key=True),Column('user_id', Integer, ForeignKey('users.id')),Column('login', String(20)),Column('passwd', String(20)),)

messages = Table('messages', meta,Column('id', Integer, primary_key=True),Column('subject', Unicode(255)),Column('body', Text),Column('author_id', Integer, ForeignKey('users.id')),)

message_recipients = Table('message_recipients', meta,Column('message_id', Integer, ForeignKey('messages.id'), primary_key=True),Column('recipient_id', Integer, ForeignKey('users.id'), primary_key=True),)

Page 29: SQLAlchemy Seminar

29

Рабочий пример: Mappings

Session = scoped_session(sessionmaker(autoflush=False))

class Base(object): def __init__(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value)

class User(Base): passclass Cred(Base): passclass Message(Base): pass

Session.mapper(User, users) # (1)

Session.mapper(Cred, creds, properties={ # (2) 'user': relation(User, backref='credentials'),})

Session.mapper(Message, messages, properties={ # (3) 'recipients': relation(User, backref='inbox', secondary=message_recipients), 'author': relation(User, backref='outbox'),})

Page 30: SQLAlchemy Seminar

30

Рабочий пример: готовность №1

[ 1]>>> import schema as sch[ 2]>>> import mappings as m

[ 3]>>> engine = create_engine('sqlite:///example.sqlite')[ 4]>>> sch.meta.bind = engine[ 5]>>> sch.meta.create_all()[ 6]>>> m.Session.bind = engine

[ 7]>>> u1 = m.User.query.get(1)[ 8]>>> u1.id<<< 1[ 9]>>> u1.name<<< u'Jack'[10]>>> u1.credentials<<< [<Cred: jack>, <Cred: jack-rabbit>][11]>>> u1.outbox<<< [<Message: from Jack to Edvard, Mary (subj: The first)>]

Page 31: SQLAlchemy Seminar

31

Рабочий пример: выборки

[1]>>> q = m.User.query.filter(User.id>1)[2]>>> print str(q)SELECT users.id AS users_id, users.name AS users_nameFROM usersWHERE users.id > ?

[3]>>> q = q.filter(m.User.name!=None)[4]>>> print str(q)SELECT users.id AS users_id, users.name AS users_nameFROM usersWHERE users.id > ? AND users.name IS NOT NULL

[5]>>> list(q)<<< [<User u'Edvard'>, <User u'Mary'>]

[6]>>> q.first()<<< <User u'Edvard'>

Page 32: SQLAlchemy Seminar

32

Рабочий пример:выборки (продолжение)

[1]>>> rabbit = m.Cred.query.\ filter_by(login='jack-rabbit').one()

[2]>>> rabbit_user = m.User.query.\ filter(User.credentials.contains(rabbit)).\ one()

[3]>>> rabbit_messages = m.Message.query.\ filter(or_( Message.author==rabbit_user, Message.recipients.contains(rabbit_user) ))[4]>>> list(rabbit_messages)<<< [<Message: from Jack to Edvard, Mary (subj: The first)>, <Message: from Edvard to Jack, Mary (subj: Hey all)>, <Message: from Mary to Jack (subj: Hi)>]

Page 33: SQLAlchemy Seminar

33

Рабочий пример:выборки (хардкор)

# Выбрать пользователей, у которых# в исходящих больше одного сообщения[1]>>> sess = m.Session()[2]>>> q = sess.query(m.User, func.count('*')).\ join(m.Message).group_by(m.User).\ having(func.count('*')>1)[3]>>> list(q)<<< [(<User u'Edvard'>, 2)]

# Выбрать пользователей, у которых# во входящих больше одного сообщения[4]>>> sess = m.Session()[5]>>> q = sess.query(m.User, func.count('*')).\ join(sch.message_recipients).group_by(m.User).\ having(func.count('*')>1)[6]>>> list(q)<<< [(<User u'Jack'>, 2), (<User u'Mary'>, 3)]

Page 34: SQLAlchemy Seminar

34

Рабочий пример:сессия, unit of work

[ 1]>>> engine = create_engine('sqlite:///example.sqlite', echo=True)[ 2]>>> sch.meta.bind = engine[ 3]>>> m.Session.bind = engine[ 4]>>> sess = m.Session()[ 5]>>> jack = m.User.query.filter_by(name=u'Jack').one()2008-09-14 14:19:11,504 INFO sqlalchemy.engine.base.Engine.0x...ca2c SELECT...[ 6]>>> ed = m.User.query.filter_by(name=u'Edvard').one()2008-09-14 14:20:22,731 INFO sqlalchemy.engine.base.Engine.0x...ca2c SELECT...[ 7]>>> jack.name = u'Jack Daniels'[ 8]>>> sess.dirty<<< IdentitySet([<User u'Jack Daniels'>])[ 9]>>> ed.name = u'Edvard Noringthon'[10]>>> sess.dirty<<< IdentitySet([<User u'Edvard Noringthon'>, <User u'Jack Daniels'>])[11]>>> sess.flush()2008-09-14 14:21:00,535 INFO sqlalchemy.engine.base.Engine.0x...ca2c UPDATE users SET name=? WHERE users.id = ?2008-09-14 14:21:00,535 INFO sqlalchemy.engine.base.Engine.0x...ca2c ['Jack Daniels', 1]2008-09-14 14:21:00,604 INFO sqlalchemy.engine.base.Engine.0x...ca2c UPDATE users SET name=? WHERE users.id = ?2008-09-14 14:21:00,604 INFO sqlalchemy.engine.base.Engine.0x...ca2c ['Edvard Noringthon', 2]

Page 35: SQLAlchemy Seminar

35

users = Table( 'users', meta, ...)

class User: pass

mapper(users, User)

«user» 5 раз, скучно?

Page 36: SQLAlchemy Seminar

36

Elixir: ActiveRecord поверх SQLAlchemy● Elixir (http://elixir.ematia.de):

– Декларативное описание в стиле Django

– События:● До (update, insert, delete)● После (update, insert, delete)

– Плагины:● acts_as_versioned (автоматическое хранение истории)● acts_as_encrypted (шифрование колонок)● associable (аналог Django generic relations)● ...

Page 37: SQLAlchemy Seminar

37

Еще вкусности SQLAlchemy● «Хитрые» атрибуты:

– Отложенная/непосредственная подгрузка

– SQL-выражение как атрибут

● Партицирование:– Вертикальное (часть таблиц в одной БД, часть в другой)

– Горизонтальное (шардинг, содержимое одной таблицы «раскидано» по таблицам/БД)

● Нетривиальные маппинги:– Несколько маппингов к одному классу

– Маппинг классов к SQL-запросам

● ...

Page 38: SQLAlchemy Seminar

38

Диагноз: нужна ли вам SA

● Используете другой ORM и счастливы?– Нет, в SQLAlchemy

«много буков», нет смысла

● Используете DB API2 и «чистый» SQL?– Да, SQLAlchemy

даст вам это и много что еще

Page 39: SQLAlchemy Seminar

39

Вопросы?

... не успели задать? - шлите почтой: [email protected]