Introduction to SQLAlchemy and Alembic Migrations

Post on 27-Jan-2015

140 views 0 download

Tags:

description

In this talk, we'll examine how to use SQLAlchemy ORM and Core in both simple queries and query builder type applications. Next, we'll explore Alembic database migrations and how we can use them to handle database changes.

Transcript of Introduction to SQLAlchemy and Alembic Migrations

SQLAlchemy andAlembic

ORM, Core and Migrations / Jason Myers @jasonamyers

Architecture

pip install sqlalchemy

pip install flask-sqlalchemy

bin/paster create -t pyramid_alchemy tutorial

from sqlalchemy import create_engineengine = create_engine( ‘dialect+driver://USER:PASS@HOST:PORT/DB’)

Porcelain

from sqlalchemy.ext.declarative import ( declarative_base)

Base = declarative_base()

from sqlalchemy import ( Column, Integer, String, Float)from sqlalchemy import ForeignKeyfrom sqlalchemy.orm import ( relationship, backref)

class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String) fullname = Column(String) balance = Column(Float) group = Column(String) addresses = relationship( "Address", order_by="Address.id", backref="user" )

def __init__(self, name, fullname, balance, group): self.name = name self.fullname = fullname self.balance = balance self.group = group

Base.metadata.create_all(engine)

from sqlalchemy.orm import sessionmakerSession = sessionmaker(bind=engine)db = Session()

user1 = User('Bob', 'Big Bob', 1000000.00, 'Mob')user2 = User('Linda', 'Linda Lu', 100.50, 'Diner')user3 = User('Lil Bob', 'Bobby Jr', 100500.00, 'Mob')user4 = User('Rachael', 'Rachael Rach', 125.50, 'Personal')

db.add(user1)

db.commit()

db.delete(user1)

db.expunge(user1)

db.refresh(user1)db.expire(user1)

db.rollback()

for user in db.query(User).all(): print user.name, user.balance

Out[1]: Bob 1000000.0

entries = db.session.query(Entries).filter_by(user_id=user.id)\ .filter(Entries.entry_time > (datetime.datetime.utcnow() - datetime.timedelta(30))).order_by('ID DESC').all()

SELECT entries.id AS entries_id, entries.user_id AS entries_user_id, entries.phone AS entries_phone, entries.measurement AS entries_measurement, entries.insulin AS entries_insulin, entries.insulin_type AS entries_insulin_type, entries.carbs AS entries_carbs, entries.tag AS entries_tag, entries.three_sixty_id AS entries_three_sixty_id, entries.entry_time AS entries_entry_time, entries.created_time AS entries_created_timeFROM entriesWHERE entries.user_id = :user_id_1 AND entries.entry_time > :entry_time_1ORDER BY ID DESC

from sqlalchemy import funcfrom sqlalchemy.sql import labelresults = db.query( User.group, label('members', func.count(User.id)), label( 'total_balance', func.sum(User.balance) )).group_by(User.group).all()for result in results: print result.group, result.members, result.total_balance

bob.addresses.append(home_address)

bob.addresses

bob.addresses.filter(Address.type='H').one()

query = db.query(User, Address).filter(User.id==Address.user_id)query = query.filter(Address.email_address=='jack@goog.com').all()

@hybrid_propertydef grand_total(self): rollup_fields = [ 'merchandise_cost', 'tax', 'shipping', ] total = sum([self.__getattribute__(x) for x in rollup_fields]) return round(total, 2)

Plumbing

from sqlalchemy import create_engineengine = create_engine( ‘dialect+driver://USER:PASS@HOST:PORT/DB’)

from sqlalchemy import (Table, Column, Integer, String, MetaData, ForeignKey)metadata = MetaData()users = Table('users', metadata, Column('id', Integer, primary_key=True), Column('name', String), Column('fullname', String),)

Base.metadata.create_all(engine)conn = engine.connect()

ins = users.insert().values(name='jack', fullname='Jack Bell')result = conn.execute(ins)

ins = users.insert()conn.execute(ins, id=2, name='wendy', fullname='Wendy McDonalds')

conn.execute(addresses.insert(), [ {'user_id': 1, 'email_address' : 'j@y.com'}, {'user_id': 1, 'email_address' : 'j@m.com'},])

def build_table(table_name):return Table( table_name, metadata, autoload=True, autoload_with=engine)

build_table('census')unavailable_fields = [ c.name for c in t.c if isinstance(c.type, NullType)]

InformixMS SQLOraclePostgresSQLiteCustom

class UnloadFromSelect(Executable, ClauseElement):

def __init__(self, select, bucket, access_key, secret_key): self.select = select self.bucket = bucket self.access_key = access_key self.secret_key = secret_key

@compiles(UnloadFromSelect)def visit_unload_from_select(element, compiler, **kw): return "unload ('%(query)s') to '%(bucket)s' credentials 'aws_access_key_id=%(access_key)s; aws_secret_access_key=%(secret_key)s' delimiter ',' addquotes allowoverwrite" % { 'query': compiler.process(element.select, unload_select=True, literal_binds=True), 'bucket': element.bucket, 'access_key': element.access_key, 'secret_key': element.secret_key, }

unload = UnloadFromSelect( select([fields]), '/'.join(['s3:/', BUCKET, filename]), ACCESS_KEY, SECRET_KEY)

unload ( 'select * from venue where venueid in ( select venueid from venue order by venueid desc limit 10)')to 's3://mybucket/venue_pipe_'credentials 'aws_access_key_id=ACCESS_KEY; aws_secret_access_key=SECRET_KEY';

s = select( [ t.c.race, t.c.factor, func.sum(g.t.c.value).label('summed') ], t.c.race > 0).where( and_( t.c.type == 'POVERTY', t.c.value != 0 )).group_by( t.c.race, t.c.factor).order_by( t.c.race, t.c.factor)

s = select( [ table.c.discharge_year, func.count(1).label( 'patient_discharges'), table.c.zip_code, ], table.c.discharge_year.in_(years)).group_by(table.c.discharge_year)s = s.where(table.c.hospital_name == provider)

if 'total_charges' not in unavailable_fields: s = s.column( func.sum(table.c.total_charges ).label('patient_charges') )

s = s.group_by(table.c.zip_code)s = s.order_by('discharges DESC')

cases = conn.execute(s).fetchall()

pip install alembic

alembic init alembic

# A generic, single database configuration.[alembic]# path to migration scriptsscript_location = alembic# template used to generate migration files# file_template = %%(rev)s_%%(slug)s# set to 'true' to run the environment during# the 'revision' command, regardless ofautogenerate# revision_environment = falsesqlalchemy.url = driver://user:pass@localhost/dbname

from glu import dbtarget_metadata = db.metadatadef run_migrations_online(): alembic_config = config.get_section(config.config_ini_section) from config import SQLALCHEMY_DATABASE_URI alembic_config['sqlalchemy.url'] = SQLALCHEMY_DATABASE_URI engine = engine_from_config( alembic_config, prefix='sqlalchemy.', poolclass=pool.NullPool)

alembic revision -m "initial"

def upgrade(): op.create_table('users_to_users', sa.Column('patient_user_id', sa.Integer(), nullable=False), sa.Column('provider_user_id', sa.Integer(), nullable=False), sa.ForeignKeyConstraint(['patient_user_id'], ['users.id'],), sa.ForeignKeyConstraint(['provider_user_id'], ['users.id'],), sa.PrimaryKeyConstraint('patient_user_id','provider_user_id') )

op.alter_column(u'reminders', u'user_created', nullable=True)

def downgrade(): op.alter_column(u'reminders', u'user_created', existing_type=mysql.TINYINT(display_width=1), nullable=False ) op.drop_table('users_to_users')

alembic upgrade head

alembic revision --autogenerate -m "Added account table"

Table (adds/removes)Columns (adds/removes)Nullable changes

Optionally: Column Type changescompare_type=TrueNo Name changes on Columns or Table

alembic currentalembic upgrade +2alembic downgrade -1alembic upgrade ae1alembic upgrade 1 --sql > file.sqlalembic history

2806761df139 -> 1e9831c8fa7d (head), Adding tags to ...2806761df139 -> 46a1d4de6e04 (head), Added timezone ...4f7119855daf -> 2806761df139 (branchpoint), Added Pr...377addf23edb -> 4f7119855daf, Added user_created to ...483d9a63fbf5 -> 377addf23edb, Adding claimed to user...464ba41d7ad8 -> 483d9a63fbf5, Adding username/passwo...2cfd9dc89267 -> 464ba41d7ad8, Adding Intercom.iod4774a3ce8 -> 2cfd9dc89267, Seperating Roles and Use...None -> d4774a3ce8, Base

THE END@jasonamyers