Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Introduction to SQLAlchemy and Alembic Migrations

5,724 views

Published on

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.

Published in: Technology, Business
  • Be the first to comment

Introduction to SQLAlchemy and Alembic Migrations

  1. 1. SQLAlchemy and Alembic ORM, Core and Migrations /Jason Myers @jasonamyers
  2. 2. Architecture
  3. 3. pip install sqlalchemy pip install flask-sqlalchemy bin/paster create -t pyramid_alchemy tutorial
  4. 4. from sqlalchemy import create_engine engine = create_engine( ‘dialect+driver://USER:PASS@HOST:PORT/DB’ )
  5. 5. Porcelain
  6. 6. from sqlalchemy.ext.declarative import ( declarative_base ) Base = declarative_base()
  7. 7. from sqlalchemy import ( Column,
 Integer, String, Float ) from sqlalchemy import ForeignKey from sqlalchemy.orm import ( relationship, backref )
  8. 8. 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" )
  9. 9. def __init__(self, name, fullname, balance, group): self.name = name self.fullname = fullname self.balance = balance self.group = group
  10. 10. Base.metadata.create_all(engine)
  11. 11. from sqlalchemy.orm import sessionmaker Session = sessionmaker(bind=engine) db = Session()
  12. 12. 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')
  13. 13. db.add(user1) db.commit() db.delete(user1)
  14. 14. db.expunge(user1) db.refresh(user1) db.expire(user1) db.rollback()
  15. 15. for user in db.query(User).all(): print user.name, user.balance Out[1]: Bob 1000000.0
  16. 16. 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()
  17. 17. 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_time FROM entries WHERE entries.user_id = :user_id_1 AND entries.entry_time > :entry_time_1 ORDER BY ID DESC
  18. 18. from sqlalchemy import func from sqlalchemy.sql import label results = 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
  19. 19. bob.addresses.append(home_address) bob.addresses bob.addresses.filter(Address.type='H').one()
  20. 20. query = db.query(User, Address).filter(User.id==Address.user_id) query = query.filter(Address.email_address=='jack@goog.com').all()
  21. 21. @hybrid_property def grand_total(self): rollup_fields = [ 'merchandise_cost', 'tax', 'shipping', ] total = sum([self.__getattribute__(x) for x in rollup_fields]) return round(total, 2)
  22. 22. Plumbing
  23. 23. from sqlalchemy import create_engine engine = create_engine( ‘dialect+driver://USER:PASS@HOST:PORT/DB’ )
  24. 24. 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), )
  25. 25. Base.metadata.create_all(engine) conn = engine.connect()
  26. 26. 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')
  27. 27. conn.execute(addresses.insert(), [ {'user_id': 1, 'email_address' : 'j@y.com'}, {'user_id': 1, 'email_address' : 'j@m.com'}, ])
  28. 28. def build_table(table_name): return Table( table_name, metadata, autoload=True, autoload_with=engine )
  29. 29. build_table('census') unavailable_fields = [ c.name for c in t.c if isinstance(c.type, NullType) ]
  30. 30. Informix MS SQL Oracle Postgres SQLite Custom
  31. 31. 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, }
  32. 32. unload = UnloadFromSelect( select([fields]), '/'.join(['s3:/', BUCKET, filename]), ACCESS_KEY, SECRET_KEY )
  33. 33. 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';
  34. 34. 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)
  35. 35. 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()
  36. 36. pip install alembic
  37. 37. alembic init alembic
  38. 38. # A generic, single database configuration. [alembic] # path to migration scripts script_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 of autogenerate # revision_environment = false sqlalchemy.url = driver://user:pass@localhost/dbname
  39. 39. from glu import db target_metadata = db.metadata def 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 )
  40. 40. alembic revision -m "initial"
  41. 41. 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)
  42. 42. 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')
  43. 43. alembic upgrade head
  44. 44. alembic revision --autogenerate -m "Added account table"
  45. 45. Table (adds/removes) Columns (adds/removes) Nullable changes
  46. 46. Optionally: Column Type changes compare_type=True No Name changes on Columns or Table
  47. 47. alembic current alembic upgrade +2 alembic downgrade -1 alembic upgrade ae1 alembic upgrade 1 --sql > file.sql alembic history
  48. 48. 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.io d4774a3ce8 -> 2cfd9dc89267, Seperating Roles and Use... None -> d4774a3ce8, Base
  49. 49. THE END @jasonamyers

×