SQLAlchemy is an object relational mapper (ORM) for Python. An object relational mapper maps objects (defined by classes) to relational storage (i.e. a relational database management system like SQLite, MySQL or PostgreSQL). There is an excellent tutorial for SQLAlchemy. In Anaconda SQLAlchemy can be installed or updated with the command:

    conda install sqlalchemy

We'll use SQLite as our database so ensure that that is also installed with:

    conda install sqlite

SQLAlchemy broadly consists of three components:

  1. Tools to model the types of objects you want to store

  2. A query layer to express SQL operations in Python-type abstractions

  3. A database engine to connect to relational database management systems

Let's start by importing the module and making a connection to the database, just to check that all is well:


In [1]:
import sqlalchemy
print sqlalchemy.__version__


0.9.9

In [2]:
from sqlalchemy import create_engine
engine = create_engine('sqlite:///:memory:', echo=True)

In [3]:
print engine


Engine(sqlite:///:memory:)

If you saw Engine(sqlite:///:memory:) then you have made a connection to an in-memory SQLite database. The SQLAlchemy documentation has a guide to database URLs that explains how to connect to different types of database, but for now our in-memory DB is good enough.

The next component to explore is the way SQLAlchemy models are built. First, however, consider a SQL database structure, for example one storing a simple address book. It might contains entries for people and addresses. Since more than one person can share an address, a database that stores a many to one relationship between people and addresses can use two tables:

CREATE TABLE addresses (id INTEGER PRIMARY KEY,
                        number INTEGER NOT NULL,
                        street VARCHAR(100) NOT NULL,
                        suburb VARCHAR(100) NOT NULL,
                        postal_code INTEGER NOT NULL);

CREATE TABLE persons (id INTEGER PRIMARY KEY,
                      firstname VARCHAR(40) NOT NULL,
                      surname VARCHAR(60) NOT NULL,
                      address_id INTEGER NOT NULL,
                      FOREIGN KEY(address_id) REFERENCES addresses(id));

These tables naturally map to classes. The rows in a table are each instances, for example:

    ID            First Name              Surname
----------------------------------------------------------------------------
    1             Peter                   van Heusden
    2             Alan                    Christoffels

and the columns are attributes of each instance. So let's create some classes to represent our database structure.

The classes with which SQLAlchemy models a database are all subclasses of a special class called the declarative base. Let's start with the Address class:


In [4]:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Integer, String, Column
Base = declarative_base()

class Address(Base):
    __tablename__ = 'addresses'
    
    id = Column(Integer, primary_key=True)
    number = Column(Integer, nullable=False)
    street = Column(String(100), nullable=False)
    suburb = Column(String(100), nullable=False)
    postal_code = Column(Integer, nullable=False)
    
    def __repr__(self):
        return "<Address(number={}, street='{}', suburb='{}', postal_code={})>".format(
                    self.number, self.street, self.suburb, self.postal_code)

Note a few things here:

  1. There is no __init__ method. That gets inherited from the Base class.

  2. A __repr__ method is provided to provide a string representation of the object (aka. instance). The difference between the use of the __repr__ method and the __str__ method is explained in this StackOverflow answer.

  3. The __tablename__ attribute tells SQLAlchemy what table to store objects of this type in.

Now let's use our new class.


In [5]:
address1 = Address(number=42, street="Robert Sobukwe Road", 
                   suburb="Bellville", postal_code=7535)
print address1
print address1.street
print type(address1.street)
print address1.id


<Address(number=42, street='Robert Sobukwe Road', suburb='Bellville', postal_code=7535)>
Robert Sobukwe Road
<type 'str'>
None

The Address class is a model that describes an address and its attributes in a way that can be mapped to a table, but it is not yet connected to the database. Now we create the other part of our address book, the Person class. This uses some extra functions to create a foreign key relationship between Person and Address.


In [6]:
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship, backref

class Person(Base):
    __tablename__ = 'persons'
    
    id = Column(Integer, primary_key=True)
    firstname = Column(String(40), nullable=False)
    lastname = Column(String(60), nullable=False)
    address_id = Column(Integer, ForeignKey('addresses.id'), 
                        nullable=False)
    address = relationship('Address', 
                           backref=backref('inhabitants', order_by=id))
    
    def __repr__(self):
        return "<Person(firstname='{}', lastname='{}', address='{}')>".format(
                 self.firstname, self.lastname, self.address)

The ForeignKey constraint ensures that a person must be associated with an address before they can be stored in the phone book and the relationship function allows the relationship between the addresses and persons tables is reflected in a relationship between objects of the Address and Person class.


In [15]:
SANBI = Address(number=42, street='Robert Sobukwe Road', 
                suburb='Bellville', postal_code=7535)
person1 = Person(firstname='Peter', lastname='van Heusden', 
                 address=SANBI)
person2 = Person(firstname='Alan', lastname='Christoffels', 
                 address=SANBI)
address2 = Address(number=64, street='Anton Fransch Street', 
                   suburb='Makhaya', postal_code='5335')
person3 = Person(firstname='Titus', lastname='Brown', address=address2)
print "inhabitants of SANBI:", SANBI.inhabitants


inhabitants of SANBI: [<Person(firstname='Peter', lastname='van Heusden', address='<Address(number=42, street='Robert Sobukwe Road', suburb='Bellville', postal_code=7535)>')>, <Person(firstname='Alan', lastname='Christoffels', address='<Address(number=42, street='Robert Sobukwe Road', suburb='Bellville', postal_code=7535)>')>]

None of these classes or objects are connected to database storage yet. To make the connections, first we need to create the tables in the database, using the database engine. To refresh, we need an engine to connect to the database. Then we use this together with our Base class to create the tables we need. With echo=True specified we will get a lot of feedback on what is happening in the database.


In [16]:
engine = create_engine('sqlite:///:memory:', echo=True)
Base.metadata.create_all(engine)


2015-04-01 10:04:16,979 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
INFO:sqlalchemy.engine.base.Engine:SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
2015-04-01 10:04:16,982 INFO sqlalchemy.engine.base.Engine ()
INFO:sqlalchemy.engine.base.Engine:()
2015-04-01 10:04:16,983 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
INFO:sqlalchemy.engine.base.Engine:SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
2015-04-01 10:04:16,985 INFO sqlalchemy.engine.base.Engine ()
INFO:sqlalchemy.engine.base.Engine:()
2015-04-01 10:04:16,987 INFO sqlalchemy.engine.base.Engine PRAGMA table_info("addresses")
INFO:sqlalchemy.engine.base.Engine:PRAGMA table_info("addresses")
2015-04-01 10:04:16,988 INFO sqlalchemy.engine.base.Engine ()
INFO:sqlalchemy.engine.base.Engine:()
2015-04-01 10:04:16,990 INFO sqlalchemy.engine.base.Engine PRAGMA table_info("persons")
INFO:sqlalchemy.engine.base.Engine:PRAGMA table_info("persons")
2015-04-01 10:04:16,991 INFO sqlalchemy.engine.base.Engine ()
INFO:sqlalchemy.engine.base.Engine:()
2015-04-01 10:04:16,993 INFO sqlalchemy.engine.base.Engine 
CREATE TABLE addresses (
	id INTEGER NOT NULL, 
	number INTEGER NOT NULL, 
	street VARCHAR(100) NOT NULL, 
	suburb VARCHAR(100) NOT NULL, 
	postal_code INTEGER NOT NULL, 
	PRIMARY KEY (id)
)


INFO:sqlalchemy.engine.base.Engine:
CREATE TABLE addresses (
	id INTEGER NOT NULL, 
	number INTEGER NOT NULL, 
	street VARCHAR(100) NOT NULL, 
	suburb VARCHAR(100) NOT NULL, 
	postal_code INTEGER NOT NULL, 
	PRIMARY KEY (id)
)


2015-04-01 10:04:16,994 INFO sqlalchemy.engine.base.Engine ()
INFO:sqlalchemy.engine.base.Engine:()
2015-04-01 10:04:16,995 INFO sqlalchemy.engine.base.Engine COMMIT
INFO:sqlalchemy.engine.base.Engine:COMMIT
2015-04-01 10:04:16,997 INFO sqlalchemy.engine.base.Engine 
CREATE TABLE persons (
	id INTEGER NOT NULL, 
	firstname VARCHAR(40) NOT NULL, 
	lastname VARCHAR(60) NOT NULL, 
	address_id INTEGER NOT NULL, 
	PRIMARY KEY (id), 
	FOREIGN KEY(address_id) REFERENCES addresses (id)
)


INFO:sqlalchemy.engine.base.Engine:
CREATE TABLE persons (
	id INTEGER NOT NULL, 
	firstname VARCHAR(40) NOT NULL, 
	lastname VARCHAR(60) NOT NULL, 
	address_id INTEGER NOT NULL, 
	PRIMARY KEY (id), 
	FOREIGN KEY(address_id) REFERENCES addresses (id)
)


2015-04-01 10:04:16,998 INFO sqlalchemy.engine.base.Engine ()
INFO:sqlalchemy.engine.base.Engine:()
2015-04-01 10:04:17,000 INFO sqlalchemy.engine.base.Engine COMMIT
INFO:sqlalchemy.engine.base.Engine:COMMIT

While the engine provides the mechanism for connecting to our database, to interact with the database we use a session. The sessionmaker function takes an engine and creates a new class (in our example called Session). The Session class can then create database sessions on request.


In [17]:
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)

In [18]:
session = Session()
print session


<sqlalchemy.orm.session.Session object at 0x7fa35a6b9390>

Using the our newly created session we can now add objects to the database. This doesn't actually do anything yet, but it sets things up for a future transaction. A transaction is a unit of work in a database management system. For example, a new person and their associated address can be added to the session in a single transaction so that either all or none of the data is added to the database. This ensures that the database moves from one consistent state to another consistent state and is never inconsistent.


In [19]:
session.add(SANBI)
session.add(person1)
session.add(person2)
session.add(person3)
session.add(address2)

The commit() operation commits a transaction to the database. Note that in the generated SQL there is a BEGIN statement, a series of INSERT statements and finally a COMMIT statement. The BEGIN and COMMIT statements denote the beginning and the end of the transaction. Only when you issue the commit() are changes actually made to the database.


In [20]:
session.commit()


2015-04-01 10:10:50,032 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
INFO:sqlalchemy.engine.base.Engine:BEGIN (implicit)
2015-04-01 10:10:50,035 INFO sqlalchemy.engine.base.Engine INSERT INTO addresses (number, street, suburb, postal_code) VALUES (?, ?, ?, ?)
INFO:sqlalchemy.engine.base.Engine:INSERT INTO addresses (number, street, suburb, postal_code) VALUES (?, ?, ?, ?)
2015-04-01 10:10:50,037 INFO sqlalchemy.engine.base.Engine (42, 'Robert Sobukwe Road', 'Bellville', 7535)
INFO:sqlalchemy.engine.base.Engine:(42, 'Robert Sobukwe Road', 'Bellville', 7535)
2015-04-01 10:10:50,038 INFO sqlalchemy.engine.base.Engine INSERT INTO addresses (number, street, suburb, postal_code) VALUES (?, ?, ?, ?)
INFO:sqlalchemy.engine.base.Engine:INSERT INTO addresses (number, street, suburb, postal_code) VALUES (?, ?, ?, ?)
2015-04-01 10:10:50,039 INFO sqlalchemy.engine.base.Engine (64, 'Anton Fransch Street', 'Makhaya', '5335')
INFO:sqlalchemy.engine.base.Engine:(64, 'Anton Fransch Street', 'Makhaya', '5335')
2015-04-01 10:10:50,043 INFO sqlalchemy.engine.base.Engine INSERT INTO persons (firstname, lastname, address_id) VALUES (?, ?, ?)
INFO:sqlalchemy.engine.base.Engine:INSERT INTO persons (firstname, lastname, address_id) VALUES (?, ?, ?)
2015-04-01 10:10:50,046 INFO sqlalchemy.engine.base.Engine ('Peter', 'van Heusden', 1)
INFO:sqlalchemy.engine.base.Engine:('Peter', 'van Heusden', 1)
2015-04-01 10:10:50,049 INFO sqlalchemy.engine.base.Engine INSERT INTO persons (firstname, lastname, address_id) VALUES (?, ?, ?)
INFO:sqlalchemy.engine.base.Engine:INSERT INTO persons (firstname, lastname, address_id) VALUES (?, ?, ?)
2015-04-01 10:10:50,050 INFO sqlalchemy.engine.base.Engine ('Alan', 'Christoffels', 1)
INFO:sqlalchemy.engine.base.Engine:('Alan', 'Christoffels', 1)
2015-04-01 10:10:50,051 INFO sqlalchemy.engine.base.Engine INSERT INTO persons (firstname, lastname, address_id) VALUES (?, ?, ?)
INFO:sqlalchemy.engine.base.Engine:INSERT INTO persons (firstname, lastname, address_id) VALUES (?, ?, ?)
2015-04-01 10:10:50,052 INFO sqlalchemy.engine.base.Engine ('Titus', 'Brown', 2)
INFO:sqlalchemy.engine.base.Engine:('Titus', 'Brown', 2)
2015-04-01 10:10:50,054 INFO sqlalchemy.engine.base.Engine COMMIT
INFO:sqlalchemy.engine.base.Engine:COMMIT

Also note that the id field of the objects is now filled in. This field is computed at INSERT time - it is a so-called autoincrement column in the database.


In [22]:
print "show SANBI id:", SANBI.id


show SANBI id: 1

Below we start a transaction by creating an object in the Address class and add that to our session with session.add(). We decide to abandon the transaction and instead of issuing a commit() we issue a rollback(). This effectively clears the current transaction.


In [23]:
address3 = Address(number='64', street='Owen Wilson Road', 
                   suburb='Bellville', postal_code=7535)

In [24]:
session.add(address3)

In [25]:
session.rollback()


2015-04-01 10:16:11,138 INFO sqlalchemy.engine.base.Engine ROLLBACK
INFO:sqlalchemy.engine.base.Engine:ROLLBACK

The example below creates an invalid Address. It is missing the suburb attribute (recall that this was defined with nullable=False in the class definition). No errors occur until we try to commit() this transaction to the database.


In [26]:
address4 = Address(number=53, street='Albertyn Road',
                   postal_code=7535)

In [27]:
session.add(address4)

In [28]:
session.commit()


2015-04-01 10:17:11,638 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
INFO:sqlalchemy.engine.base.Engine:BEGIN (implicit)
2015-04-01 10:17:11,639 INFO sqlalchemy.engine.base.Engine INSERT INTO addresses (number, street, suburb, postal_code) VALUES (?, ?, ?, ?)
INFO:sqlalchemy.engine.base.Engine:INSERT INTO addresses (number, street, suburb, postal_code) VALUES (?, ?, ?, ?)
2015-04-01 10:17:11,640 INFO sqlalchemy.engine.base.Engine (53, 'Albertyn Road', None, 7535)
INFO:sqlalchemy.engine.base.Engine:(53, 'Albertyn Road', None, 7535)
2015-04-01 10:17:11,641 INFO sqlalchemy.engine.base.Engine ROLLBACK
INFO:sqlalchemy.engine.base.Engine:ROLLBACK
---------------------------------------------------------------------------
IntegrityError                            Traceback (most recent call last)
<ipython-input-28-980f55471f82> in <module>()
----> 1 session.commit()

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/orm/session.pyc in commit(self)
    786                 raise sa_exc.InvalidRequestError("No transaction is begun.")
    787 
--> 788         self.transaction.commit()
    789 
    790     def prepare(self):

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/orm/session.pyc in commit(self)
    382         self._assert_active(prepared_ok=True)
    383         if self._state is not PREPARED:
--> 384             self._prepare_impl()
    385 
    386         if self._parent is None or self.nested:

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/orm/session.pyc in _prepare_impl(self)
    362                 if self.session._is_clean():
    363                     break
--> 364                 self.session.flush()
    365             else:
    366                 raise exc.FlushError(

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/orm/session.pyc in flush(self, objects)
   1983         try:
   1984             self._flushing = True
-> 1985             self._flush(objects)
   1986         finally:
   1987             self._flushing = False

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/orm/session.pyc in _flush(self, objects)
   2101         except:
   2102             with util.safe_reraise():
-> 2103                 transaction.rollback(_capture_exception=True)
   2104 
   2105     def is_modified(self, instance, include_collections=True,

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.pyc in __exit__(self, type_, value, traceback)
     58             exc_type, exc_value, exc_tb = self._exc_info
     59             self._exc_info = None   # remove potential circular references
---> 60             compat.reraise(exc_type, exc_value, exc_tb)
     61         else:
     62             self._exc_info = None   # remove potential circular references

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/orm/session.pyc in _flush(self, objects)
   2065             self._warn_on_events = True
   2066             try:
-> 2067                 flush_context.execute()
   2068             finally:
   2069                 self._warn_on_events = False

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.pyc in execute(self)
    370                     self.dependencies,
    371                     postsort_actions):
--> 372                 rec.execute(self)
    373 
    374     def finalize_flush_changes(self):

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.pyc in execute(self, uow)
    524                              uow.states_for_mapper_hierarchy(
    525                                  self.mapper, False, False),
--> 526                              uow
    527                              )
    528 

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/orm/persistence.pyc in save_obj(base_mapper, states, uowtransaction, single)
     63             _emit_insert_statements(base_mapper, uowtransaction,
     64                                     cached_connections,
---> 65                                     mapper, table, insert)
     66 
     67     _finalize_insert_update_commands(base_mapper, uowtransaction,

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/orm/persistence.pyc in _emit_insert_statements(base_mapper, uowtransaction, cached_connections, mapper, table, insert)
    600                 else:
    601                     result = cached_connections[connection].\
--> 602                         execute(statement, params)
    603 
    604                 primary_key = result.context.inserted_primary_key

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/engine/base.pyc in execute(self, object, *multiparams, **params)
    839                 type(object))
    840         else:
--> 841             return meth(self, multiparams, params)
    842 
    843     def _execute_function(self, func, multiparams, params):

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/sql/elements.pyc in _execute_on_connection(self, connection, multiparams, params)
    320 
    321     def _execute_on_connection(self, connection, multiparams, params):
--> 322         return connection._execute_clauseelement(self, multiparams, params)
    323 
    324     def unique_params(self, *optionaldict, **kwargs):

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/engine/base.pyc in _execute_clauseelement(self, elem, multiparams, params)
    936             compiled_sql,
    937             distilled_params,
--> 938             compiled_sql, distilled_params
    939         )
    940         if self._has_events or self.engine._has_events:

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/engine/base.pyc in _execute_context(self, dialect, constructor, statement, parameters, *args)
   1068                 parameters,
   1069                 cursor,
-> 1070                 context)
   1071 
   1072         if self._has_events or self.engine._has_events:

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/engine/base.pyc in _handle_dbapi_exception(self, e, statement, parameters, cursor, context)
   1269                 util.raise_from_cause(
   1270                     sqlalchemy_exception,
-> 1271                     exc_info
   1272                 )
   1273             else:

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/util/compat.pyc in raise_from_cause(exception, exc_info)
    197             exc_info = sys.exc_info()
    198         exc_type, exc_value, exc_tb = exc_info
--> 199         reraise(type(exception), exception, tb=exc_tb)
    200 
    201 if py3k:

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/engine/base.pyc in _execute_context(self, dialect, constructor, statement, parameters, *args)
   1061                         statement,
   1062                         parameters,
-> 1063                         context)
   1064         except Exception as e:
   1065             self._handle_dbapi_exception(

/home/pvh/anaconda/lib/python2.7/site-packages/sqlalchemy/engine/default.pyc in do_execute(self, cursor, statement, parameters, context)
    440 
    441     def do_execute(self, cursor, statement, parameters, context=None):
--> 442         cursor.execute(statement, parameters)
    443 
    444     def do_execute_no_params(self, cursor, statement, context=None):

IntegrityError: (IntegrityError) NOT NULL constraint failed: addresses.suburb u'INSERT INTO addresses (number, street, suburb, postal_code) VALUES (?, ?, ?, ?)' (53, 'Albertyn Road', None, 7535)

Our session now contains invalid data (it has an IntegrityError). We need to abandon this transaction with session.rollback() before we can use the session object again.


In [30]:
session.rollback()

Querying in SQLAlchemy

The session object is also to query the database. The query is analogous to the SELECT status in SQL and must specify what class of object we want returned (or, as we'll see below, what columns we are interested in). Used by itself query() returns an iterator which we can use in a for loop to loop through our results.


In [33]:
for person in session.query(Person):
    print "PERSON:", person.firstname, person.lastname


2015-04-01 10:21:59,712 INFO sqlalchemy.engine.base.Engine SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons
INFO:sqlalchemy.engine.base.Engine:SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons
2015-04-01 10:21:59,713 INFO sqlalchemy.engine.base.Engine ()
INFO:sqlalchemy.engine.base.Engine:()
PERSON: Peter van Heusden
PERSON: Alan Christoffels
PERSON: Titus Brown

As with SELECT statements, we can use order_by to define the order in which data is returned from our query.


In [35]:
for person in session.query(Person).order_by(Person.lastname):
    print "PERSON:",person.firstname, person.lastname


2015-04-01 10:23:48,338 INFO sqlalchemy.engine.base.Engine SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons ORDER BY persons.lastname
INFO:sqlalchemy.engine.base.Engine:SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons ORDER BY persons.lastname
2015-04-01 10:23:48,340 INFO sqlalchemy.engine.base.Engine ()
INFO:sqlalchemy.engine.base.Engine:()
PERSON: Titus Brown
PERSON: Alan Christoffels
PERSON: Peter van Heusden

Instead of getting an iterator back we can use .all() to get a list of all results.


In [36]:
persons = session.query(Person).all()


2015-04-01 10:24:45,656 INFO sqlalchemy.engine.base.Engine SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons
INFO:sqlalchemy.engine.base.Engine:SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons
2015-04-01 10:24:45,659 INFO sqlalchemy.engine.base.Engine ()
INFO:sqlalchemy.engine.base.Engine:()

In [37]:
print persons


[<Person(firstname='Peter', lastname='van Heusden', address='<Address(number=42, street='Robert Sobukwe Road', suburb='Bellville', postal_code=7535)>')>, <Person(firstname='Alan', lastname='Christoffels', address='<Address(number=42, street='Robert Sobukwe Road', suburb='Bellville', postal_code=7535)>')>, <Person(firstname='Titus', lastname='Brown', address='<Address(number=64, street='Anton Fransch Street', suburb='Makhaya', postal_code=5335)>')>]

And instead of asking for a complete object, we can specify columns we are interested in. The result then becomes a tuple of values (the values in the columns of interest) instead of an object.


In [38]:
for surname in session.query(Person.lastname).order_by(Person.lastname):
    print "LAST NAME:", surname


2015-04-01 10:25:34,848 INFO sqlalchemy.engine.base.Engine SELECT persons.lastname AS persons_lastname 
FROM persons ORDER BY persons.lastname
INFO:sqlalchemy.engine.base.Engine:SELECT persons.lastname AS persons_lastname 
FROM persons ORDER BY persons.lastname
2015-04-01 10:25:34,850 INFO sqlalchemy.engine.base.Engine ()
INFO:sqlalchemy.engine.base.Engine:()
LAST NAME: (u'Brown',)
LAST NAME: (u'Christoffels',)
LAST NAME: (u'van Heusden',)

The filter_by() method allows simple filter operations on query results. In the example below we use an established Address object to limit the list of Person objects returned from the database.


In [49]:
SANBI_people = session.query(Person).filter_by(address=SANBI).all()
for person in SANBI_people:
    print "PERSON:", person.firstname, person.lastname


2015-04-01 10:30:55,521 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
INFO:sqlalchemy.engine.base.Engine:BEGIN (implicit)
2015-04-01 10:30:55,524 INFO sqlalchemy.engine.base.Engine SELECT addresses.id AS addresses_id, addresses.number AS addresses_number, addresses.street AS addresses_street, addresses.suburb AS addresses_suburb, addresses.postal_code AS addresses_postal_code 
FROM addresses 
WHERE addresses.id = ?
INFO:sqlalchemy.engine.base.Engine:SELECT addresses.id AS addresses_id, addresses.number AS addresses_number, addresses.street AS addresses_street, addresses.suburb AS addresses_suburb, addresses.postal_code AS addresses_postal_code 
FROM addresses 
WHERE addresses.id = ?
2015-04-01 10:30:55,525 INFO sqlalchemy.engine.base.Engine (1,)
INFO:sqlalchemy.engine.base.Engine:(1,)
2015-04-01 10:30:55,528 INFO sqlalchemy.engine.base.Engine SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons 
WHERE ? = persons.address_id
INFO:sqlalchemy.engine.base.Engine:SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons 
WHERE ? = persons.address_id
2015-04-01 10:30:55,529 INFO sqlalchemy.engine.base.Engine (1,)
INFO:sqlalchemy.engine.base.Engine:(1,)
PERSON: Peter van Heusden
PERSON: Alan Christoffels

The filter_by() methods can be chained, resulting in an AND logic. The example below also illustrates the use of one(). If a query returns a single value then one() returns the result as a single object (instead of a list). If the query does not result in a single object, however, one() will raise an exception: effectively one() imposes a constract that there must be a single result.


In [50]:
director = session.query(Person).filter_by(address=SANBI
                                           ).filter_by(
                                            lastname='Christoffels').one()


2015-04-01 10:32:39,142 INFO sqlalchemy.engine.base.Engine SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons 
WHERE ? = persons.address_id AND persons.lastname = ?
INFO:sqlalchemy.engine.base.Engine:SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons 
WHERE ? = persons.address_id AND persons.lastname = ?
2015-04-01 10:32:39,144 INFO sqlalchemy.engine.base.Engine (1, 'Christoffels')
INFO:sqlalchemy.engine.base.Engine:(1, 'Christoffels')

The filter() method is more flexible filtering method which effectively allows a WHERE clause to be composed. The argument for filter() is a Boolean expression. For more information, see the SQLAlchemy tutorial.


In [51]:
for person in session.query(Person).filter(
                Person.lastname < "van Heusden"):
    print "PERSON", person.firstname, person.lastname


2015-04-01 10:38:24,451 INFO sqlalchemy.engine.base.Engine SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons 
WHERE persons.lastname < ?
INFO:sqlalchemy.engine.base.Engine:SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons 
WHERE persons.lastname < ?
2015-04-01 10:38:24,452 INFO sqlalchemy.engine.base.Engine ('van Heusden',)
INFO:sqlalchemy.engine.base.Engine:('van Heusden',)
PERSON Alan Christoffels
PERSON Titus Brown

In [60]:
from sqlalchemy import and_

director = session.query(Person).filter(and_(
                        Person.address == SANBI, 
                        Person.lastname == "Christoffels")).one()
print director.firstname


2015-04-01 20:09:10,131 INFO sqlalchemy.engine.base.Engine SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons 
WHERE ? = persons.address_id AND persons.lastname = ?
INFO:sqlalchemy.engine.base.Engine:SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons 
WHERE ? = persons.address_id AND persons.lastname = ?
2015-04-01 20:09:10,410 INFO sqlalchemy.engine.base.Engine (1, 'Christoffels')
INFO:sqlalchemy.engine.base.Engine:(1, 'Christoffels')
Alan

The text() function even allows literal text to be included in the SQL expression.


In [54]:
from sqlalchemy import text
person = session.query(Person).filter(text('id < 2')).all()


2015-04-01 10:42:13,913 INFO sqlalchemy.engine.base.Engine SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons 
WHERE id < 2
INFO:sqlalchemy.engine.base.Engine:SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons 
WHERE id < 2
2015-04-01 10:42:13,914 INFO sqlalchemy.engine.base.Engine ()
INFO:sqlalchemy.engine.base.Engine:()

SQLAlchemy queries can also execute SQL joins. SQL joins are well illustrated on Jeff Atwood's coding horror blog. Thus the following example produces the inner join between the persons and addresses table.


In [57]:
details = session.query(Person.lastname, 
                        Address.suburb).filter(
                          Person.address_id == Address.id).all()
for detail in details:
    print detail


2015-04-01 10:45:01,186 INFO sqlalchemy.engine.base.Engine SELECT persons.lastname AS persons_lastname, addresses.suburb AS addresses_suburb 
FROM persons, addresses 
WHERE persons.address_id = addresses.id
INFO:sqlalchemy.engine.base.Engine:SELECT persons.lastname AS persons_lastname, addresses.suburb AS addresses_suburb 
FROM persons, addresses 
WHERE persons.address_id = addresses.id
2015-04-01 10:45:01,187 INFO sqlalchemy.engine.base.Engine ()
INFO:sqlalchemy.engine.base.Engine:()
(u'van Heusden', u'Bellville')
(u'Christoffels', u'Bellville')
(u'Brown', u'Makhaya')

A final note: relationship definitions in SQLAlchemy models allow SQLAlchemy to executed queries on demand to extract related objects. The different options for when related objects are retrieved from the database are explained in the documentation on relationship loading techniques.


In [59]:
location = session.query(Address).filter(
    Address.street.like('%Sobukwe%')).one()
for person in location.inhabitants:
    print "PERSON:",person.firstname, person.lastname


2015-04-01 10:48:28,797 INFO sqlalchemy.engine.base.Engine SELECT addresses.id AS addresses_id, addresses.number AS addresses_number, addresses.street AS addresses_street, addresses.suburb AS addresses_suburb, addresses.postal_code AS addresses_postal_code 
FROM addresses 
WHERE addresses.street LIKE ?
INFO:sqlalchemy.engine.base.Engine:SELECT addresses.id AS addresses_id, addresses.number AS addresses_number, addresses.street AS addresses_street, addresses.suburb AS addresses_suburb, addresses.postal_code AS addresses_postal_code 
FROM addresses 
WHERE addresses.street LIKE ?
2015-04-01 10:48:28,798 INFO sqlalchemy.engine.base.Engine ('%Sobukwe%',)
INFO:sqlalchemy.engine.base.Engine:('%Sobukwe%',)
2015-04-01 10:48:28,865 INFO sqlalchemy.engine.base.Engine SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons 
WHERE ? = persons.address_id ORDER BY persons.id
INFO:sqlalchemy.engine.base.Engine:SELECT persons.id AS persons_id, persons.firstname AS persons_firstname, persons.lastname AS persons_lastname, persons.address_id AS persons_address_id 
FROM persons 
WHERE ? = persons.address_id ORDER BY persons.id
2015-04-01 10:48:28,866 INFO sqlalchemy.engine.base.Engine (1,)
INFO:sqlalchemy.engine.base.Engine:(1,)
PERSON: Peter van Heusden
PERSON: Alan Christoffels

That's all folks

Enjoy using SQLAlchemy and don't forget to Google.