Faruk Akgul

SQLAlchemy: The ORM

July 23, 2011 | View Comments

Long story short; SQLAlchemy is an ORM for Python. SQLAlchemy is far more complex than Django's ORM. You can actually call stored functions in SQLAlchemy. For a Django vs. SQLAlchemy article, have a look at Armin Ronacher's post.

There are two different ways to define a new table. One is; defining a table within the MetaData. Second one is; creating table, class and mapper with Declarative Base. I'm more like a declarative_base guy and I must say that I've never used MetaData.

Warming Up The Engine

This might be a way to define the SQLAlchemy engine. In SQLAlchemy, everything is done via a Session().

from sqlalchemy import (Column, SmallInteger, Unicode, UnicodeText,
    CHAR, VARCHAR, DateTime, ForeignKey, create_engine)
from sqlalchemy.orm import (scoped_session, sessionmaker, relation, relationship)
from sqlalchemy.ext.declarative_base import declarative_base

First, we imported data types such as SmallInteger, Unicode, VARCHAR etc. In the second line, we imported sessionmaker and relations which we use in our tables since SQLAlchemy won't wave its magic wand to define relations between tables. Third line; the declarative_base.

engine = create_engine(ENGINE, echo=True, pool_recycle=3600)
Base = declarative_base()
Session = scoped_session(sessionmaker(bind=engine, autoflush=True,
    autocommit=True, expire_on_commit=True))
session = Session()

Above code defines the engine, and declarative_base class and the session. Once the setup is complete, we need to create the table classes.

class User(Base):
    __tablename__ = 'user'
    __table_args__ = {'mysql_charset': 'utf8', 'mysql_engine': 'InnoDB'}

    uid = Column(CHAR(32), primary_key=True)
    name = Column(Unicode(255), nullable=False)
    username = Column(VARCHAR(32), nullable=False, unique=True)
    password = Column(CHAR(64), nullable=False, index=True)
    registered = Column(DateTime, default=datetime.utcnow())

    def __init__(self, name, username, password):
        self.uid = uuid4().hex
        self.name = name
        self.username = username
        self.password = password

    def __repr__(self):
        return u'<User (%s, %s)>' % (self.uid, self.username)

Here, we define a new class named User. Remember, SQLAlchemy expects you to explicitly define tablenames and all table options so we define the tablename as user.

Then we defined the table options. In table_args we explicitly stated to SQLAlchemy that this table uses utf8 as charset and it uses InnoDB as the engine.

Then we defined the table fields. nullable=False says this field won't accept NULL. unique=True says this field is unique indexed. default=datetime.utcnow() says field's default value.

Many-To-Many, Many-To-One

Let's say we've simple blog where users' post their articles.

class Article(Base):
    __tablename__ = 'article'
    __table_args__ = {'mysql_charset': 'utf8', 'mysql_engine': 'InnoDB'}

    aid = Column(CHAR(32), primary_key=True)
    author_id = Column(CHAR(32), ForeignKey('user.uid', ondelete='cascade'))
    title = Column(Unicode(255), nullable=False)
    content = Column(UnicodeText, nullable=False)
    posted = Column(DateTime, default=datetime.utcnow())

    author = relation(User, primaryjoin=User.uid == author_id, lazy='select')

    # Let's assume there's also a Comments table.
    # In this case we could make a relation between tables by ordering the results.
    comments = relation('Comments', order_by='Comments.posted.desc()', backref='article')

In the above example, we create a relation to User table. To create a many-to-many tables, we need to create the classes and the necessary association class.

CRUD Operations

CRUD is simple.

Create

For example, let's insert some data to Article class.

def insert_article(args):
    res = Article(**args)
    session.add(res)
    session.commit()
    return res

Retrieve

For example; let's retrieve an article with the specified article_id.

def retrieve_article(article_id):
    res = None
    try:
        res = session.query(Article).filter(Article.aid == article_id).first()
    except (NoResultFound):
        session.rollback()
    return res

Another example which we return some articles of a user.

def retrieve_user_articles(user_id):
    res = None
    try:
        res = session.query(Article).filter(Article.author_id == user_id).order_by(Article.posted.desc()).all()
    except (NoResultFound):
        session.rollback()
    return res

Update

def update_article(article_id):
    res = session.query(Article).get(article_id)
    res.title = 'New Title'
    session.add(res)
    session.commit()
    return res

Delete

For example, let's delete an article with specified article_id.

def delete_article(article_id):
    try:
        res = session.query(Article).filter(Article.aid == article_id).delete(synchronize_session='fetch')
        session.commit()
    except (NoResultFound):
        session.rollback()
    return res

SQL/Stored Functions

Calling SQL functions such as MySQL's COUNT() or your own stored functions is very easy with SQLAlchemy.

Let's say we want to count the number of rows in the table.

from sqlalchemy.sql import func

def count_rows():
  res = session.query(Article.aid, func.count(Article.aid).label('rows')).all()
  return res

label stands for SQL's AS (for example; SELECT value AS data FROM table. label is the AS data part). Calling your stored functions is precisely the same as calling SQL functions. Assume that we have a stored function named my_func. In this case, the proper SQLAlchemy code would be; func.my_func(arg).

I didn't cover joins and some other more SQLAlchemy features but SQLAlchemy is an endless sea.

Share:Tweet · reddit

blog comments powered by Disqus