Slide 1

Slide 1 text

Quick Overview of SQLAlchemy June 15, 2011 - Boston Python Meetup Michael Kowalchik CTO - Smarterer

Slide 2

Slide 2 text

Who am I? • Enigneer, programmer, lover of Boston • CTO and co-founder of Smarterer • this is my second company, first was: Grazr. 5 sec history: 3+ years, great tech, great team, no market, painful (but deep) lesson • aside: I’ll never, ever... write a big project in Perl again.

Slide 3

Slide 3 text

caveats, privisos, etc... • SQLAlchemy is big - this is a 10K foot view • I’m not an expert, but I’ve been messing with it for a while now so YMMV • Smarterer uses it as the basis of our ORM / database lib • Speaking of... we launched Monday so running low on sleep :)

Slide 4

Slide 4 text

Quick Poll • I assume everyone knows what an ORM is? • Have experience with Rails Active Record or Django ORM? • Have experience with SA?

Slide 5

Slide 5 text

DONT NEED NO NEWFANGLED “ORM”!

Slide 6

Slide 6 text

Anti ORM? • Used to using a lot of functions on the database (udfs, replication, triggers, etc...) • Most ORM’s fail to deliver their promised level of abstraction • Most ORM’s marketing: “You’ll never have to write SQL! Well... until you have to, so here’s an afterthought raw SQL interface”

Slide 7

Slide 7 text

Anti ORM? • Don’t protect me from the database • Don’t force me not to use the power and features of the database (otherwise why are we using an RDS?) • Make my life easier, but don’t coddle me

Slide 8

Slide 8 text

SQLAlchemy, what is it? • A Python database “toolkit” that includes powerful programmatic SQL expression generation, database abstraction through dialects, as well as a powerful ORM

Slide 9

Slide 9 text

SQLAlchemy Architecture ORM SQL Expression Language Types DBAPI Connection Pooling Schema Metadata arrows denote dependency

Slide 10

Slide 10 text

Philosophy (my take) • SQLAlchemy is for: someone who likes relational databases, understands their power, likes Python and wants to keep the power of the database • Not strictly database agnostic - doesn’t force a lowest common denominator • For people who like more flexibility

Slide 11

Slide 11 text

SA Concepts • engine (connection string, dbapi, dialects) • connection (pools) • metadata (sa representation of schema) • expressions - insert, select, update • ORM, session and Identity Map

Slide 12

Slide 12 text

engine • engine is SQLAlchemy’s representation of the database and it’s dialect • uses connection string URLs from sqlalchemy import create_engine # connection strings are RFC-1738 style urls # DIALECT+DRIVER://USERNAME:PASSWORD@HOST:PORT/DATABASE engine = create_engine('sqlite:///:memory:')

Slide 13

Slide 13 text

Dialects • dialect is the conversion from SQLAlchemy’s internal database representation to the specific database (MySQL, Postgres, Oracle, sqlite, etc...) • specified in the url from sqlalchemy import create_engine # connection strings are RFC-1738 style urls # DIALECT+DRIVER://USERNAME:PASSWORD@HOST:PORT/DATABASE # engine1 = create_engine('mysql://sa_test:@localhost/test') # engine2 = create_engine('sqlite:///test_db.sqlite') engine = create_engine('sqlite:///:memory:')

Slide 14

Slide 14 text

Connection Pools • Fairly standard database API pattern • database connections are expensive operations • connection pools “hang on” to created connections and re-use them • request a new connection and the pool tries to recycle an existing connection

Slide 15

Slide 15 text

metadata • the internal SQLAlchemy representation of the schema. • Includes tables, columns, relationships # the object that stores sqlalchemy's understanding of the schema metadata = MetaData() # traditional way to define tables addresses_table = Table('addresses', metadata, Column('id', Integer, primary_key=True), Column('street', String(255)), Column('city', String(255)), Column('user_id', Integer, ForeignKey("users.id"), nullable=False) )

Slide 16

Slide 16 text

Expression Language • The expression language isn’t an afterthought for one-off sql statements • build programmatic SQL using Python conn = engine.connect() select_query = select([users_table, addresses_table], (users_table.c.id==addresses_table.c.id) & (users_table.c.name=="bob")) result = conn.execute(select_query) for row in result: print row result.close() conn.close()

Slide 17

Slide 17 text

ORM • The ORM builds on the Expression Language • Allows mapping “plain” Python objects - no special inheritance, to database • SA’s orm is based on the Data Mapper design pattern

Slide 18

Slide 18 text

ORM: Active Record • Every object is assumed to correspond directly to a table and a row • The object contains methods for CRUD (Create, Read, Update, Delete) • Strong mirroring between database schema and object model definition Object Table and row

Slide 19

Slide 19 text

ORM: Data Mapper • How your objects map to the database are controlled by mapper objects • There is no requirement for a 1:1, table & row to object mapping • Active Record is kind of like a special case of Data Mapper Data Mapper Database Object

Slide 20

Slide 20 text

Why Data Mapper • More flexible • Decouples object and application logic from the database representation • Allows objects to represent complex data ops (joins, arbitrary selects, sql functions) • Easier to build apps on legacy databases • Can operate like Active Record

Slide 21

Slide 21 text

Why not Data Mapper • Database schema / model definition synchronization not strictly enforced by the ORM (could be a good or bad thing) • More ‘things’ to think about (sessions or object repositories) • For simple CRUD apps, might be overkill

Slide 22

Slide 22 text

Using the ORM • Two main ways of defining the ORM: Traditional and Declarative • Traditional explicitly defines schema and object to be mapped separately, then uses mappers to associate • Declarative is an alt syntax to simplify the object -> database mapping

Slide 23

Slide 23 text

Traditional Syntax • Explicitly define the table schema (or autoload it via schema reflection) • Associate ‘regular’ python objects with the schema using mappers. • Mappers will try to associate based on attribute names by default but you can control/override this

Slide 24

Slide 24 text

“Traditional” Example from sqlalchemy import Table, Column, Integer, String, MetaData from sqlalchemy.orm import mapper # the object that stores sqlalchemy's understanding of the schema metadata = MetaData() # traditional way to define tables users_table = Table('users', metadata, Column('id', Integer, primary_key=True), Column('name', String(255)), Column('fullname', String(255)), Column('password', String(255)) ) class User(object): '''Example User object, note it's a 'regular' object. ''' def __init__(self, name, fullname, password): self.name = name self.fullname = fullname self.password = password # here's the 'magic' User now connected to database mapper(User, users_table)

Slide 25

Slide 25 text

Declarative Style • SQLAlchemy includes an alternate syntax for ORM. • Makes it ActiveRecord-like • Assumes object and table definition will closely align • Specifies table/db and object and mapping in one operation

Slide 26

Slide 26 text

Declarative Example from sqlalchemy import Table, Column, Integer, String, MetaData import sqlalchemy.ext.declarative from sqlalchemy.ext.declarative import declarative_base # the object that stores sqlalchemy's understanding of the schema metadata = MetaData() Base = declarative_base(metadata=metadata) class NewsPaper(Base): __tablename__ = "newspapers" id = Column(Integer, nullable=False, primary_key=True) name = Column(String(255)) def __repr__(self): return ''''''.format(self.name)

Slide 27

Slide 27 text

Using the ORM: sessions • The center of the SQLAlchemy ORM is the Session • Provides transactions, and Unit-Of-Work pattern • Talks to the mappers, handles obj <-> db

Slide 28

Slide 28 text

Unit of Work • Keeps a list of all objects that have been modified and coordinates the writing of all changes to the persistent store • Uses an identity map, based on primary keys, that allows it to track objects • Primary use: helps avoid lots of small and/ or unnecessary database calls

Slide 29

Slide 29 text

Adding new objects # ask for a new session database_session = Session() u = User(name="mikepk") # add our new object to the db session database_session.add(u) # the object does not persist until the session is committed database_session.commit() # we now have a user in the database

Slide 30

Slide 30 text

Query for existing objects # ask for a new session database_session = Session() # get all users from the database all_users = database_session.query(User).all() >>> for user in all_users: ... print user.name ... mikepk Bob Alice Charlie

Slide 31

Slide 31 text

Schema Reflection • Using the option of autoload=True on a table, SQLAlchemy will build it’s schema from the database • Very useful for building apps on existing databases

Slide 32

Slide 32 text

Demo

Slide 33

Slide 33 text

Smarterer is hiring! We’re looking for deeply passionate technical people. Python, web dev, cloud, full stack. [email protected] Plug!