OceanofPDF - Com Essential SQLAlchemy - Rick Copeland
OceanofPDF - Com Essential SQLAlchemy - Rick Copeland
Rick Copeland
Editor
Mary E. Treseler
Copyright © 2010 Richard Copeland
O’Reilly books may be purchased for educational, business,
or sales promotional use. Online editions are also available
for most titles (http://safari.oreilly.com). For more
information, contact our corporate/institutional sales
department: (800) 998-9938 or [email protected].
Nutshell Handbook, the Nutshell Handbook logo, and the
O’Reilly logo are registered trademarks of O’Reilly Media,
Inc. Essential SQLAlchemy, the image of largescale flying
fish, and related trade dress are trademarks of O’Reilly
Media, Inc.
Many of the designations uses by manufacturers and sellers
to distinguish their products are claimed as trademarks.
Where those designations appear in this book, and O’Reilly
Media, Inc. was aware of a trademark claim, the
designations have been printed in caps or initial caps
While every precaution has been taken in the preparation of
this book, the publisher and author assume no responsibility
for errors or omissions, or for damages resulting from the
use of the information contained herein.
Preface
If you’re an application programmer, you’ve probably run
into a relational database at some point in your professional
career. Whether you’re writing enterprise client-server
applications or building the next killer Web 2.0 application,
you need someplace to put the persistent data for your
application. Relational databases, accessed via SQL, are
some of the most common places to put that data.
SQL is a powerful language for querying and manipulating
data in a database, but sometimes it’s tough to integrate it
with the rest of your application. You may have used some
language that tries to merge SQL syntax into your
application’s programming language, such as Oracle’s
Pro*C/C++ precompiler, or you may have used string
manipulation to generate queries to run over an ODBC
interface. If you’re a Python programmer, you may have used
a DB-API module. But there is a better way.
This book is about a very powerful and flexible Python library
named SQLAlchemy that bridges the gap between relational
databases and traditional object-oriented programming.
While SQLAlchemy allows you to “drop down” into raw SQL
to execute your queries, it encourages higher-level thinking
through a “pythonic” approach to database queries and
updates. It supplies the tools that let you map your
application’s classes and objects onto database tables once
and then to “forget about it,” or to return to your model
again and again to fine-tune performance.
SQLAlchemy is powerful and flexible, but it can also be a
little daunting. SQLAlchemy tutorials expose only a fraction
of what’s available in this excellent library, and though the
online documentation is extensive, it is often better as a
reference than as a way to learn the library initially. This
book is meant as a learning tool and a handy reference for
when you’re in “implementation mode” and need an answer
fast.
This book covers the 0.4 release series of conservatively
versioned SQLAlchemy.
Audience
First of all, this book is intended for those who want to learn
more about how to use relational databases with their Python
programs, or have heard about SQLAlchemy and want more
information on it. Having said that, to get the most out of this
book, the reader should have intermediate-to-advanced
Python skills and at least moderate exposure to SQL
databases. SQLAlchemy provides support for many advanced
SQL constructs, so the experienced DBA will also find plenty
of information here.
The beginning Python or database programmer would
probably be best served by reading a Python book such as
Learning Python by Mark Lutz (O’Reilly) and/or a SQL book
such as Learning SQL by Alan Beaulieu (O’Reilly), either prior
to this book or as a reference to read in parallel with this
book.
Assumptions This Book Makes
This book assumes basic knowledge about Python syntax and
semantics, particularly versions 2.4 and later. In particular,
the reader should be familiar with object-oriented
programming in Python, as a large component of
SQLAlchemy is devoted entirely to supporting this
programming style. The reader should also know basic SQL
syntax and relational theory, as this book assumes familiarity
with the SQL concepts of defining schemas, tables, SELECTs,
INSERTs, UPDATEs, and DELETEs.
Contents of This Book
Chapter 1, Introduction to SQLAlchemy
This chapter takes you on a whirlwind tour through the
main components of SQLAlchemy. It demonstrates
connecting to the database, building up SQL statements,
and mapping simple objects to the database. It also
describes SQLAlchemy’s philosophy of letting tables be
tables and letting classes be classes.
Chapter 2, Getting Started
This chapter walks you through installing SQLAlchemy
using easy_install. It shows you how to create a simple
database using SQLite, and walks though some simple
queries against a sample database to to illustrate the use
of the Engine and the SQL expression language.
Chapter 3, Engines and MetaData
This chapter describes the various engines (methods of
connecting to database servers) available for use with
SQLAlchemy, including the connection parameters they
support. It then describes the MetaData object, which is
where SQLAlchemy stores information about your
database’s schema, and how to manipulate MetaData
objects.
Chapter 4, SQLAlchemy Type Engines
This chapter describes the way that SQLAlchemy uses its
built-in types. It also shows you how to create custom
types to be used in your schema. You will learn the
requirements for creating custom types as well as the
cases where it is useful to use custom rather than built-in
types.
Chapter 5, Running Queries and Updates
This chapter tells you how to perform INSERTs,
UPDATEs, and DELETEs. It covers result set objects,
retrieving partial results, and using SQL functions to
aggregate and sort data in the database server.
Chapter 6, Building an Object Mapper
This chapter describes the object-relational mapper
(ORM) used in SQLAlchemy. It describes the differences
between the object mapper pattern (used in SQLAlchemy)
and the active record pattern used in other ORMs. It then
describes how to set up a mapper, and how the mapper
maps your tables by default. You will also learn how to
override the default mapping and how to specify various
relationships between tables.
Chapter 7, Querying and Updating at the ORM Level
This chapter shows you how to create objects, save them
to a session, and flush them to the database. You will
learn about how Session and Query objects are defined,
their methods, and how to use them to insert, update,
retrieve, and delete data from the database at the ORM
level. You will learn how to use result set mapping to
populate objects from a non-ORM query and when it
should be used.
Chapter 8, Inheritance Mapping
This chapter describes how to use SQLAlchemy to model
object-oriented inheritance. The various ways of modeling
inheritance in the relational model are described, as well
as the support SQLAlchemy provides for each.
Chapter 9, Elixir: A Declarative Extension to SQLAlchemy
This chapter describes the Elixir extension to
SQLAlchemy, which provides a declarative, active record
pattern for use with SQLAlchemy. You will learn how to
use Elixir extensions such as acts_as_versioned to
create auxiliary tables automatically, and when Elixir is
appropriate instead of “bare” SQLAlchemy.
Chapter 10, SqlSoup: An Automatic Mapper for SQLAlchemy
This chapter introduces the SQLSoup extension, which
provides an automatic metadata and object model based
on database reflection. You will learn how to use SQLSoup
to query the database with a minimum of setup, and learn
the pros and cons of such an approach.
Chapter 11, Other SQLAlchemy Extensions
This chapter covers other, less comprehensive extensions
to SQLAlchemy. It describes the extensions that are
currently used in the 0.4 release series of SQLAlchemy, as
well as briefly describing deprecated extensions and the
functionality in SQLAlchemy that supplants them.
Conventions Used in This Book
The following typographical conventions are used in this
book:
Italic
Indicates new terms, URLs, email addresses, filenames,
file extensions, pathnames, directories, and Unix utilities.
Constant width
Indicates commands, options, switches, variables,
attributes, keys, functions, types, classes, namespaces,
methods, modules, properties, parameters, values,
objects, events, event handlers, the contents of files, or
the output from commands.
Constant width italic
Note
Warning
In most cases, this code will work. For instance, with the
user_name and password variables just shown, the SQL
that would be executed is INSERT INTO user(user_name,
password) VALUES ('rick', 'parrot'). A user could,
however, supply a maliciously crafted password: parrot');
DELETE FROM user;--. In this case, the SQL executed is
INSERT INTO user(user_name, password) VALUES
('rick', 'parrot'); DELETE FROM user; --', which
would probably delete all users from your database. The
use of bind parameters (as in the first example in the text)
is an effective defense against SQL injection, but as long as
you are manipulating strings directly, there is always the
possibility of introducting a SQL injection vulnerability into
your code.
class Group(object):
users=[]
permissions=[]
class Permission(object):
groups=[]
Suppose we wanted to print out a summary of all of a given
user’s groups and permissions, something an object-oriented
style would do quite well. We might write something like the
following:
print 'Summary for %s' % user.user_name
for g in user.groups:
print ' Member of group %s' % g.group_name
for p in g.permissions:
print ' ... which has permission %s' % p.permission_name
SQLAlchemy Philosophy
SQL databases behave less and less like object collections the more
size and performance start to matter; object collections behave less
and less like tables and rows the more abstraction starts to matter.
SQLAlchemy aims to accommodate both of these principles.
--From http://www.sqlalchemy.org
class Group(object):
def __init__(self, group_name=None, users=None, permissions=None):
if users is None: users = []
if permissions is None: permissions = []
self.group_name = group_name
self._users = users
self._permissions = permissions
class Permission(object):
group_table = Table(
'tf_group', metadata,
Column('id', Integer, primary_key=True),
Column('group_name', Unicode(16), unique=True, nullable=False))
permission_table = Table(
'tf_permission', metadata,
Column('id', Integer, primary_key=True),
Column('permission_name', Unicode(16), unique=True,
nullable=False))
user_group = Table(
'user_group', metadata,
Column('user_id', None, ForeignKey('tf_user.id'),
primary_key=True),
Column('group_id', None, ForeignKey('tf_group.id'),
primary_key=True))
group_permission = Table(
'group_permission', metadata,
Column('group_id', None, ForeignKey('tf_group.id'),
primary_key=True),
Column('permission_id', None, ForeignKey('tf_permission.id'),
primary_key=True))
c
This attribute contains a collection of the columns in the
table being mapped. This is useful when constructing
SQL queries based on the mapped class, such as
referring to User.c.user_name.
_state
SQLAlchemy uses this property to track whether a
mapped object is “clean” (freshly fetched from the
databaes), “dirty” (modified since fetching from the
database), or “new” (as-yet unsaved to the database).
This property generally should not be modified by the
application programmer.
mapped properties
Engine
The beginning of any SQLAlchemy application is the Engine.
The engine manages the SQLAlchemy connection pool and
the database-independent SQL dialect layer. In our previous
examples, the engine was created implicitly when the
MetaData was created:
metadata=MetaData('sqlite://')
engine = metadata.bind
Connection pooling
MetaData Management
The MetaData object in SQLAlchemy is used to collect and
organize information about your table layout (i.e., your
database schema). We alluded to MetaData management
before in describing how to create tables. A MetaData object
must be created before any tables are defined, and each
table must be associated with a MetaData object. MetaData
objects can be created “bound” or “unbound,” based on
whether they are associated with an engine. The following is
an example of the different ways you can create MetaData
objects:
# create an unbound MetaData
unbound_meta = MetaData()
group_table = Table(
'tf_group', meta,
Column('id', Integer, primary_key=True),
Column('group_name', Unicode(16), unique=True, nullable=False))
# Use the engine parameter to load tables from the first engine
user_table = Table(
'tf_user', meta, autoload=True, autoload_with=engine1)
group_table = Table(
'tf_group', meta, autoload=True, autoload_with=engine1)
permission_table = Table(
'tf_permission', meta, autoload=True, autoload_with=engine1)
user_group_table = Table(
'user_group', meta, autoload=True, autoload_with=engine1)
group_permission_table = Table(
'group_permission', meta, autoload=True, autoload_with=engine1)
Types System
In many cases, SQLAlchemy can map SQL types to Python
types in a straightforward way. To do this, SQLAlchemy
provides a set of TypeEngine-derived classes that convert
SQL data to Python data in the sqlalchemy.types module.
TypeEngine subclasses are used to define the MetaData for
tables.
Sometimes, in keeping with the SQLAlchemy philosophy of
letting your objects be objects, you may find that the
provided TypeEngine classes do not express all of the data
types you wish to store in your database. In this case, you
can write a custom TypeEngine that converts data being
saved to the database to a database-native type, and converts
data being loaded from the database to a Python native type.
Suppose, for instance, that we wished to have a column that
stored images from the Python Imaging Library (PIL). In this
case, we might use the following TypeEngine definition:
class ImageType(sqlalchemy.types.Binary):
Notice how the SQL generated uses a question mark for the
user name value. This is known as a “bind parameter.” When
the query is run, SQLAlchemy will send the query string
(with bind parameters) and the actual variables (in this case,
the string "rick") to the database engine. Using the
SQLAlchemy SQL-generation layer has several advantages
over hand-generating SQL strings:
Security
Application data (including user-generated data) is safely
escaped via bind parameters, making SQL injection-style
attacks extremely difficult.
Performance
The likelihood of reusing a particular query string (from
the database server’s perspective) is increased. For
instance, if we wanted to select another user from the
table, the SQL generated would be identical, and a
different bind parameter would be sent. This allows the
database server in some cases to reuse its execution plan
from the first query for the second, increasing
performance.
Portability
Although SQL is a standardized language, different
database servers implement different parts of the
standard, and to different degrees of faithfulness.
SQLAlchemy provides you a way to write database-
independent SQL in Python without tying you to a
particular database server. With a little bit of planning,
the same SQLAlchemy-based application can run on
SQLite, Oracle, DB2, PostgreSQL, or any other
SQLAlchemy-supported database without code changes.
You can also use SQL functions in your queries by using the
SQLAlchemy-supplied func object:
q=select([Permission.c.permission_name,
func.count(user_group.c.user_id)],
and_(Permission.c.id==group_permission.c.permission_id,
Group.c.id==group_permission.c.group_id,
Group.c.id==user_group.c.group_id),
group_by=[Permission.c.permission_name],
distinct=True)
group_table = Table(
'tf_group', metadata,
Column('id', Integer, primary_key=True),
Column('group_name', Unicode(16), unique=True, nullable=False))
user_group = Table(
'user_group', metadata,
Column('user_id', None, ForeignKey('tf_user.id'),
primary_key=True),
Column('group_id', None, ForeignKey('tf_group.id'),
... primary_key=True))
mapper(User, user_table)
mapper(Group, group_table)
def _get_password(self):
return self._password
def _set_password(self, value):
self._password = sha.new(value).hexdigest()
password=property(_get_password, _set_password)
Installing SQLAlchemy
In order to use SQLAlchemy, you need to install the
SQLAlchemy package as well as a Python database driver for
your database. This section will guide you through installing
both.
Note
Note
Note
PostgreSQL
psycopg2 at
http://www.initd.org/pub/software/psycopg/
SQLite
pysqlite at
http://initd.org/pub/software/pysqlite/ or sqlite3
(included with Python versions 2.5 and greater)
MySQL
MySQLdb athttp://sourceforge.net/projects/mysql-
python
Oracle
cx_Oracle athttp://www.cxtools.net/
SQL Server
Support for Microsoft SQL server is provided by multiple
drivers as follows:
pyodbc at http://pyodbc.sourceforge.net/
(recommended driver)
adodbapi at http://adodbapi.sourceforge.net/
pymssql at http://pymssql.sourceforge.net/
Firebird
kinterbasdb athttp://kinterbasdb.sourceforge.net/
Informix
informixdb athttp://informixdb.sourceforge.net/
SQLAlchemy Tutorial
Once you have installed SQLAlchemy and the SQLite driver
(either pysqlite or sqlite3), you can start really exploring
SQLAlchemy. This tutorial shows off some of the basic
features of SQLAlchemy that you can use to become
immediately productive. This tutorial is based on a stripped-
down version of a user authentication module that might be
used in a web application.
permission_table = Table(
'tf_permission', metadata,
Column('id', Integer, primary_key=True),
Column('permission_name', Unicode(16),
unique=True, nullable=False))
group_permission_table = Table(
'tf_group_permission', metadata,
Column('permission_id', None, ForeignKey('tf_permission.id'),
primary_key=True),
Column('group_id', None, ForeignKey('tf_group.id'),
primary_key=True))
We can also retrieve values from each row of the result using
dict-like indexing or simple attribute lookup as follows:
>>> result = stmt.execute()
>>> row =result.fetchone()
>>> row['user_name']
u'rick'
>>> row.password
u'secret1'
>>> row.created
datetime.datetime(2007, 9, 7, 10, 6, 4, 415754)
>>> row.items()
[(u'id', 1), (u'user_name', u'rick'), (u'password', u'secret1'),
... (u'display_name', u'Rick Copeland'),
... (u'created', datetime.datetime(2007, 9, 7, 10, 6, 4, 415754))]
mapper(User, user_table)
mapper(Group, group_table)
mapper(Permission, permission_table)
Due to the UOW pattern, the new user has not yet been
saved to the database. If we try to count the users using the
user_table, we still get 3:
>>> len(list(user_table.select().execute()))
3
The loggers used with SQLAlchemy are listed next. Note that
several of these loggers deal with material covered in later
chapters (in particular, the sqlalchemy.orm.* loggers):
__iter__ ()
Allows iteration over a result proxy, generating RowProxy
objects
fetchone ()
Fetches the next RowProxy object from the ResultProxy
fetchall ()
Fetches all RowProxy objects at once
scalar ()
Fetches the next row from the cursor and treat it as a
scalar (i.e., not a RowProxy)
keys
List of the column names in the result set
rowcount
The total number of rows in the result set
close ()
Closes the ResultProxy, possibly returning the underlying
Connection to the pool
Connection Pooling
SQLAlchemy provides the connection pool as an easy and
efficient way to manage connections through the database.
Normally, you don’t need to worry about the connection pool
because it is automatically managed by the Engine class. The
connection pool can, however, be used on its own to manage
regular DB-API connections. If you wish to manage such a
pool, you could do the following:
from sqlalchemy import pool
import psycopg2
psycopg = pool.manage(psycopg2)
connection = psycopg.connect(database='mydb',
username='rick', password='foo')
def getconn_pg():
c = psycopg2.connect(database='mydb', username='rick',
password='foo')
return c
def getconn_sl():
c = sqlite.connect(filename='devdata.sqlite')
return c
AssertionPool
Allows only one connection to be checked out at a time
and raises an AssertionError when this constraint is
violated.
NullPool
Does no pooling; instead, actually opens and closes the
underlying DB-API connection on each checkout/checkin
of a connection.
QueuePool
Maintains a fixed-size connection pool. This is the default
connection pool class used for nonsqlite connections.
SingletonThreadPool
Maintains a single connection per thread. It is used with
sqlite because this database driver does not handle using
a single connection in multiple threads well.
StaticPool
Maintains a single connection that is returned for all
connection requests.
MetaData
SQLAlchemy provides the MetaData class, which collects
objects that describe tables, indexes, and other schema-level
objects. Before using any of the higher-level features of
SQLAlchemy, such as the SQL query language and the ORM,
the schema of the database must be described using
metadata. In some cases, you can reflect the structure of
schema items into the MetaData from the database. In this
case, you need only specify the name of the entity, and its
structure will be loaded from the database directly.
Defining Tables
The most common use of the MetaData object is in defining
the tables in your schema. To define tables in the MetaData,
you use the Table and Column classes as shown in the
following example:
from sqlalchemy import *
from datetime import datetime
metadata=MetaData()
user_table = Table(
'tf_user', metadata,
Column('id', Integer, primary_key=True),
Column('user_name', Unicode(16), unique=True, nullable=False),
Column('email_address', Unicode(255), unique=True, nullable=False),
Column('password', Unicode(40), nullable=False),
Column('first_name', Unicode(255), default=''),
Column('last_name', Unicode(255), default=''),
Column('created', DateTime, default=datetime.now))
product_table = Table(
'product', metadata,
Column('brand_id', Integer, ForeignKey('brand.id'),
... primary_key=True),
Column('sku', Unicode(80), primary_key=True))
style_table = Table(
'style', metadata,
Column('brand_id', Integer, primary_key=True),
Column('sku', Unicode(80), primary_key=True),
Column('code', Unicode(80), primary_key=True),
ForeignKeyConstraint(['brand_id', 'sku'],
['product.brand_id',
'product.sku']))
name
Table reflection
Column Definitions
The Column constructor Column.__init__(self, name, type_,
*args, **kwargs) takes the following arguments:
name
If set to False, this does not allow None as a value for the
column. In **kwargs, the default is True, unless the
column is a primary key.
default
Constraints
SQLAlchemy also supports a variety of constraints, both at
the column level and at the table level. All constraints are
derived from the Constraint class, and take an optional name
parameter.
Note
Primary keys
product_table = Table(
'product', metadata,
Column('brand_id', Integer, ForeignKey('brand.id'),
... primary_key=True),
Column('sku', Unicode(80), primary_key=True))
Foreign keys
UNIQUE constraints
CHECK constraints
CheckConstraints can also be specified, either at the
column level (in which case they should only refer to the
column on which they are defined), or at the Table level (in
which case they should refer only to any column in the table).
CheckConstraints are specified with a text constraint that
will be passed directly through to the underlying database
implementation, so care should be taken if you want to
maintain database independence in the presence of
CheckConstraints. MySQL and SQLite, in particular, do not
actively support such constraints.
For instance, if you wanted to validate that payments were
always positive amounts, you might create a payment table
similar to the following:
payment_table = Table(
'payment', metadata,
Column('amount', Numeric(10,2), CheckConstraint('amount > 0')))
Defaults
SQLAlchemy provides several methods of generating default
values for columns when inserting and updating rows. These
default values fall into one of two categories: active defaults or
passive defaults.
Active defaults
Passive defaults
user_table = Table(
'tf_user', MetaData(),
Column('id', Integer, primary_key=True),
Column('user_name', Unicode(16), unique=True, nullable=False),
Column('password', Unicode(40), nullable=False),
Column('first_name', Unicode(255), default=''),
Column('last_name', Unicode(255), default=''),
Column('created_apptime', DateTime, default=datetime.now),
Column('created_dbtime', DateTime, PassiveDefault('sysdate')),
Column('modified', DateTime, onupdate=datetime.now))
Caution
Defining Indexes
Once your database grows to a certain size, you will probably
need to consider adding indexes to your tables to speed up
certain selects. The easiest way to index a column is to
simply specify index=True when defining the Column:
user_table = Table(
'tf_user', MetaData(),
Column('id', Integer, primary_key=True),
Column('user_name', Unicode(16), unique=True, nullable=False,
... index=True),
Column('password', Unicode(40), nullable=False),
Column('first_name', Unicode(255), default=''),
Column('last_name', Unicode(255), default='', index=True))
Uses the sequence when updating the row, not just when
inserting. The default is False.
MetaData Operations
SQLAlchemy uses the MetaData object internally for several
purposes, particularly inside the object relational mapper
(ORM), which is covered in Chapter 6. MetaData can also be
used in connection with Engine and other Connectable
instances to create or drop tables, indexes, and sequences
from the database.
Binding MetaData
As mentioned previously, MetaData can be bound to a
database Engine. This is done in one of three ways:
metadata = MetaData('sqlite://')
user_table = Table(
'tf_user', metadata,
Column('id', Integer, primary_key=True),
Column('user_name', Unicode(16), unique=True, nullable=False,
... index=True),
Column('password', Unicode(40), nullable=False),
Column('first_name', Unicode(255), default=''),
Column('last_name', Unicode(255), default='', index=True))
Note
Generic Types
The generic TypeEngines provided by SQLAlchemy are found
in the sqlalchemy.types package. These TypeEngines cover
a fairly complete set of portable column types. The
TypeEngines supported, their corresponding Python type,
and their SQL representation, are listed in Table 4-1. Note
that there are several TypeEngines defined in all caps (such
as CLOB). These are derived from other TypeEngines and may
or may not be further specialized to allow finer-grained
specification of the underlying database type.
Table 4-1. Built-in generic TypeEngines
SQL type (for SQLite
Class name Python type Arguments
driver)
(default is
length
String string TEXT or VARCHAR
unbounded)
Integer int INTEGER none
precision=10 ,
Numeric float, Decimal NUMERIC
length=2
Float(Numeric) float NUMERIC precision=10
(default is
length
Binary byte string BLOB
unbounded)
Boolean bool BOOLEAN none
(default is
length
Unicode unicode TEXT or VARCHAR
unbounded)
any object that can
PickleType BLOB none
be pickled
precision=10
FLOAT(Numeric) float, Decimal NUMERIC ,length=2
(default is
length
TEXT(String) string TEXT
unbounded)
DECIMAL(Numeric) float, Decimal NUMERIC precision=10,length=2
INT,
INTEGER(Integer)
int INTEGER none
(default is
length
CLOB(String) string TEXT
unbounded)
(default is
length
VARCHAR(String) string VARCHAR or TEXT
unbounded)
(default is
length
CHAR(String) string CHAR or TEXT
unbounded)
VARCHAR, (default is
length
NCHAR(Unicode) string
NCHAR, or TEXT unbounded)
(default is
length
BLOB(Binary) byte string BLOB
unbounded)
BOOLEAN(Boolean) bool BOOLEAN none
Dialect-Specific Types
To generate appropriate dialect-specific SQL CREATE
TABLE statements from these generic types, SQLAlchemy
compiles those generic TypeEngines into dialect-specific
TypeEngines. In some cases, in addition to implementing the
generic types, a dialect may provide dialect-specific types
(such as IP address, etc.).
Some of the dialect-specific types don’t actually provide any
special support for converting between database values and
Python values; these are generally used for completeness,
particularly when reflecting tables. In this case, no
conversion is done between the value supplied by the DB-API
implementation and the application. This behavior is
indicated in the following tables by listing “none” as the
Python type for that TypeEngine. Tables 4-2 through 4-5 list
some of the types provided by particular database engines
that are not automatically used by SQLAlchemy.
Table 4-2. MS SQL server types
Class name Python type SQL type Arguments
MSMoney none MONEY none
MSSmallMoney none SMALLMONEY none
AdoMSNVarchar unicode NVARCHAR length
MSBigInteger int BIGINT none
MSTinyInteger int TINYINT none
MSVariant none SQL_VARIANT none
MSUniqueIdentifier none UNIQUEIDENTIFIER none
Implementing a TypeDecorator
To implement a TypeDecorator, you must provide the base
TypeEngine you are “implementing” as well as two functions,
convert_bind_param() and convert_result_value().
convert_bind_param( self, value, engine) is used to convert
Python values to SQL values suitable for the DB-API driver,
and convert_result_value( self, value, engine) is used to
convert SQL values from the DB-API driver back into Python
values. The implemented TypeEngine is specified in the impl
attribute on the TypeDecorator.
For instance, if you wish to implement a type for validating
that a particular Integer column contains only the values 0,
1, 2, and 3 (e.g., to implement an enumerated type in a
database that does not support enumerated types), you
would implement the following TypeDecorator:
from sqlalchemy import types
class MyCustomEnum(types.TypeDecorator):
impl=types.Integer
Performance-Conscious TypeDecorators
SQLAlchemy has a second, undocumented (at the time of
this book’s writing) interface for providing bind parameter
and result value conversion. If you provide a
bind_processor() or result_processor() method in your
TypeDecorator, then these will be used instead of the
convert_bind_param() and convert_result_value()
methods. The new “processor” interface methods take a
database dialect as a parameter and return a conversion
function (a “processor”) that takes a single value parameter
and returns the (possibly converted) value. If no processing
is necessary, you can simply return None rather than a new
processor:
>>> from sqlalchemy import types
>>> import sqlalchemy.databases.sqlite as sqlite
>>>
>>> class MyCustomEnum(types.TypeDecorator):
... impl = types.Integer
... def __init__(self, enum_values, *l, **kw):
... types.TypeDecorator.__init__(self, *l, **kw)
... self._enum_values = enum_values
... def bind_processor(self, dialect):
... impl_processor = self.impl.bind_processor(dialect)
... if impl_processor:
... def processor(value):
... result = impl_processor(value)
... assert value in self._enum_values, \
... "Value %s must be one of %s" % (result,
... self._enum_values)
... return result
... else:
... def processor(value):
... assert value in self._enum_values, \
... "Value %s must be one of %s" % (value,
... self._enum_values)
... return value
... return processor
...
>>> mce=MyCustomEnum([1,2,3])
>>> processor = mce.bind_processor(sqlite.dialect())
>>> print processor(1)
1
>>> print processor(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 17, in processor
AssertionError: Value 5 must be one of [1, 2, 3]
def get_col_spec(self):
return 'NEWTYPE(%s)' % ','.join(self._args)
Insert Statements
The Insert construct is perhaps the simplest. In order to
create an Insert statement, you can use the Table.insert()
method or the insert() function. (The method is actually
just a wrapper for the function.) The insert takes two
arguments: the table into which a row is being inserted, and
an optional dictionary of values to be inserted. Each key in
the dictionary represents a column and may be either the
metadata Column object or its string identifier. The values
provided can be one of the following:
A literal Python value to be inserted.
An SQL expression to be inserted, such as
func.current_timestamp( ), which will create the
SQL INSERT INTO simple2 (col1, col2) VALUES (?,
current_timestamp).
A Select statement (covered later in this chapter). In
this case, the value to be inserted is provided by a
subquery.
Update Statements
Update statements are similar to inserts, except that they
can specify a “where” clause that indicates which rows to
update. Like insert statements, update statements can be
created by either the update() function or the update()
method on the table being updated. The only parameters to
the update() function are the table being updated (omitted if
using the update() method), the where clause, and the
values to be set.
The where clause of the update() query can be a SQL clause
object (covered later in this chapter) or a text string
specifying the update condition. In order to update every row
of a table, you can simply leave off the where clause. To
update this simple table, we can execute the following
statement:
>>> stmt = simple_table.update(
... whereclause=text("col1='First value'"),
... values=dict(col1='1st Value'))
>>> stmt.execute()
<sqlalchemy.engine.base.ResultProxy object at 0xc77910>
>>> stmt = simple_table.update(text("col1='Second value'"))
>>> stmt.execute(col1='2nd Value')
...
<sqlalchemy.engine.base.ResultProxy object at 0xc77950>
>>> stmt = simple_table.update(text("col1='Third value'"))
>>> print stmt
UPDATE simple SET id=?, col1=? WHERE col1='Third value'
...
>>> engine.echo = True
>>> stmt.execute(col1='3rd value')
2007-09-25 08:57:11,253 INFO sqlalchemy.engine.base.Engine.0x..d0
... UPDATE simple SET col1=? WHERE col1='Third value'
2007-09-25 08:57:11,254 INFO sqlalchemy.engine.base.Engine.0x..d0
... ['3rd value']
2007-09-25 08:57:11,255 INFO sqlalchemy.engine.base.Engine.0x..d0
... COMMIT
<sqlalchemy.engine.base.ResultProxy object at 0xc77990>
Delete Statements
The Delete construct is used to delete data from the
database. To create a Delete construct, you can use either
the delete() function or the delete() method on the table
from which you are deleting data. Unlike insert() and
update(), delete() takes no values parameter, only an
optional where clause (omitting the where clause will delete
all rows from the table). To delete all rows from the
product_price table for sku 123, in the previous section, for
instance, we would execute the code as shown here:
>>> stmt = product_price_table.delete(
... text("sku='123'"))
>>> stmt.execute()
2007-09-27 19:22:51,612 INFO sqlalchemy.engine.base.Engine.0x..d0
... DELETE FROM product_price WHERE sku='123'
2007-09-27 19:22:51,612 INFO sqlalchemy.engine.base.Engine.0x..d0
... []
2007-09-27 19:22:51,613 INFO sqlalchemy.engine.base.Engine.0x..d0
... COMMIT
<sqlalchemy.engine.base.ResultProxy object at 0xd92510>
Queries
The real power of the SQL expression language is in its
query interface. This includes the actual queries (SQL
“SELECT” statements) as well as the syntax for specifying
“WHERE” clauses (which may be used in UPDATEs and
DELETEs, as well).
The goal of the SQL expression language, like the goal of
SQLAlchemy in general, is to provide functionality that
doesn’t “get in your way” when you need to be more specific
about the SQL you need. In that vein, you can always use the
Text construct (used previously in the UPDATE and DELETE
examples) to specify the exact SQL text you would like to
use. For most operations, however, the SQL expression
language makes for a succinct, secure, and less error-prone
way of expressing your queries.
columns=None
fetchone ()
Fetch one result from the cursor.
fetchmany (size=None)
Fetch several results from the cursor (if size is omitted,
fetch all results).
fetchall ()
Fetch all results from the cursor.
__iter__ ()
Return an iterator through the result set.
close ()
Close the ResultProxy, as well as the underlying cursor.
This method is called automatically when all result rows
are exhausted.
scalar ()
Fetch the first column of the first row, and close the result
set (useful for queries such as “SELECT
DATETIME('NOW')”).
rowcount (valid only for DML statements)
Return the number of rows updated, deleted, or inserted
by the statement.
between(cleft, cright)
Produces a BETWEEN clause like column BETWEEN cleft
AND cright.
distinct ()
Adds a DISTINCT modifier like DISTINCT column.
startswith(other)
Produces the clause column LIKE 'other%'.
endswith (other)
Produces the clause column LIKE '%other‘.
in_ (*other)
Produces an IN clause like column IN (other[0], other[1], ...).
other can also be a subquery.
like (other)
Produces a LIKE clause like column LIKE other.
op (operator)
Produces an arbitrary operator like column operator.
label (name)
Produces an AS construct for the column (a column alias)
like column AS name.
where(whereclause)
Add a constraint to the WHERE clause. All constraints
added this way will be AND-ed together to create the
whole WHERE clause.
order_by(*clauses)
Generate an ORDER BY clause (or append the given
clauses to an existing ORDER BY clause).
group_by(*clauses)
Generate a GROUP BY clause (or append the given
clauses to an existing GROUP BY clause).
having(having)
Generate a HAVING clause (or add to an existing HAVING
clause). Like where(), the final statement’s HAVING
clause will be all of the clauses added via this function,
AND-ed together.
select_from(fromclause)
Generate a FROM clause or append to the existing one.
limit(limit)
Equivalent to the limit parameter in the select()
function or method.
offset(offset)
Equivalent to the offset parameter in the select()
function or method.
column(column)
Add a column to the list of columns being selected.
distinct ()
Equivalent to passing distinct=True to the select()
function or method.
count(whereclause=None, **params)
Generate a statement that will count the rows that would
be returned from the query, optionally with a whereclause
and additional params to be passed to the generated
SELECT COUNT(...) statement.
apply_labels ()
Equivalent to use_labels=True in the select()
function/method.
prefix_with(clause)
Append a prefix to the generated SQL. (A prefix is
inserted immediately after the SELECT keyword, as in the
prefixes parameter to select().)
replace_selectable(old, alias)
Replace every occurrence of old with the alias alias.
(Aliasing is covered in more detail in later in this chapter,
Using aliases”). This can be useful when it is necessary to
modify a query to use an alias when that query was
originally written to use a reference to the actual table,
for instance.
union(other, **kwargs)
Return an UNION with this selectable and another
(covered in more detail later under Joins and Set
Operations”).
union_all(other, **kwargs)
Return an UNION ALL with this selectable and another
(covered in more detail later under Joins and Set
Operations”).
intersect(other, **kwargs)
Return an INTERSECT with this selectable and another
(covered in more detail later under Joins and Set
Operations”).
intersect_all(other, **kwargs)
Return an INTERSECT ALL with this selectable and
another (covered in more detail under Joins and Set
Operations”).
except_(other, **kwargs)
Return an EXCEPT with this selectable and another
(covered in more detail under Joins and Set Operations”).
except_all(other, **kwargs)
Return an EXCEPT ALL with this selectable and another
(covered in more detail under Joins and Set Operations”).
join(right, *args, **kwargs)
Return a INNER JOIN between this selectable and
another (covered in more detail under Joins and Set
Operations”).
outerjoin(right, *args, **kwargs)
Return a LEFT OUTER JOIN between this selectable and
another (covered in more detail under Joins and Set
Operations”).
as_scalar ()
Allows the query to be embedded in a column list of an
enclosing query.
label(name)
Label the result of this query with name for use in the
column list of an enclosing query. Also implies
as_scalar().
correlate(fromclause)
Specify a table on which to correlate, or use None to
disable SQLAlchemy’s auto-correlation on embedded
subqueries.
select(whereclauses, **params)
Generate an enclosing SELECT statment that selects all
columns of this select.
Joining selectables
To join two selectables (in tables or other select statements)
together, SQLAlchemy provides the join() (implementing
INNER JOIN) and outerjoin() (implementing OUTER JOIN)
functions, as well as join() and outerjoin() methods on all
selectables. The only difference between the *join()
methods and the *join() functions is that the methods
implicitly use self as the lefthand side of the join.
If you are familiar with the JOIN constructs in SQL, then you
are used to specifyingthe ON clause of the JOIN. For
instance, to select all stores where the price of a product is
different than its MSRP, you might write the following SQL:
SELECT store.name
FROM store
JOIN product_price ON store.id=product_price.store_id
JOIN product ON product_price.sku=product.sku
WHERE product.msrp != product_price.price;
Notice how we had to specify the join criteria for each of the
joins in the statement. Wouldn’t it be nice if the database
could infer the ON clauses based on the foreign key
constraints? Well, SQLAlchemy does this automatically:
>>> from_obj = store_table.join(product_price_table)
... .join(product_table)
>>> query = store_table.select()
>>> query = query.select_from(from_obj)
>>> query = query.where(product_table.c.msrp
... != product_price_table.c.price)
>>> print query
SELECT store.id, store.name
FROM store JOIN product_price ON store.id = product_price.store_id
... JOIN product ON product.sku = product_price.sku
WHERE product.msrp != product_price.price
Using aliases
Subqueries
SQLAlchemy provides rich support for subqueries (using a
query inside another query). We have already seen one type
of subquery in the use of the join and in set operation
support. SQLAlchemy also allows subqueries to appear in the
column list of a select statement, in the right hand side of the
SQL IN operator (using the SQLAlchemy-provided in_()
method on ClauseElements), and as an argument to the
from_obj parameter on the select() function.
Introduction to ORMs
ORMs provide methods of updating the database by using
your application objects. For instance, to update a column in
a mapped table in SQLAlchemy, you merely have to update
the object, and SQLAlchemy will take care of making sure
that the change is reflected in the database. ORMs also allow
you to construct application objects based on database
queries. Chapter 7 will focus on how to use SQLAlchemy’s
ORM to update and query objects in the database.
Warning
This table links the product table with the category table.
A product should generally have one category per level.
This table lists the retail price for each product at each
store location.
def __repr__(self):
return '<Level %s>' % self.name
class Category(object):
def __repr__(self):
return '<Category %s.%s>' % (self.level.name, self.name)
class Product(object):
def __repr__(self):
return '<Product %s>' % self.sku
class ProductSummary(object):
def __repr__(self):
return '<ProductSummary %s>' % self.name
class Region(object):
def __repr__(self):
return '<Region %s>' % self.name
class Store(object):
def __repr__(self):
return '<Store %s>' % self.name
class Price(object):
def __repr__(self):
return '<Price %s at %s for $%.2f>' % (
self.product.sku, self.store.name, self.price)
Note
Using synonyms
Mapping subqueries
class MapPoint(object):
def __init__(self, lat, long):
self.coords = lat, long
def __composite_values__(self):
return self.coords
def __eq__(self, other):
return self.coords == other.coords
def __ne__(self, other):
return self.coords != other.coords
def __repr__(self):
return '(%s lat, %s long)' % self.coords
We can then map the class and use it with the composite()
function:
>>> mapper(RouteSegment, segment_table, properties=dict(
... begin=composite(MapPoint,
... segment_table.c.lat0,
... segment_table.c.long0),
... end=composite(MapPoint,
... segment_table.c.lat1,
segment_table.c.long1)))
<sqlalchemy.orm.mapper.Mapper object at 0x2b13e58a5450>
>>>
>>> work=MapPoint(33.775562,-84.29478)
>>> library=MapPoint(34.004313,-84.452062)
>>> park=MapPoint(33.776868,-84.389785)
>>> routes = [
... RouteSegment(work, library),
... RouteSegment(work, park),
... RouteSegment(library, work),
... RouteSegment(library, park),
... RouteSegment(park, library),
... RouteSegment(park, work)]
>>> for rs in routes:
... session.save(rs)
...
>>> session.flush()
>>>
>>> q = session.query(RouteSegment)
>>> print RouteSegment.begin==work
segment.lat0 = ? AND segment.long0 = ?
>>> q = q.filter(RouteSegment.begin==work)
>>> for rs in q:
... print rs
...
<Route (33.775562 lat, -84.29478 long) to (34.004313 lat,
-84.452062
... long)>
<Route (33.775562 lat, -84.29478 long) to (33.776868 lat,
-84.389785
... long)>
mapper(FullProduct, q)
entity_name=None
polymorphic_on=None
Basic Relationships
The three main relationships modeled by SQLAlchemy are
1:N, M:N, and 1:1 (which is actually a special case of 1:N). In
a 1:N relationship, one table (the “N” side) generally has a
foreign key to another table (the “1” side). In M:N, two
tables (the “primary” tables) are related via a scondary,
“join” table that has foreign keys into both primary tables. A
1:1 relationship is simply a 1:N relationship where there is
only one “N”-side row with a foreign key to any particular
“1”-side row.
1:N relations
M:N relations
It is often useful to model many-to-many (M:N) type relations
between objects. In the database, this is accomplished by the
use of an association or join table. In the following schema,
the relation between the product_table and the
category_table is a many-to-many:
category_table = Table(
'category', metadata,
Column('id', Integer, primary_key=True),
Column('level_id', None, ForeignKey('level.id')),
Column('parent_id', None, ForeignKey('category.id')),
Column('name', String(20)))
product_table = Table(
'product', metadata,
Column('sku', String(20), primary_key=True),
Column('msrp', Numeric))
product_category_table = Table(
'product_category', metadata,
Column('product_id', None, ForeignKey('product.sku'),
... primary_key=True),
Column('category_id', None, ForeignKey('category.id'),
... primary_key=True))
1:1 relations
Using BackRefs
In most cases, when mapping a relation between two tables,
we want to create a property on both classes. We can
certainly do this in SQLAlchemy by using two relation()
calls, one for each mapper, but this is verbose and potentially
leads to the two properties becoming out-of-sync with each
other. To eliminate these problems, SQLAlchemy provides
the backref parameter to the relation() function:
>>> mapper(ProductSummary, product_summary_table)
<sqlalchemy.orm.mapper.Mapper object at 0xfbba10>
>>> mapper(Product, product_table, properties=dict(
... summary=relation(ProductSummary, uselist=False,
... backref='product')))
<sqlalchemy.orm.mapper.Mapper object at 0xee7dd0>
>>>
>>> prod = session.query(Product).get('123')
>>> prod.summary = ProductSummary(name="Fruit", description="Some
... Fruit")
>>> print prod.summary
<ProductSummary Fruit>
>>> print prod.summary.product
<Product 123>
>>> print prod.summary.product is prod
True
all
Specifies that all options should be enabled except
delete-orphan:.
delete
When the parent object is marked for deletion via
session.delete(), mark the child(ren) as well.
save-update
When the parent object is attached to the session, attach
the child(ren) as well. (Attachment to a session generally
happens by calling the save(), update(), or
save_or_update() methods on the Session object.)
refresh-expire
When the parent object is refreshed (reloaded from the
database) or expired (marked as expired, to be refreshed
if any properties are subsequently read), refresh or expire
the child(ren) as well.
merge
When the parent object is merged, then merge the
child(ren) as well. Merging is the process of taking an
object and copying its state onto a persistent instance of
that object that is managed by the session.
expunge
When the parent object is expunged from the session
(removing all references to the object from the session,
the opposite of save_or_update()), expunge the
child(ren) as well.
delete-orphan
When the child object is removed from the relation (by
reassigning a 1:1 or N:1 relation or by removing it from
the list in a 1:N or M:N relation), mark the child object for
deletion. (This operation is referred to as “orphaning” the
child object by removing its relation to its parent.)
join_depth=None
class SetAndListLike(object):
__emulates__ = set
def __init__(self):
self._c = set()
@collection.appender
def append(self, o):
self._c.add(o)
def remove(self, o):
self._c.remove(o)
def __iter__(self):
return iter(self._c)
appender(cls, fn)
This decorator marks the decorated function as a
“collection appender.” The decorated function should take
one positional argument: the value to add to the
collection.
remover(cls, fn)
This decorator marks the decorated function as a
“collection remover.” The decorated function should take
one positional argument: the value to remove from the
collection.
iterator(cls, fn)
This decorator marks the decorated function as a
“collection iterator.” The decorated function should take
no arguments and return an iterator over all collection
members.
internally_instrumented(cls, fn)
This decorator prevents other decorators from being
applied to the decorated function. This is useful to prevent
“recognized” method names such as append() from being
automatically decorated.
on_link(cls, fn)
This decorator marks the decorated function as a “linked
to attribute” event handler. This event handler is called
when the collection class is linked to the
CollectionAdapter that, in turn, is linked to the relation
attribute. The decorated function should take one
positional argument: the CollectionAdapter being linked
(or None if the adapter is being unlinked). This might be
useful if you wish to perform some setup on the mapped
class or relation when your custom collection is initially
linked.
adds(cls, arg)
This decorator factory is used to create decorators that
function as “collection appenders.” The one argument to
the factory is an indicator of which parameter to the
decorated function should be added to the collection. This
argument may be specified as either an integer
(representing the position number of a positional
argument) or a string (indicating the name of the
parameter).
replaces(cls, arg)
This decorator factory is used to create decorators that
function as “collection replacers.” The one argument to
the factory is an indicator of which parameter to the
decorated function should be added to the collection. This
argument may be specified as either an integer
(representing the position number of a positional
argument) or a string (indicating the name of the
parameter). The return value from the decorated function,
if any, is used as the value to be removed from the
function.
removes(cls, arg)
This decorator factory is used to create decorators that
function as “collection removers.” The one argument to
the factory is an indicator of which parameter to the
decorated function should be removed from the collection.
This argument may be specified as either an integer
(representing the position number of a positional
argument) or a string (indicating the name of the
parameter).
removes_return(cls)
This decorator factory is used to create decorators that
function as “collection removers.” The value that is
returned from the decorated function is the value that
SQLAlchemy will consider to be removed from the
collection. This is useful for implementing a list-like
pop() method, for instance.
column_mapped_collection(mapping_spec)
Return a collection class that will be keyed by the
mapping_spec, which may be either a column from the
related table, or a list of columns from the related table.
attribute_mapped_collection(attr_name)
Return a collection class that will be keyed by the
attr_name, which is the name of an attribute on the related
class.
mapped_collection(keyfunc)
Return a collection class that will be keyed by the value
returned from the supplied keyfunc function. keyfunc takes
as its single parameter the related object and returns a
key value.
or the attribute:
mapper(Region, region_table, properties=dict(
stores=relation(Store,
collection_class=attribute_mapped_collection('name')))
Vertical Partitioning
In vertical partitioning, different mapped classes are
retrieved from different database servers. In the following
example, we create product_table in one in-memory sqlite
database and product_summary_table in another:
engine1 = create_engine('sqlite://')
engine2 = create_engine('sqlite://')
metadata = MetaData()
product_table = Table(
'product', metadata,
Column('sku', String(20), primary_key=True),
Column('msrp', Numeric))
product_summary_table = Table(
'product_summary', metadata,
Column('sku', None, ForeignKey('product.sku'), primary_key=True),
Column('name', Unicode(255)),
Column('description', Unicode))
product_table.create(bind=engine1)
product_summary_table.create(bind=engine2)
stmt = product_table.insert()
engine1.execute(
stmt,
[dict(sku="123", msrp=12.34),
dict(sku="456", msrp=22.12),
dict(sku="789", msrp=41.44)])
stmt = product_summary_table.insert()
engine2.execute(
stmt,
[dict(sku="123", name="Shoes", description="Some Shoes"),
dict(sku="456", name="Pants", description="Some Pants"),
dict(sku="789", name="Shirts", description="Some Shirts")])
Now, we can create and map the Product and
ProductSummary classes:
class Product(object):
def __init__(self, sku, msrp, summary=None):
self.sku = sku
self.msrp = msrp
self.summary = summary
def __repr__(self):
return '<Product %s>' % self.sku
class ProductSummary(object):
def __init__(self, name, description):
self.name = name
self.description = description
def __repr__(self):
return '<ProductSummary %s>' % self.name
clear_mappers()
mapper(ProductSummary, product_summary_table, properties=dict(
product=relation(Product,
backref=backref('summary', uselist=False))))
mapper(Product, product_table)
Horizontal Partitioning
In horizontal partitioning, or “sharding,” the database
schema (or part of it) is replicated across multiple databases
(“shards”). This essentially means that some rows of a
mapped table will be loaded from one database and some
from another. To use sharding, you must provide functions
that identify which database to access in various situations.
These arguments are passed to the sessionmaker( ) function,
along with a class_ parameter specifying that we will be
creating a ShardedSession:
Session = sessionmaker(class_=ShardedSession)
metadata = MetaData()
product_table = Table(
'product', metadata,
Column('sku', String(20), primary_key=True),
Column('msrp', Numeric))
metadata.create_all(bind=engine1)
metadata.create_all(bind=engine2)
class Product(object):
def __init__(self, sku, msrp):
self.sku = sku
self.msrp = msrp
def __repr__(self):
return '<Product %s>' % self.sku
clear_mappers()
product_mapper = mapper(Product, product_table)
Session = sessionmaker(class_=ShardedSession)
session = Session(
shard_chooser=shard_chooser,
id_chooser=id_chooser,
query_chooser=query_chooser,
shards=dict(even=engine1,
odd=engine2))
Creating a Session
The first step in creating a session is to obtain a Session
object from SQLAlchemy. One way to do this is to directly
instantiate the sqlalchemy.orm.session.Session class.
However, this constructor for the SQLAlchemy-provided
Session has a number of keyword arguments, making
instantiating Sessions in this manner verbose and tedious.
In order to alleviate this burden, SQLAlchemy provides the
sessionmaker() function, which returns a subclass of
orm.session.Session with default arguments set for its
constructor.
Once you have this customized Session class, you can
instantiate it as many times as necessary in your application
without needing to retype the keyword arguments (which in
many applications will not change between Session
instantiations). If you wish to override the defaults supplied
to sessionmaker, you can do so at Session instantiation
time. You can also modify the default arguments bound to a
particular Session subclass by calling the class method
Session.configure():
# Create a Session class with the default
# options
Session = sessionmaker(bind=engine)
bind=None
autoflush=True
engine = create_engine('sqlite://')
metadata = MetaData(engine)
product_table = Table(
'product', metadata,
Column('sku', String(20), primary_key=True),
Column('msrp', Numeric))
class Product(object):
def __init__(self, sku, msrp, summary=None):
self.sku = sku
self.msrp = msrp
self.summary = summary
self.categories = []
self.prices = []
def __repr__(self):
return '<Product %s>' % self.sku
mapper(Product, product_table)
Transient
The object exists in memory only. It is not attached to a
session, and it has no representation in the database. A
Transient object has no relationship to the ORM other
than the fact that its class has an associated mapper().
Pending
A Pending object has been marked for insertion into the
database at the next flush() operation. Transient objects
become Pending when they are save()d to the Session.
Persistent
The object is present in both the session and the
database. Persistent objects are created either by
flush()ing Pending objects or by querying the database
for existing instances.
Detached
The object has a corresponding record in the database,
but is not attached to any session. Detached objects
cannot issue any SQL automatically to load related
objects or attributes, unlike Persistent objects. An object
becomes detached if it is explicitly expunge()d from the
session.
category_table = Table(
'category', metadata,
Column('id', Integer, primary_key=True),
Column('level_id', None, ForeignKey('level.id')),
Column('parent_id', None, ForeignKey('category.id')),
Column('name', String(20)))
product_category_table = Table(
'product_category', metadata,
Column('product_id', None, ForeignKey('product.sku'),
... primary_key=True),
Column('category_id', None, ForeignKey('category.id'),
... primary_key=True))
class Product(object):
def __init__(self, sku, msrp, summary=None):
self.sku = sku
self.msrp = msrp
self.summary = summary
self.categories = []
self.prices = []
def __repr__(self):
return '<Product %s>' % self.sku
class Level(object):
def __init__(self, name, parent=None):
self.name = name
self.parent = parent
def __repr__(self):
return '<Level %s>' % self.name
class Category(object):
def __init__(self, name, level, parent=None):
self.name = name
self.level = level
self.parent = parent
def __repr__(self):
return '<Category %s.%s>' % (self.level.name, self.name)
Now that we have created all the objects and specified the
relations between them, we can save one object to the
Session, and all related objects will be saved as well (this is
due to the default 'save-update' value of the cascade
parameter in all the relations() created). In this example,
the department object is connected to all the other objects
through various relation()s, so it is sufficient to save it
alone. Once this is done, we can flush the changes out to the
database. For the purposes of brevity, we will use a fresh
session with echo_uow set to False:
>>> session = Session(echo_uow=False)
>>> session.save(department)
>>> session.flush()
2007-10-28 18:41:10,042 INFO sqlalchemy.engine.base.Engine.0x..90
... BEGIN
2007-10-28 18:41:10,043 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO product (sku, msrp) VALUES (?, ?)
2007-10-28 18:41:10,043 INFO sqlalchemy.engine.base.Engine.0x..90
... ['111', '55.95']
2007-10-28 18:41:10,045 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO product (sku, msrp) VALUES (?, ?)
2007-10-28 18:41:10,045 INFO sqlalchemy.engine.base.Engine.0x..90
... ['222', '15.95']
2007-10-28 18:41:10,047 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO level (parent_id, name) VALUES (?, ?)
2007-10-28 18:41:10,047 INFO sqlalchemy.engine.base.Engine.0x..90
... [None, 'Department']
2007-10-28 18:41:10,049 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO level (parent_id, name) VALUES (?, ?)
2007-10-28 18:41:10,049 INFO sqlalchemy.engine.base.Engine.0x..90
... [1, 'Class']
2007-10-28 18:41:10,053 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO level (parent_id, name) VALUES (?, ?)
2007-10-28 18:41:10,053 INFO sqlalchemy.engine.base.Engine.0x..90
... [2, 'SubClass']
2007-10-28 18:41:10,057 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO category (level_id, parent_id, name) VALUES (?, ?,
... ?)
2007-10-28 18:41:10,057 INFO sqlalchemy.engine.base.Engine.0x..90
... [1, None, 'Bottoms']
2007-10-28 18:41:10,059 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO category (level_id, parent_id, name) VALUES (?, ?,
... ?)
2007-10-28 18:41:10,059 INFO sqlalchemy.engine.base.Engine.0x..90
... [1, None, 'Tops']
2007-10-28 18:41:10,060 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO category (level_id, parent_id, name) VALUES (?, ?,
... ?)
2007-10-28 18:41:10,060 INFO sqlalchemy.engine.base.Engine.0x..90
... [2, 1, 'Pants']
2007-10-28 18:41:10,062 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO category (level_id, parent_id, name) VALUES (?, ?,
... ?)
2007-10-28 18:41:10,063 INFO sqlalchemy.engine.base.Engine.0x..90
... [2, 2, 'Shirts']
2007-10-28 18:41:10,065 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO category (level_id, parent_id, name) VALUES (?, ?,
... ?)
2007-10-28 18:41:10,065 INFO sqlalchemy.engine.base.Engine.0x..90
... [3, 4, 'T-Shirts']
2007-10-28 18:41:10,066 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO category (level_id, parent_id, name) VALUES (?, ?,
... ?)
2007-10-28 18:41:10,066 INFO sqlalchemy.engine.base.Engine.0x..90
... [3, 4, 'Dress Shirts']
2007-10-28 18:41:10,068 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO category (level_id, parent_id, name) VALUES (?, ?,
... ?)
2007-10-28 18:41:10,068 INFO sqlalchemy.engine.base.Engine.0x..90
... [3, 3, 'Slacks']
2007-10-28 18:41:10,069 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO category (level_id, parent_id, name) VALUES (?, ?,
... ?)
2007-10-28 18:41:10,070 INFO sqlalchemy.engine.base.Engine.0x..90
... [3, 3, 'Denim']
2007-10-28 18:41:10,071 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO product_category (product_id, category_id) VALUES
... (?, ?)
2007-10-28 18:41:10,072 INFO sqlalchemy.engine.base.Engine.0x..90
... [['222', 2], ['111', 1], ['111', 8], ['222', 4], ['111', 3],
... ['222', 5]]
mapper(Account, account_table)
Extending Sessions
Similar to the MapperExtension covered in Chapter 6,
SessionExtensions can be used to hook into session
operations. Unlike MapperExtensions, SessionExtensions
cannot modify the process that they “hook into” easily,
making SessionExtensions more useful for recording
Session operations than influencing them directly.
SessionExtensions are installed via the extension parameter
to the Session constructor.
The various methods that a subclass of SessionExtension
can implement are described here:
before_commit(self, session)
Called just before a commit is executed.
after_commit(self, session)
Called just after a commit is executed.
after_rollback(self, session)
Called just after a rollback has occurred.
before_flush(self, session, flush_context, objects)
Called just before the flush process starts. The objects
parameter is the optional list of objects passed to the
Session’s flush() method.
after_flush(self, session, flush_context)
Called just after the flush process completes, but before
any commit(). The session’s properties at this point still
show their pre-flush state.
after_flush_postexec(self, session, flush_context)
Called just after the flush process completes, as well as
after any automatic commit() occurs. (If no explicit
transaction is specified, all flush()es generate their own
transactions.) The session’s properties at this point show
their final, post-flush state.
Querying at the ORM Level
Saving and updating objects via SQLAlchemy’s ORM
interface isn’t very useful without the ability to retrieve
objects from the database. This is where the Session’s
query() method comes in handy. To retrieve an iterator over
all the objects of a particular type in the database, simply
specify either the class you wish to query or its mapper:
>>> query = session.query(Product)
>>> print query
SELECT product.sku AS product_sku, product.msrp AS product_msrp
FROM product ORDER BY product.oid
>>> for obj in query:
... print obj
...
2007-11-16 16:19:42,669 INFO sqlalchemy.engine.base.Engine.0x..90
... SELECT product.sku AS product_sku, product.msrp AS product_msrp
FROM product ORDER BY product.oid
2007-11-16 16:19:42,669 INFO sqlalchemy.engine.base.Engine.0x..90
[]
<Product 123>
<Product 456>
<Product 222>
asc( self )
Return a clause representing the mapped column in
ascending order.
between(self, cleft, cright)
Generate a BETWEEN clause with the specified left and
right values (column BETWEEN cleft AND cright).
concat(self, other)
Generate a clause that concatenates the value of the
column with the value given.
desc( self )
Generate a clause representing the mapped column in
ascending order.
distinct( self )
Generate a clause that limits the result set to rows with
distinct values for this column.
endswith(self, other)
Generate a clause (using LIKE) that implements the
Python endswith() string method.
in_(self, other)
Generate an IN clause with other as the righthand side.
other may be either a sequence of literal values or a
selectable.
like(self, other)
Generate a LIKE clause with other as the righthand side.
startswith(self, other)
Generate a clause (using LIKE) that implements the
Python startswith() string method.
get( ident )
Retrieve an object by its identity (the primary key of the
mapped selectable). If there is no object identified by that
key, return None. get() is also available as a method on
the Session object.
first()
Retrieve the first result from the query. If there are no
results, return None. This is equivalent to query.all()[0].
one()
Retrieve the first result from the query, raising an
exception unless the query returns exactly one result. This
is implemented by executing the query with a limit of 2. If
either 0 or 2 rows are returned, an exception is raised.
Otherwise, the single object is returned.
extension( ext )
Add the MapperExtension ext into the beginning of the list
of extensions that will be called in the context of the
query.
eagerload( name )
Set the load strategy on the named relation() property
to be eager (equivalent to specifying lazy=False in the
mapper() call). For mapped column properties, use
undefer() instead.
eagerload_all( name )
name is a string containing a list of dot-separated names
that represent a chain of relation() properties to be
eager loaded. For mapped column properties, use
undefer() instead.
lazyload( name )
Set the load strategy on the named relation() property
to be lazy (equivalent to specifying lazy=True in the
mapper() call). For mapped column properties, use
defer() instead.
noload( name )
Set the load strategy on the named property to be
nonloading (equivalent to specifying lazy=None in the
mapper() calls).
contains_alias( alias )
Indicates to the query that the main table in the
underlying select statement has been aliased to the given
alias (which is a string or Alias object).
group_by(self, criterion)
Apply a SQL GROUP BY clause to the query and return
the resulting query. This is generally useful in ORM
queries only when you are grouping by the main class and
aggregating over some related class. For instance, if a
Product had many Recommendations, you might group by
the product’s sku and add a having() clause to return
products with three or more recommendations.
having(self, criterion)
Apply a SQL HAVING clause to the query and return the
resulting query.
instances(self, cursor)
Return a list of mapped instances corresponding to rows
in the given cursor (generally a ResultProxy).
instances() takes other parameters, but they are
deprecated in SQLAlchemy 0.4.
join(self, prop, id=None, aliased=False,
from_joinpoint=False)
class Clothing(Product):
def __init__(self, sku, msrp, clothing_info):
Product.__init__(self, sku, msrp)
self.clothing_info = clothing_info
class Accessory(Product):
def __init__(self, sku, msrp, accessory_info):
Product.__init__(self, sku, msrp)
self.accessory_info = accessory_info
Figure 8-1. Sample inheritance hierarchy
Single Table Inheritance Mapping
In single table inheritance, a single table is used to represent
all the different types in the class hierarchy, as shown in
Figure 8-2.
mapper(Clothing, inherits=Product,
polymorphic_identity='C')
mapper(Accessory, inherits=Product,
polymorphic_identity='A')
clothing_table = Table(
'clothing', metadata,
Column('sku', String(20), primary_key=True),
Column('msrp', Numeric),
Column('clothing_info', String))
accessory_table = Table(
'accessory', metadata,
Column('sku', String(20), primary_key=True),
Column('msrp', Numeric),
Column('accessory_info', String))
accessory_table = Table(
'accessory', metadata,
Column('sku', None, ForeignKey('product.sku'),
primary_key=True),
Column('accessory_info', String))
Now, when we iterate over all the Products, we see that the
auxiliary queries have been eliminated:
>>> session.clear()
>>> session.query(Product).all()
2007-11-19 21:25:44,320 INFO sqlalchemy.engine.base.Engine.0x..d0
... SELECT product.sku AS product_sku, product.msrp AS
product_msrp,
... product.product_type AS product_product_type
FROM product ORDER BY product.oid
2007-11-19 21:25:44,321 INFO sqlalchemy.engine.base.Engine.0x..d0
[]
[<Product 123>, <Product 456>, <Clothing 789>, <Clothing 111>,
... <Accessory 222>, <Accessory 333>]
Using select_table
mapper(
Product, product_table,
polymorphic_on=product_table.c.product_type,
polymorphic_identity='P',
select_table=pjoin)
inventory_table = Table(
'inventory', metadata,
Column('store_id', None, ForeignKey('store.id')),
Column('product_id', None, ForeignKey('product.sku')),
Column('quantity', Integer, default=0)
product_table = Table(
'product', metadata,
Column('sku', String(20), primary_key=True),
Column('msrp', Numeric),
Column('vendor_id', None, ForeignKey('vendor.id'))
clothing_table = Table(
'clothing', metadata,
Column('sku', String(20), primary_key=True),
Column('msrp', Numeric),
Column('vendor_id', None, ForeignKey('vendor.id'),
Column('clothing_info', String))
accessory_table = Table(
'accessory', metadata,
Column('sku', String(20), primary_key=True),
Column('msrp', Numeric),
Column('vendor_id', None, ForeignKey('vendor.id'),
Column('accessory_info', String))
punion = polymorphic_union(
dict(P=product_table,
C=clothing_table,
A=accessory_table),
'type_')
mapper(
Product, product_table, select_table=punion,
polymorphic_on=punion.c.type_,
polymorphic_identity='P')
Nonpolymorphic Inheritance
All of the inheritance relationships shown so far were
implemented using SQLAlchemy’s polymorphic loading. If
polymorphic loading is not desired, either because of its
overhead or because you always know what types of
classes you will be fetching, it is possible to use
nonpolymorphic loading by omitting all of the polymorphic_*
parameters from the mappers.
Nonpolymorphic loading will always return the type of
object being selected in the case of a query (never the child
class, as polymorphic loading does). Relations to
nonpolymorphic classes also apply only to the actual class
being mapped, not to its descendants. Polymorphic loading
is much more flexible than nonpolymorphic loading, and
therefore should probably be selected unless the
performance overhead is prohibitive.
Chapter 9. Elixir: A Declarative Extension to
SQLAlchemy
This chapter describes Elixir, a module developed to
automate some of the more common tasks in SQLAlchemy by
providing a declarative layer atop “base” or “raw”
SQLAlchemy. This chapter also describes the various
extensions to Elixir that provide features such as encryption
and versioning.
Introduction to Elixir
The Elixir module was developed as a declarative layer on
top of SQLAlchemy, implementing the “active record”
pattern described in Chapter 6. Elixir goes out of its way to
make all of the power of SQLAlchemy available, while
providing sensible default behavior with significantly less
code than “raw” SQLAlchemy. This chapter describes
versions 0.4 and 0.5 of Elixir, corresponding to the 0.4
version of SQLAlchemy. Differences between versions 0.4
and 0.5 are discussed in the upcoming sidebar, Differences
Between Elixir 0.4 and 0.5.”
So, what exactly does Elixir do? Well, consider a simple
product database. In SQLAlchemy, we might set up the
products, stores, and prices with the following code:
product_table = Table(
'product', metadata,
Column('sku', String(20), primary_key=True),
Column('msrp', Numeric))
store_table = Table(
'store', metadata,
Column('id', Integer, primary_key=True),
Column('name', Unicode(255)))
product_price_table = Table(
'product_price', metadata,
Column('sku', None, ForeignKey('product.sku'), primary_key=True),
Column('store_id', None, ForeignKey('store.id'), primary_key=True),
Column('price', Numeric, default=0))
class Product(object):
def __init__(self, sku, msrp):
self.sku = sku
self.msrp = msrp
self.prices = []
def __repr__(self):
return '<Product %s>' % self.sku
class Store(object):
def __init__(self, name):
self.name = name
def __repr__(self):
return '<Store %s>' % self.name
class Price(object):
def __init__(self, product, store, price):
self.product = product
self.store = store
self.price = price
def __repr__(self):
return '<Price %s at %s for $%.2f>' % (
self.product.sku, self.store.name, self.price)
def __repr__(self):
return '<Product %s>' % self.sku
class Store(Entity):
name=Field(Unicode(255))
prices=OneToMany('Price')
def __repr__(self):
return '<Store %s>' % self.name
class Price(Entity):
price=Field(Numeric, default=0)
product=ManyToOne('Product')
store=ManyToOne('Store')
def __repr__(self):
return '<Price %s at %s for $%.2f>' % (
self.product.sku, self.store.name, self.price)
Note
metadata.bind = 'sqlite://'
class Product(Entity):
has_field('sku', String(20), primary_key=True)
has_field('msrp', Numeric)
has_many('prices', of_kind='Price')
class Store(Entity):
has_field('name', Unicode(255))
has_many('prices', of_kind='Price')
class Price(Entity):
has_field('price', Numeric, default=0)
belongs_to('product', of_kind='Product')
belongs_to('store', of_kind='Store')
Note
Two things are important to note here. The first is that our
has_field() statement did indeed create a “proxy”
statement to the Store entity’s name field. The second is
Elixir’s naming convention. By default, tables created to
implement entities have names generated by combining the
module name with the entity name.
Elixir deferred properties
Relations
Relations with Elixir are extremely similar to relations using
“bare” SQLAlchemy, except that in Elixir, relations are
defined by their cardinality (one to many, one to one, etc.)
rather than inferred by foreign key relationships. In fact,
Elixir will automatically create the foreign key columns
necessary to implement the relations as defined.
Attribute-based syntax
Warning
DSL syntax
Inheritance
Inheritance in Elixir is handled via either the single table
inheritance mapping or the joined table inheritance mapping
supported by SQLAlchemy (and described in detail in
Chapter 8). Elixir also supports specifying whether
polymorphic or nonpolymorphic loading should be used with
the mapped classes. Both the inheritance method (joined
table or single table) and whether the loader should be
polymorphic are specified via the DSL statement
using_options(). There is currently no corresponding
attribute-based syntax for specifying options on entities. So,
to create the Product, Clothing, and Accessory hierarchy
described in Chapter 8 in Elixir as a joined table (“multiple”)
and polymorphic hierarchy, we would write the following
(with methods omitted for clarity):
class Product(Entity):
using_options(inheritance='multi', polymorphic=True)
sku=Field(String(20), primary_key=True)
msrp=Field(Numeric)
class Clothing(Product):
using_options(inheritance='multi', polymorphic=True)
clothing_info=Field(String)
class Accessory(Product):
using_options(inheritance='multi', polymorphic=True)
accessory_info=Field(String)
Associable Extension
In many database schemas, there may be one table that
relates to many others via a many-to-many or a many-to-one
join. The elixir.ext.associable extension provides a
convenient way to specify this pattern and to generate the
appropriate association tables. This is accomplished by the
associable() function, which returns a DSL statement that
can be used in the definition of the related entities.
For instance, suppose we have a schema that represents
brands and retailers, each of which may have multiple
addresses stored in the database. This can be accomplished
as follows:
class Address(Entity):
has_field('street', Unicode(255))
has_field('city', Unicode(255))
has_field('postal_code', Unicode(10))
class Brand(Entity):
has_field('name', Unicode(255)),
has_field('description', Unicode)
is_addressable()
class Retailer(Entity):
has_field('name', Unicode(255)),
has_field('description', Unicode)
is_addressable()
plural_name=None
name=None
Encrypted Extension
The elixir.ext.encrypted extension provides encrypted
field support for Elixir using the Blowfish algorithm from the
PyCrypto library, which must be installed separately.
(PyCrypto is available from the Python Package Index via
“easy_install pycrypto”.) The encrypted extension provides
the DSL statement acts_as_encrypted( ), which takes
the following parameters:
for_fields=[]
Versioned Extension
The elixir.ext.versioned extension provides a history and
versioning for the fields in an entity. These services are
provided by the acts_as_versioned() DSL statement.
Marking an entity as versioned will apply the following
operations:
def __repr__(self):
return '<Product %s, mrsp %s>' % (self.sku, self.msrp)
@after_revert
def price_rollback(self):
print "Rolling back prices to %s" % self.msrp
we can then use the audit trail as follows:
>>> prod = Product(sku='123', msrp=11.22)
>>> session.flush()
>>> print prod
<Product 123, mrsp 11.22>
>>> print prod.version
1
>>> print len(prod.versions)
1
>>> prod.msrp *= 1.2
>>> session.flush()
>>> print prod
<Product 123, mrsp 13.464>
>>> print prod.version
2
>>> prod.msrp *= 1.3
>>> session.flush()
>>> print prod
<Product 123, mrsp 17.5032>
>>> print prod.version
3
>>> print prod.compare_with(prod.versions[0])
{'timestamp': (datetime.datetime(2007, 11, 21, 15, 50, 43, 951977),
... datetime.datetime(2007, 11, 21, 15, 50, 43, 893200)), 'msrp':
... (17.5032, Decimal("11.22"))}
>>> for ver in prod.versions:
... print ver.version, ver.timestamp, ver.msrp
...
1 2007-11-21 15:50:43.893200 11.22
2 2007-11-21 15:50:43.939225 13.464
3 2007-11-21 15:50:43.951977 17.5032
>>> prod.revert()
Rolling back prices to 17.5032
>>> prod = Product.get('123')
>>> print prod
<Product 123, mrsp 17.5032>
revert( self )
Revert the entity to the last version saved in the history
table. After reverting, the instance in memory will be
expired and must be refreshed to retrieve the reverted
fields.
revert_to(self, to_version)
Revert the entity to a particular version number saved in
the history table. After reverting, the instance in memory
will be expired and must be refreshed to retrieve the
reverted fields.
compare_with(self, version)
Compare the current field values of the entity with the
values in a particular version instance. The return value
from this method is a dict keyed by the field name with
values of pairs (named_version_value, current_value). Note
that instances in the entity’s versions attribute also have
a compare_with() method, allowing historical versions to
be compared with other versions.
get_as_of(self, dt)
Retrieves the most recent version of the entity that was
saved before the datetimedt. If the current version is the
most recent before dt, then it is returned.
Introduction to SqlSoup
If Elixir is ideally suited for blue sky, legacy-free
development, SqlSoup is ideally suited for connecting to
legacy databases. In fact, SqlSoup provides no method of
defining a database schema through tables, classes, and
mappers; it uses extensive autoloading to build the
SQLAlchemy constructs (Tables, classes, and mapper()s)
automatically from an existing database.
To illustrate the uses of SQLAlchemy in this chapter, we will
use the following SQLAlchemy-created schema. Note that,
unlike in previous chapters, we will be saving the test
database in an on-disk SQLite database rather than using an
in-memory database, to illustrate the fact that SqlSoup relies
entirely on auto loading:
from sqlalchemy import *
engine = create_engine('sqlite:///chapter10.db')
metadata = MetaData(engine)
product_table = Table(
'product', metadata,
Column('sku', String(20), primary_key=True),
Column('msrp', Numeric))
store_table = Table(
'store', metadata,
Column('id', Integer, primary_key=True),
Column('name', Unicode(255)))
product_price_table = Table(
'product_price', metadata,
Column('sku', None, ForeignKey('product.sku'), primary_key=True),
Column('store_id', None, ForeignKey('store.id'), primary_key=True),
Column('price', Numeric, default=0))
metadata.create_all()
stmt = product_table.insert()
stmt.execute([dict(sku="123", msrp=12.34),
dict(sku="456", msrp=22.12),
dict(sku="789", msrp=41.44)])
stmt = store_table.insert()
stmt.execute([dict(name="Main Store"),
dict(name="Secondary Store")])
stmt = product_price_table.insert()
stmt.execute([dict(store_id=1, sku="123"),
dict(store_id=1, sku="456"),
dict(store_id=1, sku="789"),
dict(store_id=2, sku="123"),
dict(store_id=2, sku="456"),
dict(store_id=2, sku="789")])
bind(attribute)
The underlying Engine or Connectable for this SqlSoup
instance.
schema(attribute)
Use the specified schema name for auto loading and
automatically mapping tables.
clear(self )
Call the underlying contextual session’s clear() method.
delete(self, *args, **kwargs)
Call the underlying contextual session’s delete() method
with the specified arguments.
flush(self)
Call the underlying contextual session’s flush() method.
join(self, *args, *kwargs)
Call SQLAlchemy’s join() function with the specified
arguments and return an automatically mapped object
corresponding to rows of the generated join.
map(self, selectable, *kwargs)
Automatically map an arbitrary selectable, returning the
generated mapped class.
with_labels(self, item)
Add labels to the columns of item (generally a join) based
on the name of their table of origin. This is useful when
mapping joins between two tables with the same column
names.
c (attribute)
The c attribute of the underlying table.
query (attribute)
An ORM-based query object on the automatically mapped
class.
_table (attribute)
The underlying selectable to this automatically mapped
object. Useful when dropping to the SQL layer in SqlSoup
queries.
column_name (attribute)
The SQLAlchemy property object of the automatically
mapped column.
delete(cls, *args, **kwargs) (classmethod)
Execute a delete() on the underlying table with the
given arguments.
insert(cls, **kwargs) (classmethod)
Execute an insert() on the underlying table with the
given arguments, and return a newly constructed instance
of the automatically mapped class.
update(cls, whereclause=None, values=None, **kwargs)
(classmethod)
Execute an update() on the underlying table with the
given arguments.
In order to chain the join object to other tables, just use the
join() method again:
>>> join2 = db.join(join1, db.store, isouter=True)
>>> join2.all()
[MappedJoin(sku='123',msrp=Decimal("12.34"),store_id=1,
... price=Decimal("0"),id=1,name='Main Store'),
... MappedJoin(sku='123',msrp=Decimal("12.34"),store_id=2,
... price=Decimal("0"),id=2,name='Secondary Store'),
... MappedJoin(sku='456',msrp=Decimal("22.12"),store_id=1,
... price=Decimal("0"),id=1,name='Main Store'),
... MappedJoin(sku='456',msrp=Decimal("22.12"),store_id=2,
... price=Decimal("0"),id=2,name='Secondary Store'),
... MappedJoin(sku='789',msrp=Decimal("41.44"),store_id=1,
... price=Decimal("0"),id=1,name='Main Store'),
... MappedJoin(sku='789',msrp=Decimal("41.44"),store_id=2,
... price=Decimal("0"),id=2,name='Secondary Store'),
...
MappedJoin(sku='111',msrp=Decimal("22.44"),store_id=None,price=None,
... id=None,name=None)]
... store_id=1,price=Decimal("0"))
Succinct usage
SqlSoup requires very little code to get started: just a
database URI, and you’re ready to go. Raw SQLAlchemy is
much more verbose, requiring setup for tables, mappers,
and mapped classes. Even if you’re using autoloading with
SQLAlchemy, it still requires you to set up your mappers
and mapped classes if you wish to use the ORM.
Ad-hoc queries and mappers
Due to the ease of setting up SqlSoup, it is much more
convenient to create queries and mappings from joins and
other selectable objects.
Succinct usage
Although not as terse as SqlSoup, Elixir generally requires
less code than raw SQLAlchemy to implement similar
functionality. This is particularly true when using the
associable and versioned extensions, for instance.
Rapid model development
Because Elixir generally sees itself as the keeper of the
schema, it can be more aggressive in what types of
schema it supports. When using the associable
extension, for instance, it is possible to create auxiliary
tables with a single DSL line of code. This allows complex
schema to be developed rapidly when your application is
first being written.
Clear separation of concerns
Due to the data mapper pattern used in SQLAlchemy
(rather than the active record pattern used in Elixir), it is
clear where the database schema resides (in theTable()
classes), where the application logic resides (in the
mapped classes), and where the mapping occurs (in the
mapper() configuration). Elixir puts all this information
into the Entity classes, making it a bit more difficult to
separate these concerns.
Ability to use or migrate existing schemas
Elixir’s aggressiveness in defining new tables and
columns implicitly based on DSL statements in the Entity
classes can make it challenging to use with an existing
database. In such a situation, it’s important to be aware of
what schema changes are implied by each change to the
Entity classes and/or to have access to a schema
migration tool that can assist in migrating existing
databases.
Flexibility
Raw SQLAlchemy’s win over Elixir is much more limited
than its win over SqlSoup, mainly because Elixir provides
convenient ways to “drop into” the underlying
SQLAlchemy tables, mappers, and classes. SQLAlchemy
still wins on flexibility over Elixir, however, as it is, in fact,
necessary to drop into regular SQLAlchemy to model
some things when using Elixir.
Chapter 11. Other SQLAlchemy Extensions
SQLAlchemy provides an extremely powerful method of
defining schemas, performing queries, and manipulating
data, both at the ORM level and at the SQL level.
SQLAlchemy also provides several extensions to this core
behavior. We have already seen one of these extensions,
SqlSoup, discussed in Chapter 10. One of the nice things
about the SQLAlchemy extensions package is that it provides
a “proving ground” for functionality that may eventually
make it into the core SQLAlchemy packages. When this
occurs (the functionality of an extension is absorbed into the
core feature set of SQLAlchemy), the extension is deprecated
and eventually removed.
This chapter discusses the two remaining nondeprecated
extensions available in SQLAlchemy 0.4,
sqlalchemy.ext.associationproxy and
sqlalchemy.ext.orderinglist. We will also describe the
deprecated extensions, focusing on how to achieve the same
functionality using “core” SQLAlchemy.
Association Proxy
The association proxy extension allows our mapped classes
to have attributes that are proxied from related objects. One
place where this is useful is when we have two tables related
via an association table that contains extra information in
addition to linking the two tables. For instance, suppose we
have a database containing the following schema:
user_table = Table(
'user', metadata,
Column('id', Integer, primary_key=True),
Column('user_name', String(255), unique=True),
Column('password', String(255)))
brand_table = Table(
'brand', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(255)))
sales_rep_table = Table(
'sales_rep', metadata,
Column('brand_id', None, ForeignKey('brand.id'), primary_key=True),
Column('user_id', None, ForeignKey('user.id'), primary_key=True),
Column('commission_pct', Integer, default=0))
class Brand(object):
users=association_proxy('sales_reps', 'user')
class Brand(object):
def __init__(self, name=None):
self.name = name
class SalesRep(object):
def __init__(self, user=None, brand=None, commission_pct=0):
self.user = user
self.brand = brand
self.commission_pct=commission_pct
reps_by_user_class=attribute_mapped_collection('user')
mapper(Brand, brand_table, properties=dict(
sales_reps_by_user=relation(
SalesRep, backref='brand',
collection_class=reps_by_user_class)))
Brand.commissions=association_proxy(
'sales_reps_by_user', 'commission_pct',
creator=lambda key,value: SalesRep(user=key, commission_pct=value))
todo_item_table = Table(
'todo_item', metadata,
Column('id', Integer, primary_key=True),
Column('list_name', None, ForeignKey('todo_list.name')),
Column('list_position', Integer),
Column('value', Unicode))
class TodoItem(object):
def __init__(self, value, position=None):
self.value = value
self.list_position = position
def __repr__(self):
return '<%s: %s>' % (self.list_position, self.value)
Rather than “fixing up” the list after each insert or remove
operation, we can instead use orderinglist to keep track of
the list_position attribute automatically:
>>> from sqlalchemy.ext.orderinglist import ordering_list
>>>
>>> mapper(TodoList, todo_list_table, properties=dict(
... items=relation(TodoItem,
... backref='list',
... order_by=[todo_item_table.c.list_position],
... collection_class
... =ordering_list('list_position'))))
<sqlalchemy.orm.mapper.Mapper object at 0xbcb850>
>>> mapper(TodoItem, todo_item_table)
<sqlalchemy.orm.mapper.Mapper object at 0xbcb710>
>>>
>>> session.clear()
>>> lst = session.get(TodoList, 'list1')
>>> print lst.items
[<0: Buy groceries>, <1: Mow lawn>, <2: Do laundry>]
>>> del lst.items[1]
>>> print lst.items
[<0: Buy groceries>, <1: Do laundry>]
>>> session.flush()
sqlalchemy.ext.selectresults
The sqlalchemy.ext.selectresults extension provided
generative query support for ORM queries. Since version
0.3.6, this support has been built in to the native Query
class. sqlalchemy.ext.selectresults also provides a
MapperExtension that adds generative query behavior on
a per-mapper basis.
sqlalchemy.ext.sessioncontext
The sqlalchemy.ext.sessioncontext extension provided
contextual session support. This has been deprecated in
favor of the scoped_session() support in core
SQLAlchemy.
sqlalchemy.ext.assignmapper
The sqlalchemy.ext.assignmapper extension provided
the ability to automatically save mapped objects and
additional instrumentation on mapped classes above what
the mapper() function normally does. This has been
deprecated in favor of the Session.mapper() function
available with contextual sessions created by
scoped_session() in core SQLAlchemy.
sqlalchemy.ext.activemapper
The sqlalchemy.ext.activemapper extension provided a
declarative layer implementing the active record pattern
on SQLAlchemy. This has been deprecated in favor of the
external package Elixir (Chapter 9), a more
comprehensive declarative layer.
Index
Symbols
name argument
Column constructor, Column Definitions
Table constructor, Defining Tables
name parameter
ForeignKey constructor, Foreign keys
Sequence constructor, Creating Explicit Sequences
name parameter (Constraint class), Constraints
“named colon” format, Using literal text in queries
nested loops, The Object/Relational “Impedance Mismatch”
__ne__( ) method, Mapping composite values
noload( ) method, ORM Querying with Joins
nonpolymorphic inheritance, Relations and Inheritance
non_primary parameter (mapper( ) function), Other
mapper( ) Parameters
NOT boolean operator, Operators and functions in WHERE
clauses
nullable argument (Column constructor), Column Definitions
NullPool pool type (sqlalchemy.pool), Connection Pooling