1. Install
● Python 2.5 or 2.6 (or pysqlite for 2.4)
● SQLAlchemy 0.5
1. easy_install, if needed
● wget http://peak.telecommunity.com/dist/ez_setup.py
● python ez_setup.py
2. easy_install sqlalchemy==0.5.8
● http://old.utahpython.org/sqla2010/
2. Michael Bayer
Michael Bayer is a software architect in New
York City and is the creator of SQLAlchemy.
http://techspot.zzzeek.org/
@zzzeek
3. ORM 101 – the bad old days
c = db.cursor()
sql = "SELECT * FROM users WHERE name = %s"
c.execute(sql, (name,))
user = User(*c.fetchone())
user.last_login_at = time.time()
sql = "UPDATE users SET last_login_at = %s WHERE name = %s"
c.execute(sql, (user.last_login_at, name0))
c.commit()
4.
5. ORM 101 – and there was light
session = Session()
user = session.query(User).filter(name=name).one()
user.last_login_at = time.time()
session.commit()
6. The devil is in the details
● Compound WHERE clauses, subqueries,
outer joins, sql functions, ...
● Eager/lazy loading
● Support for legacy schemas
● Inheritance
● Conceptual integrity
● Setup overhead
● Database support
10. Tough love?
“Disproving the myth of 'the best database layer is
the one that makes the database invisible' is a
primary philosophy of SA. If you don't want to deal
with SQL, then there's little point to using a
[relational] database in the first place.”
11. Technical excellence
● PK: multi-column is fine; mutable is fine; any
data type is fine; doesn't have to be named
“id”
http://blogs.ittoolbox.com/database/soup/archives/primary-keyvil-part-i-73
● Recognizes all database defaults instead of
allowing a few special cases like “created_at”
● Doesn't make up its own query language
● No XML
● Introspection or define-tables-in-Python
● Session/unit-of-work based
● Migrations
16. Today's agenda: fundamentals
Data Mapper vs Active Record
SA Fundamentals
Mapping basics
Queries
Sessions & the identity map
Relationship lifecycle
Backrefs
17. Agenda 2: More ORM details
Multi-object queries
One-to-one
Many-to-many
Relation queries
Eager/lazy loading
Transactions
Extensions and related projects
18. Agenda 3: Extensions and
related projects
Migrate
FormAlchemy
SqlSoup
Elixir
z3c.sqlalchemy
38. Exercise
● Map the orderitems table to an OrderItem
class
● Get a list of all OrderItems
– Where they belong to order #3
– ... or the item name is “item 1”
– ... ordered by item name
(Now would be a good time to look at
tutorial_samples.py)
46. Why sessions are your friends
Some ORMs rely on explicit save
u = User.get(1)
u.orders[0].description = 'An order'
u.save() # not real SA code
# doh! orders[0] was not saved!
More convenient, less error-prone to let ORM
track dirty objects
47. Identity map
Rows with same PK get mapped to same
object (per-session)
Limited caching for get()
Only for get()
50. Exercise
Load the user named 'jack' (lowercase)
Remove his first order
Save changes to the db
51. Fun with collections
u = session.query(User).filter_by(name='jack').one()
u.orders = u.orders[1:]
session.commit()
>>> session.query(Order).get(1)
Order(order_id=1,user_id=None,...)
52. Two solutions
o = u.orders[0]
>>> o
Order(order_id=1,user_id=7,description=u'order 1',isopen=0)
session.delete(o)
session.commit()
>>> u.orders
[Order(order_id=3,...), Order(order_id=5,...)]
Why does it make sense for this to work
differently than the previous example?
53. #2: delete-orphan
class User(Base):
__table__ = users
orders = relation(Order,
cascade="all, delete-orphan",
order_by=[Order.order_id])
u = session.query(User).get(7)
u.orders = u.orders[1:]
session.commit()
>>> session.query(Order).get(1) is None
True
56. Backrefs
class User(Base):
__table__ = users
orders = relation(Order,
backref='user',
order_by=[orders.c.order_id])
o = session.query(Order).first()
o.user
57. That's it for fundamentals
Multi-object queries
One-to-one
Many-to-many
Relation queries
Eager/lazy loading
Transactions
Extensions and related projects
59. A taste of advanced querying
All orders with an open order:
q = session.query(User).join(User.orders)
q.filter(Order.isopen==1).all()
60. Selecting multiple classes
What if we want the user and the order?
(efficiently)
q = session.query(User, Order) # cartesian join!
q = q.join(User.orders)
# or!
q = q.filter(User.user_id==Order.user_id)
u, o = q.filter(Order.isopen==1).first()
61. Dropping down to SQL
sql = """
select u.*
from users u
where u.user_id in (
select user_id from orders where isopen = 1)
"""
session.query(User).from_statement(sql)
sql = """
select u.*, o.*
from users u join orders o on (u.user_id = o.user_id)
where o.isopen = 1
"""
session.query(User, Order).from_statement(sql)
62. Dropping down a little less
q = session.query(User, Order)
q = q.join(User.orders)
q.filter("orders.isopen = 1").all()
63. Exercise
In a single query, select the users and orders
where the order description is like 'order%'
64. One to one
class Address(Base):
__table__ = addresses
class User(Base):
orders = relation(Order,
order_by=[orders.c.order_id])
address = relation(Address)
How does SQLA know to treat these
differently?
65. Many to many
class Keyword(Base):
__table__ = keywords
class Item(Base):
__table__ = orderitems
keywords = relation(Keyword, secondary=itemkeywords)
73. Eager loading
class User(Base):
orders = relation(Order,
order_by=[orders.c.order_id],
lazy=False)
# or
q = session.query(User).options(eagerload('orders'))
# also lazyload, noload
74. Transactions are simple
● session: autocommit=False
– this is the default
– commit() / rollback() manually
● autocommit=True
– each flush() also commits